diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index fe8cc8c12f..bf9ad08ea2 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -21,20 +21,21 @@ namespace OpenRA.Network public List Slots = new List(); public Global GlobalSettings = new Global(); - public Client ClientWithIndex( int clientID ) + public Client ClientWithIndex(int clientID) { - return Clients.SingleOrDefault( c => c.Index == clientID ); + return Clients.SingleOrDefault(c => c.Index == clientID); } - public Client ClientInSlot( Slot slot ) + public Client ClientInSlot(Slot slot) { - return Clients.SingleOrDefault( c => c.Slot == slot.Index ); + return Clients.SingleOrDefault(c => c.Slot == slot.Index); } public enum ClientState { NotReady, - Ready + Ready, + Disconnected = 1000 } public class Client @@ -55,7 +56,7 @@ namespace OpenRA.Network public int Index; public string Bot; // trait name of the bot to initialize in this slot, or null otherwise. public bool Closed; // host has explicitly closed this slot. - public string MapPlayer; // playerReference to bind against. + public string MapPlayer; // playerReference to bind against. public bool Spectator = false; // Spectating or not // todo: more stuff? } @@ -71,7 +72,7 @@ namespace OpenRA.Network public bool AllowCheats = false; } - public Session( string[] mods ) + public Session(string[] mods) { this.GlobalSettings.Mods = mods.ToArray(); } @@ -80,27 +81,27 @@ namespace OpenRA.Network { var clientData = new List(); - foreach( var client in Clients ) - clientData.Add( new MiniYamlNode( "Client@{0}".F( client.Index ), FieldSaver.Save( client ) ) ); + foreach (var client in Clients) + clientData.Add(new MiniYamlNode("Client@{0}".F(client.Index), FieldSaver.Save(client))); - foreach( var slot in Slots ) - clientData.Add( new MiniYamlNode( "Slot@{0}".F( slot.Index ), FieldSaver.Save( slot ) ) ); + foreach (var slot in Slots) + clientData.Add(new MiniYamlNode("Slot@{0}".F(slot.Index), FieldSaver.Save(slot))); - clientData.Add( new MiniYamlNode( "GlobalSettings", FieldSaver.Save( GlobalSettings ) ) ); + clientData.Add(new MiniYamlNode("GlobalSettings", FieldSaver.Save(GlobalSettings))); return clientData.WriteToString(); } public static Session Deserialize(string data) { - var session = new Session( Game.Settings.Game.Mods ); + var session = new Session(Game.Settings.Game.Mods); var ys = MiniYaml.FromString(data); foreach (var y in ys) { var yy = y.Key.Split('@'); - switch( yy[0] ) + switch (yy[0]) { case "GlobalSettings": FieldLoader.Load(session.GlobalSettings, y.Value); @@ -111,7 +112,7 @@ namespace OpenRA.Network break; case "Slot": - session.Slots.Add(FieldLoader.Load(y.Value )); + session.Slots.Add(FieldLoader.Load(y.Value)); break; } } diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index ae2733cd2b..5a9e603ff1 100755 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -16,7 +16,7 @@ namespace OpenRA.Network { static class UnitOrders { - static Player FindPlayerByClient( this World world, Session.Client c) + static Player FindPlayerByClient(this World world, Session.Client c) { /* todo: this is still a hack. * the cases we're trying to avoid are the extra players on the host's client -- Neutral, other MapPlayers, @@ -25,7 +25,7 @@ namespace OpenRA.Network p => p.ClientIndex == c.Index && p.PlayerName == c.Name); } - public static void ProcessOrder( OrderManager orderManager, World world, int clientId, Order order ) + public static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order) { if (world != null) { @@ -34,103 +34,118 @@ namespace OpenRA.Network return; } - switch( order.OrderString ) + switch (order.OrderString) { - case "Chat": - { - var client = orderManager.LobbyInfo.ClientWithIndex( clientId ); - - if (Game.IsHost && Game.Settings.Server.Extension != null && !Game.Settings.Server.Extension.OnIngameChat(client, order.TargetString, false)) - break; - - if (client != null) + case "Chat": { - var player = world != null ? world.FindPlayerByClient(client) : null; - var suffix = (player != null && player.WinState == WinState.Lost) ? " (Dead)" : ""; - Game.AddChatLine(client.Color1, client.Name+suffix, order.TargetString); - } - else - Game.AddChatLine(Color.White, "(player {0})".F(clientId), order.TargetString); - break; - } - case "TeamChat": - { - var client = orderManager.LobbyInfo.ClientWithIndex(clientId); + var client = orderManager.LobbyInfo.ClientWithIndex(clientId); - if (Game.IsHost && Game.Settings.Server.Extension != null && !Game.Settings.Server.Extension.OnIngameChat(client, order.TargetString, true)) - break; + if (Game.IsHost && Game.Settings.Server.Extension != null && !Game.Settings.Server.Extension.OnIngameChat(client, order.TargetString, false)) + break; - if (client != null) - { - if (world == null) + + if (client != null) { - if (client.Team == orderManager.LocalClient.Team) - Game.AddChatLine(client.Color1, client.Name + " (Team)", - order.TargetString); + var player = world != null ? world.FindPlayerByClient(client) : null; + var suffix = (player != null && player.WinState == WinState.Lost) ? " (Dead)" : ""; + Game.AddChatLine(client.Color1, client.Name + suffix, order.TargetString); } else - { - var player = world.FindPlayerByClient(client); - var display = player != null - && (world.LocalPlayer != null && player.Stances[world.LocalPlayer] == Stance.Ally - || player.WinState == WinState.Lost); + Game.AddChatLine(Color.White, "(player {0})".F(clientId), order.TargetString); + break; + } - if (display) + case "Disconnected": /* reports that the target player disconnected */ + { + var client = orderManager.LobbyInfo.ClientWithIndex(clientId); + if (client != null) + { + client.State = Session.ClientState.Disconnected; + } + break; + } + case "TeamChat": + { + var client = orderManager.LobbyInfo.ClientWithIndex(clientId); + + if (Game.IsHost && Game.Settings.Server.Extension != null && !Game.Settings.Server.Extension.OnIngameChat(client, order.TargetString, true)) + break; + + if (client != null) + { + if (world == null) { - var suffix = (player != null && player.WinState == WinState.Lost) ? " (Dead)" : " (Team)"; - Game.AddChatLine(client.Color1, client.Name + suffix, order.TargetString); + if (client.Team == orderManager.LocalClient.Team) + Game.AddChatLine(client.Color1, client.Name + " (Team)", + order.TargetString); + } + else + { + var player = world.FindPlayerByClient(client); + var display = player != null + && + (world.LocalPlayer != null && + player.Stances[world.LocalPlayer] == Stance.Ally + || player.WinState == WinState.Lost); + + if (display) + { + var suffix = (player != null && player.WinState == WinState.Lost) + ? " (Dead)" + : " (Team)"; + Game.AddChatLine(client.Color1, client.Name + suffix, order.TargetString); + } } } + break; } - break; - } - case "StartGame": - { - Game.AddChatLine(Color.White, "Server", "The game has started."); - Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map); - break; - } - case "SyncInfo": - { - orderManager.LobbyInfo = Session.Deserialize( order.TargetString ); - - if( orderManager.FramesAhead != orderManager.LobbyInfo.GlobalSettings.OrderLatency - && !orderManager.GameStarted ) + case "StartGame": { - orderManager.FramesAhead = orderManager.LobbyInfo.GlobalSettings.OrderLatency; - Game.Debug( "Order lag is now {0} frames.".F( orderManager.LobbyInfo.GlobalSettings.OrderLatency ) ); + Game.AddChatLine(Color.White, "Server", "The game has started."); + Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map); + break; + } + case "SyncInfo": + { + orderManager.LobbyInfo = Session.Deserialize(order.TargetString); + + if (orderManager.FramesAhead != orderManager.LobbyInfo.GlobalSettings.OrderLatency + && !orderManager.GameStarted) + { + orderManager.FramesAhead = orderManager.LobbyInfo.GlobalSettings.OrderLatency; + Game.Debug( + "Order lag is now {0} frames.".F(orderManager.LobbyInfo.GlobalSettings.OrderLatency)); + } + Game.SyncLobbyInfo(); + break; } - Game.SyncLobbyInfo(); - break; - } - case "SetStance": - { - - var targetPlayer = order.Player.World.players[order.TargetLocation.X]; - var newStance = (Stance)order.TargetLocation.Y; + case "SetStance": + { + var targetPlayer = order.Player.World.players[order.TargetLocation.X]; + var newStance = (Stance)order.TargetLocation.Y; - if (Game.IsHost && Game.Settings.Server.Extension != null) - Game.Settings.Server.Extension.OnIngameSetStance(order.Player, targetPlayer, newStance); - + if (Game.IsHost && Game.Settings.Server.Extension != null) + Game.Settings.Server.Extension.OnIngameSetStance(order.Player, targetPlayer, newStance); - SetPlayerStance(world, order.Player, targetPlayer, newStance); - // automatically declare war reciprocally - if (newStance == Stance.Enemy) - SetPlayerStance(world, targetPlayer, order.Player, newStance); + SetPlayerStance(world, order.Player, targetPlayer, newStance); - Game.Debug("{0} has set diplomatic stance vs {1} to {2}".F( - order.Player.PlayerName, targetPlayer.PlayerName, newStance)); - break; - } - default: - { - if( !order.IsImmediate ) - foreach (var t in order.Subject.TraitsImplementing()) - t.ResolveOrder(order.Subject, order); - break; - } + // automatically declare war reciprocally + if (newStance == Stance.Enemy) + SetPlayerStance(world, targetPlayer, order.Player, newStance); + + Game.Debug("{0} has set diplomatic stance vs {1} to {2}".F( + order.Player.PlayerName, targetPlayer.PlayerName, newStance)); + break; + } + default: + { + if (!order.IsImmediate) + foreach (var t in order.Subject.TraitsImplementing()) + t.ResolveOrder(order.Subject, order); + break; + } } } diff --git a/OpenRA.Game/Player.cs b/OpenRA.Game/Player.cs index da4e41254d..4803dbe974 100644 --- a/OpenRA.Game/Player.cs +++ b/OpenRA.Game/Player.cs @@ -13,85 +13,86 @@ using System.Drawing; using System.Linq; using OpenRA.FileFormats; using OpenRA.Network; -using OpenRA.Traits; - -namespace OpenRA -{ - public enum PowerState { Normal, Low, Critical }; - public enum WinState { Won, Lost, Undefined }; - - public class Player - { - public Actor PlayerActor; - public int Kills; +using OpenRA.Traits; + +namespace OpenRA +{ + public enum PowerState { Normal, Low, Critical }; + public enum WinState { Won, Lost, Undefined }; + + public class Player + { + public Actor PlayerActor; + public int Kills; public int Deaths; - public WinState WinState = WinState.Undefined; - - public readonly string Palette; - public readonly Color Color; - public readonly Color Color2; - - public readonly string PlayerName; - public readonly string InternalName; - public readonly CountryInfo Country; - public readonly int Index; + public WinState WinState = WinState.Undefined; + + public readonly string Palette; + public readonly Color Color; + public readonly Color Color2; + + public readonly string PlayerName; + public readonly string InternalName; + public readonly CountryInfo Country; + public readonly int Index; public readonly bool NonCombatant = false; public readonly int ClientIndex; public readonly PlayerReference PlayerRef; - public bool IsBot; - - public ShroudRenderer Shroud; - public World World { get; private set; } - - public Player( World world, PlayerReference pr, int index ) - { - World = world; - Shroud = new ShroudRenderer(this, world.Map); - - Index = index; - Palette = "player"+index; - - Color = pr.Color; + public bool IsBot; + + public ShroudRenderer Shroud; + public World World { get; private set; } + + public Player(World world, PlayerReference pr, int index) + { + World = world; + Shroud = new ShroudRenderer(this, world.Map); + + Index = index; + Palette = "player" + index; + + Color = pr.Color; Color2 = pr.Color2; - ClientIndex = 0; /* it's a map player, "owned" by host */ - - PlayerName = InternalName = pr.Name; - NonCombatant = pr.NonCombatant; - Country = world.GetCountries() + ClientIndex = 0; /* it's a map player, "owned" by host */ + + PlayerName = InternalName = pr.Name; + NonCombatant = pr.NonCombatant; + Country = world.GetCountries() .FirstOrDefault(c => pr.Race == c.Race) ?? world.GetCountries().Random(world.SharedRandom); - PlayerRef = pr; - - PlayerActor = world.CreateActor("Player", new TypeDictionary{ new OwnerInit( this ) }); - } - - public Player( World world, Session.Client client, PlayerReference pr, int index ) - { - World = world; - Shroud = new ShroudRenderer(this, world.Map); - + PlayerRef = pr; + + PlayerActor = world.CreateActor("Player", new TypeDictionary { new OwnerInit(this) }); + } + + public Player(World world, Session.Client client, PlayerReference pr, int index) + { + World = world; + Shroud = new ShroudRenderer(this, world.Map); + Index = index; - Palette = "player"+index; - Color = client.Color1; - Color2 = client.Color2; + Palette = "player" + index; + Color = client.Color1; + Color2 = client.Color2; PlayerName = client.Name; - InternalName = pr.Name; - Country = world.GetCountries() - .FirstOrDefault(c => client != null && client.Country == c.Race ) + + InternalName = pr.Name; + Country = world.GetCountries() + .FirstOrDefault(c => client != null && client.Country == c.Race) ?? world.GetCountries().Random(world.SharedRandom); ClientIndex = client.Index; - PlayerRef = pr; - - PlayerActor = world.CreateActor("Player", new TypeDictionary{ new OwnerInit( this ) }); - } - - public void GiveAdvice(string advice) - { - Sound.PlayToPlayer(this, advice); - } - - public Dictionary Stances = new Dictionary(); - } + PlayerRef = pr; + + PlayerActor = world.CreateActor("Player", new TypeDictionary { new OwnerInit(this) }); + } + + public void GiveAdvice(string advice) + { + Sound.PlayToPlayer(this, advice); + } + + public Dictionary Stances = new Dictionary(); + } } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 9fc6097f08..c8ff05bd38 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -656,35 +656,41 @@ namespace OpenRA.Server new ServerOrder("Chat", text).Serialize()); } - static void SendChat(Connection asConn, string text) - { - DispatchOrders(asConn, 0, new ServerOrder("Chat", text).Serialize()); - } + static void SendChat(Connection asConn, string text) + { + DispatchOrders(asConn, 0, new ServerOrder("Chat", text).Serialize()); + } + + static void SendDisconnected(Connection asConn) + { + DispatchOrders(asConn, 0, new ServerOrder("Disconnected", "").Serialize()); + } static void InterpretServerOrder(Connection conn, ServerOrder so) { switch (so.Name) { case "Command": - { - if(GameStarted) - SendChatTo(conn, "Cannot change state when game started."); - else if (GetClient(conn).State == Session.ClientState.Ready && !(so.Data == "ready" || so.Data == "startgame") ) - SendChatTo(conn, "Cannot change state when marked as ready."); - else if (!InterpretCommand(conn, so.Data)) { - Log.Write("server", "Bad server command: {0}", so.Data); - SendChatTo(conn, "Bad server command."); - }; - } - break; + if (GameStarted) + SendChatTo(conn, "Cannot change state when game started."); + else if (GetClient(conn).State == Session.ClientState.Ready && !(so.Data == "ready" || so.Data == "startgame")) + SendChatTo(conn, "Cannot change state when marked as ready."); + else if (!InterpretCommand(conn, so.Data)) + { + Log.Write("server", "Bad server command: {0}", so.Data); + SendChatTo(conn, "Bad server command."); + }; + } + break; - case "Chat": + case "Chat": case "TeamChat": - if (E(e => e.OnChat(conn, so.Data, so.Name == "TeamChat"))) - foreach (var c in conns.Except(conn).ToArray()) - DispatchOrdersToClient(c, GetClient(conn).Index, 0, so.Serialize()); - break; + case "Disconnected": + if (E(e => e.OnChat(conn, so.Data, so.Name == "TeamChat"))) + foreach (var c in conns.Except(conn).ToArray()) + DispatchOrdersToClient(c, GetClient(conn).Index, 0, so.Serialize()); + break; } } @@ -698,6 +704,9 @@ namespace OpenRA.Server conns.Remove(toDrop); SendChat(toDrop, "Connection Dropped"); + if (GameStarted) + SendDisconnected(toDrop); /* Report disconnection */ + lobbyInfo.Clients.RemoveAll(c => c.Index == toDrop.PlayerIndex); DispatchOrders( toDrop, toDrop.MostRecentFrame, new byte[] { 0xbf } ); diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index f97ff9ef97..02ac637ec5 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -84,6 +84,7 @@ + diff --git a/OpenRA.Mods.RA/Player/SurrenderOnDisconnect.cs b/OpenRA.Mods.RA/Player/SurrenderOnDisconnect.cs new file mode 100644 index 0000000000..6ef3ca5738 --- /dev/null +++ b/OpenRA.Mods.RA/Player/SurrenderOnDisconnect.cs @@ -0,0 +1,44 @@ +#region Copyright & License Information +/* + * Copyright 2007-2010 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see LICENSE. + */ +#endregion + +using OpenRA.Network; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + class SurrenderOnDisconnectInfo : TraitInfo + { + + } + + class SurrenderOnDisconnect : ITick + { + private bool Disconnected = false; + + public void Tick(Actor self) + { + if (Disconnected) return; + + var p = self.Owner; + + if (p.WinState == WinState.Lost || p.WinState == WinState.Won) return; /* already won or lost */ + + var client = p.World.LobbyInfo.ClientWithIndex(p.ClientIndex); + if (client == null) + return; + + if (client.State == Session.ClientState.Disconnected) + { + Disconnected = true; /* dont call this multiple times! */ + self.World.IssueOrder(new Order("Surrender", self)); + } + } + } +} diff --git a/mods/cnc/rules/system.yaml b/mods/cnc/rules/system.yaml index 9428f8fdd4..13dabe08ea 100644 --- a/mods/cnc/rules/system.yaml +++ b/mods/cnc/rules/system.yaml @@ -68,7 +68,7 @@ Player: PlayerColorPalette: BasePalette: terrain PaletteFormat: cnc - + SurrenderOnDisconnect: World: OpenWidgetAtGameStart: Widget: INGAME_ROOT diff --git a/mods/ra/rules/system.yaml b/mods/ra/rules/system.yaml index 9fbe888b03..20b98e181a 100644 --- a/mods/ra/rules/system.yaml +++ b/mods/ra/rules/system.yaml @@ -124,6 +124,7 @@ Player: 3tnk: 0% PlayerColorPalette: BasePalette: terrain + SurrenderOnDisconnect: World: OpenWidgetAtGameStart: