some Network changes.

This commit is contained in:
Bob
2010-01-23 16:08:36 +13:00
parent f1e0b46d38
commit 3bb40f7d0e
21 changed files with 282 additions and 363 deletions

View File

@@ -76,7 +76,7 @@ namespace OpenRA.Server
// assign the player number.
newConn.PlayerIndex = ChooseFreePlayerIndex();
newConn.socket.Send( BitConverter.GetBytes( newConn.PlayerIndex ) );
conns.Add(newConn);
lobbyInfo.Clients.Add(

View File

@@ -16,7 +16,7 @@ namespace OpenRa
{
if (isChatting && typing.Length > 0)
{
Game.controller.AddOrder(Order.Chat(Game.world.LocalPlayer, typing));
Game.orderManager.IssueOrder(Order.Chat(Game.world.LocalPlayer, typing));
AddLine(Game.world.LocalPlayer, typing);
}

View File

@@ -382,7 +382,7 @@ namespace OpenRa
{
var queue = world.LocalPlayer.PlayerActor.traits.Get<Traits.ProductionQueue>();
foreach( var item in queue.AllItems( groupName ) )
Game.controller.AddOrder(Order.CancelProduction(world.LocalPlayer, item.Item));
Game.orderManager.IssueOrder(Order.CancelProduction(world.LocalPlayer, item.Item));
}
void ChooseAvailableTab( World world )
@@ -760,7 +760,7 @@ namespace OpenRa
{
var unit = Rules.Info[item];
Sound.Play(unit.Traits.Contains<BuildingInfo>() ? "abldgin1.aud" : "train1.aud");
Game.controller.AddOrder(Order.StartProduction(world.LocalPlayer, item));
Game.orderManager.IssueOrder(Order.StartProduction(world.LocalPlayer, item));
}
void HandleBuildPalette( World world, string item, bool isLmb )
@@ -785,7 +785,7 @@ namespace OpenRa
if (producing.Paused)
{
Game.controller.AddOrder(Order.PauseProduction(player, item, false));
Game.orderManager.IssueOrder(Order.PauseProduction(player, item, false));
return;
}
}
@@ -800,12 +800,12 @@ namespace OpenRa
if (producing.Paused || producing.Done || producing.TotalCost == producing.RemainingCost)
{
Sound.Play("cancld1.aud");
Game.controller.AddOrder(Order.CancelProduction(player, item));
Game.orderManager.IssueOrder(Order.CancelProduction(player, item));
}
else
{
Sound.Play("onhold1.aud");
Game.controller.AddOrder(Order.PauseProduction(player, item, true));
Game.orderManager.IssueOrder(Order.PauseProduction(player, item, true));
}
}
}

View File

@@ -41,14 +41,12 @@ namespace OpenRa
}
}
List<Order> recentOrders = new List<Order>();
void ApplyOrders(World world, float2 xy, MouseInput mi)
{
if (orderGenerator == null) return;
var orders = orderGenerator.Order(world, xy.ToInt2(), mi).ToArray();
recentOrders.AddRange( orders );
Game.orderManager.IssueOrders( orders );
var voicedActor = orders.Select(o => o.Subject)
.FirstOrDefault(a => a.Owner == world.LocalPlayer && a.traits.Contains<Unit>());
@@ -65,16 +63,6 @@ namespace OpenRa
}
}
public void AddOrder(Order o) { recentOrders.Add(o); }
public List<Order> GetRecentOrders( bool imm )
{
Func<Order, bool> p = o => o.IsImmediate ^ !imm;
var result = recentOrders.Where(p).ToList();
recentOrders.RemoveAll(o => p(o)); // ffs.
return result;
}
float2 dragStart, dragEnd;
public bool HandleInput(World world, MouseInput mi)
{

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRa.GameRules;
using OpenRa.Traits;
@@ -51,5 +52,19 @@ namespace OpenRa
if (oai == null) return 0;
return oai.HP;
}
public static V GetOrAdd<K, V>( this Dictionary<K, V> d, K k )
where V : new()
{
return d.GetOrAdd( k, _ => new V() );
}
public static V GetOrAdd<K, V>( this Dictionary<K, V> d, K k, Func<K, V> createFn )
{
V ret;
if( !d.TryGetValue( k, out ret ) )
d.Add( k, ret = createFn( k ) );
return ret;
}
}
}

