diff --git a/OpenRA.Server/Connection.cs b/OpenRA.Server/Connection.cs new file mode 100644 index 0000000000..fd547df34d --- /dev/null +++ b/OpenRA.Server/Connection.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.IO; + +namespace OpenRA.Server +{ + class Connection + { + public Socket socket; + public List data = new List(); + public ReceiveState State = ReceiveState.Header; + public int ExpectLength = 8; + public int Frame = 0; + + public byte[] PopBytes(int n) + { + var result = data.GetRange(0, n); + data.RemoveRange(0, n); + return result.ToArray(); + } + } + + enum ReceiveState { Header, Data }; +} diff --git a/OpenRA.Server/OpenRA.Server.csproj b/OpenRA.Server/OpenRA.Server.csproj index c66a6b2c0a..714160b8d0 100644 --- a/OpenRA.Server/OpenRA.Server.csproj +++ b/OpenRA.Server/OpenRA.Server.csproj @@ -48,6 +48,7 @@ + diff --git a/OpenRA.Server/Server.cs b/OpenRA.Server/Server.cs index 6abf3054e9..5db7614d00 100644 --- a/OpenRA.Server/Server.cs +++ b/OpenRA.Server/Server.cs @@ -6,70 +6,151 @@ using System.Net.Sockets; using System.Threading; using System.IO; using System.Net; +using System.Collections; namespace OpenRA.Server { static class Server { - static void Main(string[] args) - { - var listener = new TcpListener(IPAddress.Any, 1234); - var clients = new List(); + static List conns = new List(); + static TcpListener listener = new TcpListener(IPAddress.Any, 1234); + public static void Main(string[] args) + { listener.Start(); + Console.WriteLine("Server started."); + + for (; ; ) + { + var checkRead = new ArrayList(); + var checkWrite = new ArrayList(); + var checkError = new ArrayList(); + checkRead.Add(listener.Server); + foreach (var c in conns) + checkRead.Add(c.socket); + + Socket.Select(checkRead, null, null, 1000000 /* 1s */); + + Console.WriteLine("Select() completed with {0} sockets", + checkRead.Count); + + foreach (Socket s in checkRead) + if (s == listener.Server) AcceptConnection(); + else ReadData(conns.Single(c => c.socket == s)); + } + } + + static void AcceptConnection() + { + var newConn = new Connection { socket = listener.AcceptSocket() }; + newConn.socket.Blocking = false; + conns.Add(newConn); + + /* todo: assign a player number, setup host behavior, etc */ + + Console.WriteLine("Accepted connection from {0}.", + newConn.socket.RemoteEndPoint); + } + + static bool ReadDataInner(Connection conn) + { + var rx = new byte[1024]; + var len = 0; + for (; ; ) { try { - var conn = listener.AcceptTcpClient(); - conn.NoDelay = true; - Console.WriteLine("Accepted connection from {0}", - conn.Client.RemoteEndPoint.ToString()); - - new Thread(() => + if (0 < (len = conn.socket.Receive(rx))) { - lock (clients) - clients.Add(conn); - - var ns = conn.GetStream(); - - try - { - for (; ; ) - { - var frame = BitConverter.ToInt32(ns.Read(4), 0); - var length = BitConverter.ToInt32(ns.Read(4), 0); - var data = ns.Read(length); - - lock (clients) - foreach (var c in clients) - if (c != conn) - { - var otherStream = c.GetStream(); - otherStream.Write(BitConverter.GetBytes(frame)); - otherStream.Write(BitConverter.GetBytes(length)); - otherStream.Write(data); - } - } - } - catch (Exception e) - { - Console.WriteLine("Client dropped: {0}", conn.Client.RemoteEndPoint.ToString()); - - lock (clients) - clients.Remove(conn); - } - }) { IsBackground = true }.Start(); + Console.WriteLine("Read {0} bytes", len); + conn.data.AddRange(rx.Take(len)); + } + else + break; } - catch (Exception e) + catch (SocketException e) { - Console.WriteLine(e.ToString()); + if (e.SocketErrorCode == SocketError.WouldBlock) break; + DropClient(conn, e); + return false; } } + + return true; + } + + static void ReadData(Connection conn) + { + Console.WriteLine("Start ReadData() for {0}", + conn.socket.RemoteEndPoint); + + if (ReadDataInner(conn)) + while (conn.data.Count >= conn.ExpectLength) + { + var bytes = conn.PopBytes(conn.ExpectLength); + switch (conn.State) + { + case ReceiveState.Header: + { + conn.Frame = BitConverter.ToInt32(bytes, 0); + conn.ExpectLength = BitConverter.ToInt32(bytes, 4); + conn.State = ReceiveState.Data; + } break; + + case ReceiveState.Data: + { + DispatchOrders(conn, conn.Frame, bytes); + conn.ExpectLength = 8; + conn.State = ReceiveState.Header; + } break; + } + } + + Console.WriteLine("End ReadData() for {0}", + conn.socket.RemoteEndPoint); + } + + static void DispatchOrders(Connection conn, int frame, byte[] data) + { + foreach (var c in conns.Except(conn).ToArray()) + { + try + { + c.socket.Blocking = true; + c.socket.Send(BitConverter.GetBytes(frame)); + c.socket.Send(BitConverter.GetBytes(data.Length)); + c.socket.Send(data); + c.socket.Blocking = false; + } + catch (Exception e) { DropClient(c, e); } + } + + if (frame == 0) + InterpretServerOrders(data); + } + + static void InterpretServerOrders(byte[] data) + { + /* todo: handle all server orders! */ + } + + static void DropClient(Connection c, Exception e) + { + Console.WriteLine("Client dropped: {0}.", c.socket.RemoteEndPoint); + Console.WriteLine(e.ToString()); + + conns.Remove(c); + + /* todo: tell everyone else that `c` has dropped */ } public static void Write(this Stream s, byte[] data) { s.Write(data, 0, data.Length); } public static byte[] Read(this Stream s, int len) { var data = new byte[len]; s.Read(data, 0, len); return data; } + public static IEnumerable Except(this IEnumerable ts, T t) + { + return ts.Except(new[] { t }); + } } } diff --git a/OpenRa.Game/GameRules/UnitInfo.cs b/OpenRa.Game/GameRules/UnitInfo.cs index 573c7adc52..2d1ef73114 100755 --- a/OpenRa.Game/GameRules/UnitInfo.cs +++ b/OpenRa.Game/GameRules/UnitInfo.cs @@ -103,6 +103,7 @@ namespace OpenRa.Game.GameRules public readonly int Storage = 0; public readonly bool Unsellable = false; public readonly int[] RallyPoint = { 1, 3 }; + public readonly float[] SpawnOffset = null; public BuildingInfo(string name) : base(name) { } } diff --git a/OpenRa.Game/OrderManager.cs b/OpenRa.Game/OrderManager.cs index 016400e844..c88512f565 100755 --- a/OpenRa.Game/OrderManager.cs +++ b/OpenRa.Game/OrderManager.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; -using System.Net; -using IjwFramework.Types; +using System.Linq; using System.Net.Sockets; using System.Threading; diff --git a/OpenRa.Game/Traits/Production.cs b/OpenRa.Game/Traits/Production.cs index 5397377ffe..fb777dfab4 100755 --- a/OpenRa.Game/Traits/Production.cs +++ b/OpenRa.Game/Traits/Production.cs @@ -34,13 +34,18 @@ namespace OpenRa.Game.Traits { var mobile = newUnit.traits.GetOrDefault(); if( mobile != null ) - newUnit.QueueActivity( new Traits.Activities.Move( rp.rallyPoint, 1 ) ); + newUnit.QueueActivity( new Activities.Move( rp.rallyPoint, 1 ) ); var heli = newUnit.traits.GetOrDefault(); - if( heli != null ) + if (heli != null) heli.targetLocation = rp.rallyPoint; // TODO: make Activity.Move work for helis. } + var bi = self.unitInfo as BuildingInfo; + if (bi != null && bi.SpawnOffset != null) + newUnit.CenterLocation = self.CenterLocation + + new float2(bi.SpawnOffset[0], bi.SpawnOffset[1]); + Game.world.Add( newUnit ); if( self.traits.Contains() ) diff --git a/units.ini b/units.ini index bbd8e3bdf5..e7612499e7 100755 --- a/units.ini +++ b/units.ini @@ -307,6 +307,7 @@ Dimensions=2,2 Footprint=xx xx Produces=Plane SelectionPriority=3 +SpawnOffset=0,0 ; todo: push this up a bit, but we've got a z-order issue first. [DOME] Description=Radar Dome Traits=Building, RenderBuilding