From 4f172d7ed85b9db83dade835a9809e38b1b455a6 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 19 Jun 2011 02:39:34 +1200 Subject: [PATCH] Make bots first-class players. - Bots have their own Clients, with unique ClientIDs - Hosts can set bot team/color in the lobby - Bots are kicked when switching to a smaller map without enough slots - Order validator assumes that only client 0 has permission to issue bot orders --- OpenRA.Game/Network/Session.cs | 5 +- OpenRA.Game/Network/UnitOrders.cs | 5 +- OpenRA.Game/Player.cs | 23 +--- OpenRA.Game/Server/Server.cs | 5 +- OpenRA.Game/Traits/ValidateOrder.cs | 14 ++- .../Widgets/Logic/CncLobbyLogic.cs | 105 +++++++++--------- .../Widgets/Logic/CncReplayBrowserLogic.cs | 3 +- OpenRA.Mods.RA/CreateMPPlayers.cs | 4 +- OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs | 70 +++++++++--- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 45 ++++---- mods/cnc/chrome/lobby.yaml | 40 ++----- mods/ra/chrome/gamelobby.yaml | 18 --- 12 files changed, 159 insertions(+), 178 deletions(-) diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index e3ed8f6137..45e25626c2 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -34,8 +34,7 @@ namespace OpenRA.Network public string FirstEmptySlot() { - return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null - && s.Value.Bot == null).Key; + return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null).Key; } public enum ClientState @@ -55,12 +54,12 @@ namespace OpenRA.Network public ClientState State; public int Team; public string Slot; // slot ID, or null for observer + public string Bot; // Bot type, null for real clients } public class Slot { public string PlayerReference; // playerReference to bind against. - 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 bool AllowBots; diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index cdba271652..31ce9c6819 100755 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -21,10 +21,9 @@ namespace OpenRA.Network 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, - * bots,.. */ + * the cases we're trying to avoid are the extra players on the host's client -- Neutral, other MapPlayers,..*/ return world.Players.FirstOrDefault( - p => (p.ClientIndex == c.Index && p.PlayerRef.Playable && !p.IsBot)); + p => (p.ClientIndex == c.Index && p.PlayerRef.Playable)); } public static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order) diff --git a/OpenRA.Game/Player.cs b/OpenRA.Game/Player.cs index 9b312b8df8..4a9d64d614 100644 --- a/OpenRA.Game/Player.cs +++ b/OpenRA.Game/Player.cs @@ -46,11 +46,13 @@ namespace OpenRA PlayerRef = pr; string botType = null; + // Real player or host-created bot if (client != null) { ClientIndex = client.Index; ColorRamp = client.ColorRamp; PlayerName = client.Name; + botType = client.Bot; Country = world.GetCountries() .FirstOrDefault(c => client.Country == c.Race) @@ -58,36 +60,21 @@ namespace OpenRA } else { - // Map player or bot + // Map player ClientIndex = 0; /* it's a map player, "owned" by host */ ColorRamp = pr.ColorRamp; PlayerName = pr.Name; NonCombatant = pr.NonCombatant; - IsBot = pr.Bot != null; botType = pr.Bot; Country = world.GetCountries() .FirstOrDefault(c => pr.Race == c.Race) ?? world.GetCountries().Random(world.SharedRandom); - - // Multiplayer bot - if (slot != null && slot.Bot != null) - { - IsBot = true; - botType = slot.Bot; - PlayerName = slot.Bot; - - // pick a random color for the bot - var hue = (byte)world.SharedRandom.Next(255); - var sat = (byte)world.SharedRandom.Next(255); - var lum = (byte)world.SharedRandom.Next(51,255); - ColorRamp = new ColorRamp(hue, sat, lum, 10); - } } - PlayerActor = world.CreateActor("Player", new TypeDictionary { new OwnerInit(this) }); - // Enable the bot logic + // Enable the bot logic on the host + IsBot = botType != null; if (IsBot && Game.IsHost) { var logic = PlayerActor.TraitsImplementing() diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index e035f7da11..d78ceafef4 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -129,10 +129,11 @@ namespace OpenRA.Server * for manual spawnpoint choosing. * - 256 max players is a dirty hack */ - int ChooseFreePlayerIndex() + public int ChooseFreePlayerIndex() { for (var i = 0; i < 256; i++) - if (conns.All(c => c.PlayerIndex != i) && preConns.All(c => c.PlayerIndex != i)) + if (conns.All(c => c.PlayerIndex != i) && preConns.All(c => c.PlayerIndex != i) + && lobbyInfo.Clients.All(c => c.Index != i)) return i; throw new InvalidOperationException("Already got 256 players"); diff --git a/OpenRA.Game/Traits/ValidateOrder.cs b/OpenRA.Game/Traits/ValidateOrder.cs index 02c3297b33..d16d40cd1d 100644 --- a/OpenRA.Game/Traits/ValidateOrder.cs +++ b/OpenRA.Game/Traits/ValidateOrder.cs @@ -18,10 +18,18 @@ namespace OpenRA.Traits { public bool OrderValidation(OrderManager orderManager, World world, int clientId, Order order) { - // Drop exploiting orders - if (order.Subject != null && order.Subject.Owner.ClientIndex != clientId) + if (order.Subject == null || order.Subject.Owner == null) + return true; + + var subjectClient = order.Subject.Owner.ClientIndex; + + // Hack: Assumes bots always run on clientId 0. + var isBotOrder = orderManager.LobbyInfo.Clients[subjectClient].Bot != null && clientId == 0; + + // Drop exploiting orders + if (subjectClient != clientId && !isBotOrder) { - Game.Debug("Detected exploit order from {0}: {1}".F(clientId, order.OrderString)); + Game.Debug("Detected exploit order from client {0}: {1}".F(clientId, order.OrderString)); return false; } diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncLobbyLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncLobbyLogic.cs index 0cbae2fce1..d3d40fee17 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncLobbyLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncLobbyLogic.cs @@ -22,8 +22,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic { public class CncLobbyLogic { - Widget LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, BotTemplate, - LocalSpectatorTemplate, RemoteSpectatorTemplate, NewSpectatorTemplate; + Widget LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, + LocalSpectatorTemplate, RemoteSpectatorTemplate, NewSpectatorTemplate; ScrollPanelWidget chatPanel; Widget chatTemplate; @@ -108,7 +108,6 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic LocalPlayerTemplate = Players.GetWidget("TEMPLATE_LOCAL"); RemotePlayerTemplate = Players.GetWidget("TEMPLATE_REMOTE"); EmptySlotTemplate = Players.GetWidget("TEMPLATE_EMPTY"); - BotTemplate = Players.GetWidget("TEMPLATE_BOT"); LocalSpectatorTemplate = Players.GetWidget("TEMPLATE_LOCAL_SPECTATOR"); RemoteSpectatorTemplate = Players.GetWidget("TEMPLATE_REMOTE_SPECTATOR"); NewSpectatorTemplate = Players.GetWidget("TEMPLATE_NEW_SPECTATOR"); @@ -150,7 +149,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic return sc; }; - CountryNames = Rules.Info["world"].Traits.WithInterface().ToDictionary(a => a.Race, a => a.Name); + CountryNames = Rules.Info["world"].Traits.WithInterface() + .ToDictionary(a => a.Race, a => a.Name); CountryNames.Add("random", "Random"); var mapButton = lobby.GetWidget("CHANGEMAP_BUTTON"); @@ -296,25 +296,27 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic } } - bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot) + bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot, Session.Client client) { var options = new List() { - new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && slot.Bot == null)), + new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && client == null)), new SlotDropDownOption("Closed", "slot_close "+slot.PlayerReference, () => slot.Closed) }; - + if (slot.AllowBots) foreach (var b in Rules.Info["player"].Traits.WithInterface().Select(t => t.Name)) { var bot = b; - options.Add(new SlotDropDownOption("Bot: {0}".F(bot), "slot_bot {0} {1}".F(slot.PlayerReference, bot), () => slot.Bot == bot)); + options.Add(new SlotDropDownOption("Bot: {0}".F(bot), + "slot_bot {0} {1}".F(slot.PlayerReference, bot), + () => client != null && client.Bot == bot)); } - + Func setupItem = (o, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - o.Selected, + o.Selected, () => orderManager.IssueOrder(Order.Command(o.Order))); item.GetWidget("LABEL").GetText = () => o.Title; return item; @@ -329,7 +331,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic Func setupItem = (race, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - () => client.Country == race, + () => client.Country == race, () => orderManager.IssueOrder(Order.Command("race {0} {1}".F(client.Index, race)))); item.GetWidget("LABEL").GetText = () => CountryNames[race]; var flag = item.GetWidget("FLAG"); @@ -347,7 +349,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic Func setupItem = (ii, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - () => client.Team == ii, + () => client.Team == ii, () => orderManager.IssueOrder(Order.Command("team {0} {1}".F(client.Index, ii)))); item.GetWidget("LABEL").GetText = () => ii == 0 ? "-" : ii.ToString(); return item; @@ -377,7 +379,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic { { "onSelect", onSelect }, { "onChange", onChange }, - { "initialRamp", orderManager.LocalClient.ColorRamp } + { "initialRamp", client.ColorRamp } }); color.AttachPanel(colorChooser); @@ -398,7 +400,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic Widget template; // Empty slot - if (client == null && slot.Bot == null) + if (client == null) { template = EmptySlotTemplate.Clone(); Func getText = () => slot.Closed ? "Closed" : "Open"; @@ -408,7 +410,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic var name = template.GetWidget("NAME_HOST"); name.IsVisible = () => true; name.GetText = getText; - name.OnMouseDown = _ => ShowSlotDropDown(name, slot); + name.OnMouseDown = _ => ShowSlotDropDown(name, slot, client); } else { @@ -421,51 +423,43 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic if (join != null) { join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + key)); return true; }; - join.IsVisible = () => !slot.Closed && slot.Bot == null && orderManager.LocalClient.State != Session.ClientState.Ready; - } - } - // Bot - else if (client == null && slot.Bot != null) - { - template = BotTemplate.Clone(); - Func getText = () => slot.Bot; - - if (Game.IsHost) - { - var name = template.GetWidget("NAME_HOST"); - name.IsVisible = () => true; - name.GetText = getText; - name.OnMouseDown = _ => ShowSlotDropDown(name, slot); - } - else - { - var name = template.GetWidget("NAME"); - name.IsVisible = () => true; - name.GetText = getText; + join.IsVisible = () => !slot.Closed && orderManager.LocalClient.State != Session.ClientState.Ready; } } // Editable player in slot - else if (client.Index == orderManager.LocalClient.Index && client.State != Session.ClientState.Ready) + else if ((client.Index == orderManager.LocalClient.Index && client.State != Session.ClientState.Ready) || + (client.Bot != null && Game.IsHost)) { template = LocalPlayerTemplate.Clone(); - var name = template.GetWidget("NAME"); - name.Text = client.Name; - name.OnEnterKey = () => + if (client.Bot != null) { - name.Text = name.Text.Trim(); - if (name.Text.Length == 0) - name.Text = client.Name; + var name = template.GetWidget("BOT_DROPDOWN"); + name.IsVisible = () => true; + name.GetText = () => client.Name; + name.OnMouseDown = _ => ShowSlotDropDown(name, slot, client); + } + else + { + var name = template.GetWidget("NAME"); + name.IsVisible = () => true; + name.Text = client.Name; + name.OnEnterKey = () => + { + name.Text = name.Text.Trim(); + if (name.Text.Length == 0) + name.Text = client.Name; - name.LoseFocus(); - if (name.Text == client.Name) + name.LoseFocus(); + if (name.Text == client.Name) + return true; + + orderManager.IssueOrder(Order.Command("name " + name.Text)); + Game.Settings.Player.Name = name.Text; + Game.Settings.Save(); return true; - - orderManager.IssueOrder(Order.Command("name " + name.Text)); - Game.Settings.Player.Name = name.Text; - Game.Settings.Save(); - return true; - }; - name.OnLoseFocus = () => name.OnEnterKey(); + }; + name.OnLoseFocus = () => name.OnEnterKey(); + } var color = template.GetWidget("COLOR"); color.IsDisabled = () => slot.LockColor; @@ -485,11 +479,12 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic factionflag.GetImageCollection = () => "flags"; var team = template.GetWidget("TEAM"); - team.IsDisabled = () => slot.LockTeam; - team.OnMouseDown = _ => { if (slot.LockTeam) return true; return ShowTeamDropDown(team, client); }; + team.IsDisabled = () => slot.LockTeam || client.Bot != null; + team.OnMouseDown = _ => { if (team.IsDisabled()) return true; return ShowTeamDropDown(team, client); }; team.GetText = () => (client.Team == 0) ? "-" : client.Team.ToString(); var status = template.GetWidget("STATUS"); + status.IsVisible = () => client.Bot == null; status.IsChecked = () => client.State == Session.ClientState.Ready; status.OnClick += CycleReady; } @@ -513,6 +508,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic var status = template.GetWidget("STATUS"); status.IsChecked = () => client.State == Session.ClientState.Ready; + status.IsVisible = () => client.Bot == null; if (client.Index == orderManager.LocalClient.Index) status.OnClick += CycleReady; @@ -661,6 +657,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic // Set the initial state updateSliders(); + onChange(ramp); } } } diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncReplayBrowserLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncReplayBrowserLogic.cs index d57a8af814..cff52288c0 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncReplayBrowserLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncReplayBrowserLogic.cs @@ -78,7 +78,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic panel.GetWidget("MAP_TITLE").GetText = () => currentMap != null ? currentMap.Title : "(Unknown Map)"; - var players = currentSummary.LobbyInfo.Slots.Count(s => currentSummary.LobbyInfo.ClientInSlot(s.Key) != null || s.Value.Bot != null); + var players = currentSummary.LobbyInfo.Slots + .Count(s => currentSummary.LobbyInfo.ClientInSlot(s.Key) != null); panel.GetWidget("PLAYERS").GetText = () => players.ToString(); } catch (Exception e) diff --git a/OpenRA.Mods.RA/CreateMPPlayers.cs b/OpenRA.Mods.RA/CreateMPPlayers.cs index 2e216e659f..278eaf47cf 100644 --- a/OpenRA.Mods.RA/CreateMPPlayers.cs +++ b/OpenRA.Mods.RA/CreateMPPlayers.cs @@ -34,13 +34,13 @@ namespace OpenRA.Mods.RA foreach (var kv in w.LobbyInfo.Slots) { var client = w.LobbyInfo.ClientInSlot(kv.Key); - if (client == null && kv.Value.Bot == null) + if (client == null) continue; var player = new Player(w, client, kv.Value, w.Map.Players[kv.Value.PlayerReference]); w.AddPlayer(player); - if (client != null && client.Index == Game.LocalClientId) + if (client.Index == Game.LocalClientId) w.SetLocalPlayer(player.InternalName); } diff --git a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs index 9a6d732b5e..a5f3a25648 100644 --- a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs @@ -82,8 +82,7 @@ namespace OpenRA.Mods.RA.Server } var slot = server.lobbyInfo.Slots[s]; - if (slot.Closed || slot.Bot != null || - server.lobbyInfo.ClientInSlot(s) != null) + if (slot.Closed || server.lobbyInfo.ClientInSlot(s) != null) return false; client.Slot = s; @@ -119,17 +118,20 @@ namespace OpenRA.Mods.RA.Server var occupant = server.lobbyInfo.ClientInSlot(s); if (occupant != null) { - var occupantConn = server.conns.FirstOrDefault( c => c.PlayerIndex == occupant.Index ); - if (occupantConn != null) + if (occupant.Bot != null) + server.lobbyInfo.Clients.Remove(occupant); + else { - server.SendOrderTo(occupantConn, "ServerError", "Your slot was closed by the host"); - server.DropClient(occupantConn); + var occupantConn = server.conns.FirstOrDefault( c => c.PlayerIndex == occupant.Index ); + if (occupantConn != null) + { + server.SendOrderTo(occupantConn, "ServerError", "Your slot was closed by the host"); + server.DropClient(occupantConn); + } } } - var slot = server.lobbyInfo.Slots[s]; - slot.Closed = true; - slot.Bot = null; + server.lobbyInfo.Slots[s].Closed = true; server.SyncLobbyInfo(); return true; }}, @@ -150,7 +152,11 @@ namespace OpenRA.Mods.RA.Server var slot = server.lobbyInfo.Slots[s]; slot.Closed = false; - slot.Bot = null; + + // Slot may have a bot in it + var occupant = server.lobbyInfo.ClientInSlot(s); + if (occupant != null && occupant.Bot != null) + server.lobbyInfo.Clients.Remove(occupant); server.SyncLobbyInfo(); return true; @@ -178,10 +184,30 @@ namespace OpenRA.Mods.RA.Server return true; } + var botType = string.Join(" ", parts.Skip(1).ToArray() ); var slot = server.lobbyInfo.Slots[parts[0]]; - slot.Bot = string.Join(" ", parts.Skip(1).ToArray() ); slot.Closed = false; + var bot = new Session.Client() + { + Index = server.ChooseFreePlayerIndex(), + Name = botType, + Bot = botType, + Slot = parts[0], + Country = "random", + SpawnPoint = 0, + Team = 0, + State = Session.ClientState.NotReady + }; + + // pick a random color for the bot + var hue = (byte)Game.CosmeticRandom.Next(255); + var sat = (byte)Game.CosmeticRandom.Next(255); + var lum = (byte)Game.CosmeticRandom.Next(51,255); + bot.ColorRamp = new ColorRamp(hue, sat, lum, 10); + + S.SyncClientToPlayerReference(client, server.Map.Players[parts[0]]); + server.lobbyInfo.Clients.Add(bot); server.SyncLobbyInfo(); return true; }}, @@ -193,20 +219,29 @@ namespace OpenRA.Mods.RA.Server server.SendChatTo( conn, "Only the host can change the map" ); return true; } - server.lobbyInfo.GlobalSettings.Map = s; + server.lobbyInfo.GlobalSettings.Map = s; + var oldSlots = server.lobbyInfo.Slots.Keys.ToArray(); LoadMap(server); - // Reassign players into slots + // Reassign players into new slots based on their old slots: + // - Observers remain as observers + // - Players who now lack a slot are made observers + // - Bots who now lack a slot are dropped + var slots = server.lobbyInfo.Slots.Keys.ToArray(); int i = 0; - foreach(var c in server.lobbyInfo.Clients) + foreach (var os in oldSlots) { + var c = server.lobbyInfo.ClientInSlot(os); + if (c == null) + continue; + c.SpawnPoint = 0; c.State = Session.ClientState.NotReady; - c.Slot = c.Slot == null || i >= server.lobbyInfo.Slots.Count ? - null : server.lobbyInfo.Slots.ElementAt(i++).Key; - + c.Slot = i < slots.Length ? slots[i++] : null; if (c.Slot != null) S.SyncClientToPlayerReference(c, server.Map.Players[c.Slot]); + else if (c.Bot != null) + server.lobbyInfo.Clients.Remove(c); } server.SyncLobbyInfo(); @@ -282,7 +317,6 @@ namespace OpenRA.Mods.RA.Server return new Session.Slot { PlayerReference = pr.Name, - Bot = null, Closed = false, AllowBots = pr.AllowBots, LockRace = pr.LockRace, diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index bc2c97e3d0..e58a85a76f 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -35,7 +35,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic readonly OrderManager orderManager; readonly WorldRenderer worldRenderer; [ObjectCreator.UseCtor] - internal LobbyLogic( [ObjectCreator.Param( "widget" )] Widget lobby, [ObjectCreator.Param] OrderManager orderManager, [ObjectCreator.Param] WorldRenderer worldRenderer) + internal LobbyLogic([ObjectCreator.Param( "widget" )] Widget lobby, + [ObjectCreator.Param] OrderManager orderManager, + [ObjectCreator.Param] WorldRenderer worldRenderer) { this.orderManager = orderManager; this.worldRenderer = worldRenderer; @@ -93,13 +95,18 @@ namespace OpenRA.Mods.RA.Widgets.Logic return sc; }; - CountryNames = Rules.Info["world"].Traits.WithInterface().ToDictionary(a => a.Race, a => a.Name); + CountryNames = Rules.Info["world"].Traits.WithInterface() + .ToDictionary(a => a.Race, a => a.Name); CountryNames.Add("random", "Random"); var mapButton = lobby.GetWidget("CHANGEMAP_BUTTON"); mapButton.OnMouseUp = mi => { - Widget.OpenWindow( "MAP_CHOOSER", new WidgetArgs() { { "orderManager", orderManager }, { "mapName", MapUid } } ); + Widget.OpenWindow("MAP_CHOOSER", new WidgetArgs() + { + { "orderManager", orderManager }, + { "mapName", MapUid } + }); return true; }; @@ -221,11 +228,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic } } - bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot) + bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot, Session.Client client) { var options = new List() { - new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && slot.Bot == null)), + new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && client == null)), new SlotDropDownOption("Closed", "slot_close "+slot.PlayerReference, () => slot.Closed) }; @@ -233,7 +240,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic foreach (var b in Rules.Info["player"].Traits.WithInterface().Select(t => t.Name)) { var bot = b; - options.Add(new SlotDropDownOption("Bot: {0}".F(bot), "slot_bot {0} {1}".F(slot.PlayerReference, bot), () => slot.Bot == bot)); + options.Add(new SlotDropDownOption("Bot: {0}".F(bot), + "slot_bot {0} {1}".F(slot.PlayerReference, bot), + () => client != null && client.Bot == bot)); } Func setupItem = (o, itemTemplate) => @@ -327,32 +336,30 @@ namespace OpenRA.Mods.RA.Widgets.Logic var c = orderManager.LobbyInfo.ClientInSlot(kv.Key); Widget template; - if (c == null) + if (c == null || c.Bot != null) { if (Game.IsHost) { template = EmptySlotTemplateHost.Clone(); var name = template.GetWidget("NAME"); - name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; - name.OnMouseDown = _ => ShowSlotDropDown(name, s); + name.GetText = () => s.Closed ? "Closed" : (c == null) ? "Open" : c.Bot; + name.OnMouseDown = _ => ShowSlotDropDown(name, s, c); } else { template = EmptySlotTemplate.Clone(); var name = template.GetWidget("NAME"); - name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; + name.GetText = () => s.Closed ? "Closed" : (c == null) ? "Open" : c.Bot; } var join = template.GetWidget("JOIN"); if (join != null) { join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + s.PlayerReference)); return true; }; - join.IsVisible = () => !s.Closed && s.Bot == null && orderManager.LocalClient.State != Session.ClientState.Ready; + join.IsVisible = () => !s.Closed && c == null && orderManager.LocalClient.State != Session.ClientState.Ready; } - var bot = template.GetWidget("BOT"); - if (bot != null) - bot.IsVisible = () => s.Bot != null; + template.GetWidget("BOT").IsVisible = () => c != null; } else if (c.Index == orderManager.LocalClient.Index && c.State != Session.ClientState.Ready) { @@ -401,11 +408,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic var status = template.GetWidget("STATUS"); status.IsChecked = () => c.State == Session.ClientState.Ready; status.OnClick = CycleReady; - - var spectator = template.GetWidget("SPECTATOR"); - - Session.Slot ss = s; - spectator.IsVisible = () => ss.Bot != null; } else { @@ -429,11 +431,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (c.Index == orderManager.LocalClient.Index) status.OnClick = CycleReady; - var spectator = template.GetWidget("SPECTATOR"); - - Session.Slot ss = s; - spectator.IsVisible = () => ss.Bot != null; - var kickButton = template.GetWidget("KICK"); kickButton.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index; kickButton.OnMouseUp = mi => diff --git a/mods/cnc/chrome/lobby.yaml b/mods/cnc/chrome/lobby.yaml index 68051d7783..1bd16feca8 100644 --- a/mods/cnc/chrome/lobby.yaml +++ b/mods/cnc/chrome/lobby.yaml @@ -67,6 +67,14 @@ Container@SERVER_LOBBY: Width:150 Height:25 MaxLength:16 + Visible:false + DropDownButton@BOT_DROPDOWN: + Id:BOT_DROPDOWN + Text:Name + Width:150 + Height:25 + Font:Regular + Visible:false DropDownButton@COLOR: Id:COLOR Width:80 @@ -207,38 +215,6 @@ Container@SERVER_LOBBY: Height:25 X:160 Y:0 - Container@TEMPLATE_BOT: - Id:TEMPLATE_BOT - X:5 - Y:0 - Width:475 - Height:25 - Visible:false - Children: - DropDownButton@NAME_HOST: - Id:NAME_HOST - Text:Name - Width:150 - Height:25 - Font:Regular - Visible:false - Label@NAME: - Id:NAME - Text:Name - Width:145 - Height:25 - X:5 - Y:0-1 - Visible:false - Label@BOT: - Id:BOT - Text:Bot - Width:278 - Height:25 - X:160 - Y:0 - Align:Center - Font:Bold Container@TEMPLATE_LOCAL_SPECTATOR: Id:TEMPLATE_LOCAL_SPECTATOR X:5 diff --git a/mods/ra/chrome/gamelobby.yaml b/mods/ra/chrome/gamelobby.yaml index 44bbb0416b..da3aa061e9 100644 --- a/mods/ra/chrome/gamelobby.yaml +++ b/mods/ra/chrome/gamelobby.yaml @@ -98,15 +98,6 @@ Background@SERVER_LOBBY: Y:2 Width:20 Height:20 - Label@SPECTATOR: - Id:SPECTATOR - Text:Spectator - Width:278 - Height:25 - X:160 - Y:0 - Align:Center - Font:Bold Container@TEMPLATE_REMOTE: Id:TEMPLATE_REMOTE X:5 @@ -170,15 +161,6 @@ Background@SERVER_LOBBY: Y:2 Width:20 Height:20 - Label@SPECTATOR: - Id:SPECTATOR - Text:Spectator - Width:278 - Height:25 - X:160 - Y:0 - Align:Center - Font:Bold Container@TEMPLATE_EMPTY: Id:TEMPLATE_EMPTY X:5