View File

@@ -2,14 +2,15 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using IjwFramework.Types;
using OpenRa.FileFormats;
using OpenRa.GameRules;
using OpenRa.Graphics;
using OpenRa.Network;
using OpenRa.Orders;
using OpenRa.Support;
using OpenRa.Traits;
using System.Windows.Forms;
using Timer = OpenRa.Support.Timer;
namespace OpenRa
@@ -101,13 +102,14 @@ namespace OpenRa
ChangeMap(mapName);
if (Settings.Replay != "")
orderManager = new OrderManager(new IOrderSource[] { new ReplayOrderSource(Settings.Replay) });
//orderManager = new OrderManager(new IOrderSource[] { new ReplayOrderSource(Settings.Replay) });
throw new NotImplementedException();
else
{
var orderSources = (string.IsNullOrEmpty(Settings.NetworkHost))
? new IOrderSource[] { new LocalOrderSource() }
: new IOrderSource[] { new LocalOrderSource(), new NetworkOrderSource(Settings.NetworkHost, Settings.NetworkPort) };
orderManager = new OrderManager(orderSources, "replay.rep");
var connection = (string.IsNullOrEmpty(Settings.NetworkHost))
? new EchoConnection()
: new NetworkConnection( Settings.NetworkHost, Settings.NetworkPort );
orderManager = new OrderManager(connection, "replay.rep");
}
}
@@ -271,7 +273,7 @@ namespace OpenRa
/* hack hack hack */
if( e.KeyCode == Keys.F8 && !Game.orderManager.GameStarted )
{
Game.controller.AddOrder(
Game.orderManager.IssueOrder(
new Order( "ToggleReady", Game.world.LocalPlayer.PlayerActor, null, int2.Zero, "" ) { IsImmediate = true } );
}

View File

@@ -3,6 +3,7 @@ using System.Linq;
using OpenRa.FileFormats;
using OpenRa.Orders;
using OpenRa.Traits;
using OpenRa.Network;
namespace OpenRa.Graphics
{
@@ -57,32 +58,25 @@ namespace OpenRa.Graphics
world.WorldRenderer.Draw();
Game.chrome.Draw( world );
if (Game.orderManager.IsNetplay &&
Game.orderManager.Sources.OfType<NetworkOrderSource>().First().State == ConnectionState.NotConnected)
if( Game.orderManager.Connection.ConnectionState == ConnectionState.NotConnected )
Game.chrome.DrawDialog("Connection lost.");
}
else
{
// what a hack. as soon as we have some real chrome stuff...
if (Game.orderManager.IsNetplay)
switch( Game.orderManager.Connection.ConnectionState )
{
var nos = Game.orderManager.Sources.OfType<NetworkOrderSource>().First();
switch (nos.State)
{
case ConnectionState.Connecting:
Game.chrome.DrawDialog("Connecting to {0}:{1}...".F( Game.Settings.NetworkHost, Game.Settings.NetworkPort ));
break;
case ConnectionState.NotConnected:
Game.chrome.DrawDialog("Connection failed.");
break;
case ConnectionState.Connected:
Game.chrome.DrawLobby( world );
break;
}
case ConnectionState.Connecting:
Game.chrome.DrawDialog("Connecting to {0}:{1}...".F( Game.Settings.NetworkHost, Game.Settings.NetworkPort ));
break;
case ConnectionState.NotConnected:
Game.chrome.DrawDialog("Connection failed.");
break;
case ConnectionState.Connected:
Game.chrome.DrawLobby( world );
break;
}
else
Game.chrome.DrawLobby( world );
}
var c = Game.chrome.HitTest(mousePos) ? Cursor.Default : Game.controller.ChooseCursor( world );

111
OpenRa.Game/Network/Connection.cs Executable file
View File

@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.IO;
namespace OpenRa.Network
{
enum ConnectionState
{
NotConnected,
Connecting,
Connected,
}
interface IConnection
{
int LocalClientId { get; }
ConnectionState ConnectionState { get; }
void Send( byte[] packet );
void Receive( Action<int, byte[]> packetFn );
}
class EchoConnection : IConnection
{
protected struct ReceivedPacket
{
public int FromClient;
public byte[] Data;
}
protected List<ReceivedPacket> receivedPackets = new List<ReceivedPacket>();
public virtual int LocalClientId
{
get { return 1; }
}
public virtual ConnectionState ConnectionState
{
get { return ConnectionState.Connected; }
}
public virtual void Send( byte[] packet )
{
lock( this )
receivedPackets.Add( new ReceivedPacket { FromClient = LocalClientId, Data = packet } );
}
public virtual void Receive( Action<int, byte[]> packetFn )
{
List<ReceivedPacket> packets;
lock( this )
{
packets = receivedPackets;
receivedPackets = new List<ReceivedPacket>();
}
foreach( var p in packets )
packetFn( p.FromClient, p.Data );
}
}
class NetworkConnection : EchoConnection
{
TcpClient socket;
int clientId;
ConnectionState connectionState = ConnectionState.Connecting;
public NetworkConnection( string host, int port )
{
new Thread( _ =>
{
try
{
socket = new TcpClient( host, port );
var reader = new BinaryReader( socket.GetStream() );
clientId = reader.ReadInt32();
connectionState = ConnectionState.Connected;
for( ; ; )
{
var len = reader.ReadInt32();
var buf = reader.ReadBytes( len );
lock( this )
receivedPackets.Add( new ReceivedPacket { FromClient = -1, Data = buf } );
}
}
catch
{
connectionState = ConnectionState.NotConnected;
}
}
) { IsBackground = true }.Start();
}
public override int LocalClientId { get { return clientId; } }
public override ConnectionState ConnectionState { get { return connectionState; } }
public override void Send( byte[] packet )
{
base.Send( packet );
var ms = new MemoryStream();
ms.Write( BitConverter.GetBytes( (int)packet.Length ) );
ms.Write( packet );
ms.WriteTo( socket.GetStream() );
}
}
}

View File

View File

@@ -1,30 +1,36 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRa.Orders
namespace OpenRa.Network
{
static class OrderIO
{
static void Write(this Stream s, byte[] buf)
public static void Write(this Stream s, byte[] buf)
{
s.Write(buf, 0, buf.Length);
}
public static void WriteFrameData(this Stream s, IEnumerable<Order> orders, int frameNumber)
{
var ms = new MemoryStream();
ms.Write(BitConverter.GetBytes(frameNumber));
foreach (var order in orders)
ms.Write(order.Serialize());
var bytes = Serialize( orders, frameNumber );
s.Write( BitConverter.GetBytes( (int)bytes.Length ) );
s.Write( bytes );
}
s.Write(BitConverter.GetBytes((int)ms.Length));
ms.WriteTo(s);
public static byte[] Serialize( this IEnumerable<Order> orders, int frameNumber )
{
var ms = new MemoryStream();
ms.Write( BitConverter.GetBytes( frameNumber ) );
foreach( var o in orders.Select( o => o.Serialize() ) )
ms.Write( o );
return ms.ToArray();
}
public static List<Order> ToOrderList(this byte[] bytes, World world)
{
var ms = new MemoryStream(bytes);
var ms = new MemoryStream(bytes, 4, bytes.Length - 4);
var reader = new BinaryReader(ms);
var ret = new List<Order>();
while( ms.Position < ms.Length )

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRa.Network
{
class OrderManager
{
Stream savingReplay;
int frameNumber = 0;
public int FramesAhead = 0;
public bool GameStarted { get { return frameNumber != 0; } }
public IConnection Connection { get; private set; }
Dictionary<int, Dictionary<int, byte[]>> frameClientData = new Dictionary<int, Dictionary<int, byte[]>>();
List<int> readyForFrames = new List<int>();
List<Order> localOrders = new List<Order>();
public void StartGame()
{
if (GameStarted) return;
frameNumber = 1;
for( int i = frameNumber ; i <= FramesAhead ; i++ )
Connection.Send( new List<Order>().Serialize( i ) );
}
public int FrameNumber { get { return frameNumber; } }
public OrderManager( IConnection conn )
{
Connection = conn;
}
public OrderManager( IConnection conn, string replayFilename )
: this( conn )
{
savingReplay = new FileStream( replayFilename, FileMode.Create );
}
public void IssueOrders( Order[] orders )
{
foreach( var order in orders )
IssueOrder( order );
}
public void IssueOrder( Order order )
{
localOrders.Add( order );
}
public void TickImmediate( World world )
{
var immediateOrders = localOrders.Where( o => o.IsImmediate ).ToList();
if( immediateOrders.Count != 0 )
Connection.Send( immediateOrders.Serialize( 0 ) );
localOrders.RemoveAll( o => o.IsImmediate );
var immediatePackets = new List<byte[]>();
Connection.Receive(
( clientId, packet ) =>
{
var frame = BitConverter.ToInt32( packet, 0 );
if( packet.Length == 5 && packet[ 4 ] == 0xEF )
readyForFrames.Add( frame );
else if( frame == 0 )
immediatePackets.Add( packet );
else
frameClientData.GetOrAdd( frame ).Add( clientId, packet );
} );
foreach( var p in immediatePackets )
foreach( var o in p.ToOrderList( world ) )
UnitOrders.ProcessOrder( o );
}
public bool IsReadyForNextFrame
{
get { return readyForFrames.Contains( FrameNumber ); }
}
public void Tick( World world )
{
if( !IsReadyForNextFrame )
throw new InvalidOperationException();
readyForFrames.RemoveAll( f => f <= FrameNumber );
Connection.Send( localOrders.Serialize( FrameNumber + FramesAhead ) );
localOrders.Clear();
var frameData = frameClientData[ FrameNumber ];
foreach( var order in frameData.OrderBy( p => p.Key ).SelectMany( o => o.Value.ToOrderList( world ) ) )
UnitOrders.ProcessOrder( order );
++frameNumber;
}
}
}

View File

@@ -4,7 +4,7 @@ using OpenRa.GameRules;
using OpenRa.Graphics;
using OpenRa.Traits;
namespace OpenRa.Orders
namespace OpenRa.Network
{
static class UnitOrders
{

View File

@@ -101,19 +101,17 @@
<Compile Include="Graphics\ChromeProvider.cs" />
<Compile Include="Graphics\MappedImage.cs" />
<Compile Include="Graphics\Minimap.cs" />
<Compile Include="Network\Connection.cs" />
<Compile Include="Resources1.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Orders\ChronosphereSelectOrderGenerator.cs" />
<Compile Include="Orders\IOrderSource.cs" />
<Compile Include="Orders\IronCurtainOrderGenerator.cs" />
<Compile Include="Orders\LocalOrderSource.cs" />
<Compile Include="Effects\Missile.cs" />
<Compile Include="Orders\NetworkOrderSource.cs" />
<Compile Include="Orders\OrderIO.cs" />
<Compile Include="Orders\OrderManager.cs" />
<Compile Include="Network\OrderIO.cs" />
<Compile Include="Network\OrderManager.cs" />
<Compile Include="Orders\PowerDownOrderGenerator.cs" />
<Compile Include="Orders\RepairOrderGenerator.cs" />
<Compile Include="Orders\SellOrderGenerator.cs" />
@@ -122,7 +120,6 @@
<Compile Include="PackageDownloader.cs" />
<Compile Include="PathSearch.cs" />
<Compile Include="ProductionItem.cs" />
<Compile Include="Orders\ReplayOrderSource.cs" />
<Compile Include="Shroud.cs" />
<Compile Include="Smudge.cs" />
<Compile Include="Sound.cs" />
@@ -182,7 +179,7 @@
<Compile Include="Graphics\Sheet.cs" />
<Compile Include="PathFinder.cs" />
<Compile Include="Graphics\Sequence.cs" />
<Compile Include="Orders\Order.cs" />
<Compile Include="Network\Order.cs" />
<Compile Include="Graphics\SequenceProvider.cs" />
<Compile Include="Graphics\SheetBuilder.cs" />
<Compile Include="Graphics\HardwarePalette.cs" />
@@ -271,7 +268,7 @@
<Compile Include="Traits\Unit.cs" />
<Compile Include="Traits\WaterPaletteRotation.cs" />
<Compile Include="Traits\WithShadow.cs" />
<Compile Include="Orders\UnitOrders.cs" />
<Compile Include="Network\UnitOrders.cs" />
<Compile Include="Traits\Util.cs" />
<Compile Include="UiOverlay.cs" />
<Compile Include="Graphics\Util.cs" />

View File

@@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace OpenRa.Orders
{
interface IOrderSource
{
void SendLocalOrders(int localFrame, List<Order> localOrders);
List<byte[]> OrdersForFrame(int currentFrame);
bool IsReadyForFrame(int frameNumber);
}
}

View File

@@ -1,31 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenRa.Orders
{
class LocalOrderSource : IOrderSource
{
Dictionary<int, List<byte[]>> orders = new Dictionary<int, List<byte[]>>();
public List<byte[]> OrdersForFrame(int currentFrame)
{
if (!orders.ContainsKey(currentFrame))
return new List<byte[]>();
var result = orders[currentFrame];
orders.Remove(currentFrame);
return result;
}
public void SendLocalOrders(int localFrame, List<Order> localOrders)
{
if (localFrame == 0) return;
orders[localFrame] = localOrders.Select(o=>o.Serialize()).ToList();
}
public bool IsReadyForFrame(int frameNumber)
{
return true;
}
}
}

View File

@@ -1,112 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System;
namespace OpenRa.Orders
{
enum ConnectionState
{
NotConnected,
Connecting,
Connected,
}
class NetworkOrderSource : IOrderSource
{
TcpClient socket;
Dictionary<int, List<byte[]>> orderBuffers = new Dictionary<int, List<byte[]>>();
Dictionary<int, bool> gotEverything = new Dictionary<int, bool>();
public ConnectionState State { get; private set; }
public NetworkOrderSource(string host, int port)
{
State = ConnectionState.Connecting;
socket = new TcpClient();
socket.BeginConnect(host, port, OnConnected, null);
socket.NoDelay = true;
}
void OnConnected(IAsyncResult r)
{
try
{
socket.EndConnect(r);
State = ConnectionState.Connected;
new Thread(() =>
{
var reader = new BinaryReader(socket.GetStream());
try
{
for (; ; )
{
var len = reader.ReadInt32();
var frame = reader.ReadInt32();
var buf = reader.ReadBytes(len - 4);
lock (orderBuffers)
{
if (len == 5 && buf[0] == 0xef) /* got everything marker */
gotEverything[frame] = true;
else
{
/* accumulate this chunk */
if (!orderBuffers.ContainsKey(frame))
orderBuffers[frame] = new List<byte[]> { buf };
else
orderBuffers[frame].Add(buf);
}
}
}
}
catch (IOException)
{
State = ConnectionState.NotConnected;
}
}) { IsBackground = true }.Start();
}
catch
{
State = ConnectionState.NotConnected;
}
}
static List<byte[]> NoOrders = new List<byte[]>();
List<byte[]> ExtractOrders(int frame)
{
lock (orderBuffers)
{
List<byte[]> result;
if (!orderBuffers.TryGetValue(frame, out result))
result = NoOrders;
orderBuffers.Remove(frame);
gotEverything.Remove(frame);
return result;
}
}
public List<byte[]> OrdersForFrame(int currentFrame)
{
return ExtractOrders(currentFrame).ToList();
}
public void SendLocalOrders(int localFrame, List<Order> localOrders)
{
if (socket.Connected)
socket.GetStream().WriteFrameData(localOrders, localFrame);
}
public bool IsReadyForFrame(int frameNumber)
{
lock (orderBuffers)
return gotEverything.ContainsKey(frameNumber);
}
}
}

View File

@@ -1,98 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenRa.Orders
{
class OrderManager
{
Stream savingReplay;
List<IOrderSource> sources;
int frameNumber = 0;
public int FramesAhead = 3;
public bool GameStarted { get { return frameNumber != 0; } }
public bool IsNetplay { get { return sources.OfType<NetworkOrderSource>().Any(); } }
public void StartGame()
{
if (GameStarted) return;
frameNumber = 1;
foreach (var p in this.sources)
for (int i = frameNumber; i <= FramesAhead; i++)
p.SendLocalOrders(i, new List<Order>());
}
public IEnumerable<IOrderSource> Sources { get { return sources; } }
public int FrameNumber { get { return frameNumber; } }
public OrderManager( IEnumerable<IOrderSource> sources )
{
this.sources = sources.ToList();
if (!IsNetplay)
StartGame();
}
public OrderManager( IEnumerable<IOrderSource> sources, string replayFilename )
: this( sources )
{
savingReplay = new FileStream( replayFilename, FileMode.Create );
}
public bool IsReadyForNextFrame
{
get
{
foreach( var p in sources )
if( !p.IsReadyForFrame( frameNumber ) )
return false;
return true;
}
}
void ProcessOrders(World world, int frame, bool save)
{
var orders = sources
.SelectMany(s => s.OrdersForFrame(frame))
.SelectMany(x => x.ToOrderList(world))
.OrderBy(o => o.Player.Index)
.ToList();
foreach (var o in orders)
UnitOrders.ProcessOrder(o);
if (save && savingReplay != null)
savingReplay.WriteFrameData(orders, frame);
}
public void TickImmediate( World world )
{
var localOrders = Game.controller.GetRecentOrders(true);
if (localOrders.Count > 0)
foreach (var p in sources)
p.SendLocalOrders(0, localOrders);
ProcessOrders(world, 0, false);
}
public void Tick( World world )
{
var localOrders = Game.controller.GetRecentOrders(false);
foreach( var p in sources )
p.SendLocalOrders( frameNumber + FramesAhead, localOrders );
ProcessOrders(world, frameNumber, true);
++frameNumber;
// sanity check on the framenumber. This is 2^31 frames maximum, or multiple *years* at 40ms/frame.
if( ( frameNumber & 0x80000000 ) != 0 )
throw new InvalidOperationException( "(OrderManager) Frame number too large" );
}
}
}

View File

@@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace OpenRa.Orders
{
class ReplayOrderSource : IOrderSource
{
BinaryReader replayReader;
public ReplayOrderSource(string replayFilename)
{
replayReader = new BinaryReader(File.Open(replayFilename, FileMode.Open));
}
public void SendLocalOrders(int localFrame, List<Order> localOrders) { }
public List<byte[]> OrdersForFrame(int frameNumber)
{
if (frameNumber == 0)
return new List<byte[]>();
try
{
var len = replayReader.ReadInt32() - 4;
var frame = replayReader.ReadInt32();
var ret = replayReader.ReadBytes(len);
if (frameNumber != frame)
throw new InvalidOperationException("Attempted time-travel in OrdersForFrame (replay)");
return new List<byte[]> { ret };
}
catch (EndOfStreamException)
{
return new List<byte[]>();
}
}
public bool IsReadyForFrame(int frameNumber)
{
return true;
}
}
}

View File

@@ -67,7 +67,7 @@ namespace OpenRa
Game.chat.AddLine(Color.White, "Debug", "Requesting package: {0}".F(currentPackage));
Game.controller.AddOrder(
Game.orderManager.IssueOrder(
new Order("RequestFile", Game.world.LocalPlayer.PlayerActor, null, int2.Zero, currentPackage) { IsImmediate = true });
Fraction = 0f;

View File

@@ -22,7 +22,7 @@ namespace OpenRa.SupportPowers
// Play chronosphere active anim
var chronosphere = target.World.Actors.Where(a => a.Owner == p.Owner && a.traits.Contains<Chronosphere>()).FirstOrDefault();
if (chronosphere != null)
Game.controller.AddOrder(Order.PlayAnimation(chronosphere, "active"));
Game.orderManager.IssueOrder(Order.PlayAnimation(chronosphere, "active"));
// Trigger screen desaturate effect
foreach (var a in target.World.Actors.Where(a => a.traits.Contains<ChronoshiftPaletteEffect>()))

View File

@@ -29,7 +29,7 @@ namespace OpenRa.SupportPowers
.Where(a => a.Owner == p.Owner && a.traits.Contains<IronCurtain>())
.FirstOrDefault();
if (ironCurtain != null)
Game.controller.AddOrder(Order.PlayAnimation(ironCurtain, "active"));
Game.orderManager.IssueOrder(Order.PlayAnimation(ironCurtain, "active"));
}
SupportPower p;