diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 8e6710b7e3..e3ed8f6137 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -18,7 +18,8 @@ namespace OpenRA.Network public class Session { public List Clients = new List(); - public List Slots = new List(); + // Keyed by the PlayerReference id that the slot corresponds to + public Dictionary Slots = new Dictionary(); public Global GlobalSettings = new Global(); public Client ClientWithIndex(int clientID) @@ -26,15 +27,15 @@ namespace OpenRA.Network return Clients.SingleOrDefault(c => c.Index == clientID); } - public Client ClientInSlot(Slot slot) + public Client ClientInSlot(string slot) { - return Clients.SingleOrDefault(c => c.Slot == slot.Index); + return Clients.SingleOrDefault(c => c.Slot == slot); } - public int FirstEmptySlot() + public string FirstEmptySlot() { - return Slots.First(s => !s.Closed && ClientInSlot(s) == null - && s.Bot == null).Index; + return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null + && s.Value.Bot == null).Key; } public enum ClientState @@ -53,17 +54,19 @@ namespace OpenRA.Network public string Name; public ClientState State; public int Team; - public int Slot; // which slot we're in, or -1 for `observer`. + public string Slot; // slot ID, or null for observer } public class Slot { - public int Index; + 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 string MapPlayer; // playerReference to bind against. - public bool Spectator = false; // Spectating or not - // todo: more stuff? + + public bool AllowBots; + public bool LockRace; + public bool LockColor; + public bool LockTeam; } public class Global @@ -90,7 +93,7 @@ namespace OpenRA.Network 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))); + clientData.Add(new MiniYamlNode("Slot@{0}".F(slot.Key), FieldSaver.Save(slot.Value))); clientData.Add(new MiniYamlNode("GlobalSettings", FieldSaver.Save(GlobalSettings))); @@ -117,7 +120,8 @@ namespace OpenRA.Network break; case "Slot": - session.Slots.Add(FieldLoader.Load(y.Value)); + var s = FieldLoader.Load(y.Value); + session.Slots.Add(s.PlayerReference, s); break; } } diff --git a/OpenRA.Game/Player.cs b/OpenRA.Game/Player.cs index 22ca713a28..9b312b8df8 100644 --- a/OpenRA.Game/Player.cs +++ b/OpenRA.Game/Player.cs @@ -74,7 +74,7 @@ namespace OpenRA if (slot != null && slot.Bot != null) { IsBot = true; - botType = pr.Bot; + botType = slot.Bot; PlayerName = slot.Bot; // pick a random color for the bot diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index bb8b9da479..e035f7da11 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -218,9 +218,8 @@ namespace OpenRA.Server client.Index = newConn.PlayerIndex; client.Slot = lobbyInfo.FirstEmptySlot(); - var slotData = lobbyInfo.Slots.FirstOrDefault( x => x.Index == client.Slot ); - if (slotData != null && slotData.MapPlayer != null) - SyncClientToPlayerReference(client, Map.Players[slotData.MapPlayer]); + if (client.Slot != null) + SyncClientToPlayerReference(client, Map.Players[client.Slot]); lobbyInfo.Clients.Add(client); diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncLobbyLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncLobbyLogic.cs index bc70913f0c..831a90f87d 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncLobbyLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncLobbyLogic.cs @@ -22,7 +22,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic { public class CncLobbyLogic { - Widget LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, EmptySlotTemplateHost; + Widget LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, BotTemplate, + LocalSpectatorTemplate, RemoteSpectatorTemplate, NewSpectatorTemplate; ScrollPanelWidget chatPanel; Widget chatTemplate; @@ -107,7 +108,10 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic LocalPlayerTemplate = Players.GetWidget("TEMPLATE_LOCAL"); RemotePlayerTemplate = Players.GetWidget("TEMPLATE_REMOTE"); EmptySlotTemplate = Players.GetWidget("TEMPLATE_EMPTY"); - EmptySlotTemplateHost = Players.GetWidget("TEMPLATE_EMPTY_HOST"); + BotTemplate = Players.GetWidget("TEMPLATE_BOT"); + LocalSpectatorTemplate = Players.GetWidget("TEMPLATE_LOCAL_SPECTATOR"); + RemoteSpectatorTemplate = Players.GetWidget("TEMPLATE_REMOTE_SPECTATOR"); + NewSpectatorTemplate = Players.GetWidget("TEMPLATE_NEW_SPECTATOR"); var mapPreview = lobby.GetWidget("MAP_PREVIEW"); mapPreview.Map = () => Map; @@ -231,7 +235,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic { var slot = orderManager.LobbyInfo.FirstEmptySlot(); var bot = Rules.Info["player"].Traits.WithInterface().Select(t => t.Name).FirstOrDefault(); - if (bot != null) + if (slot != null && bot != null) orderManager.IssueOrder(Order.Command("slot_bot {0} {1}".F(slot, bot))); }); } @@ -278,11 +282,6 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic title.Text = orderManager.LobbyInfo.GlobalSettings.ServerName; } - Session.Client GetClientInSlot(Session.Slot slot) - { - return orderManager.LobbyInfo.ClientInSlot( slot ); - } - class SlotDropDownOption { public string Title; @@ -297,19 +296,19 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic } } - bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot, bool showBotOptions) + bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot) { var options = new List() { - new SlotDropDownOption("Open", "slot_open "+slot.Index, () => (!slot.Closed && slot.Bot == null)), - new SlotDropDownOption("Closed", "slot_close "+slot.Index, () => slot.Closed) + new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && slot.Bot == null)), + new SlotDropDownOption("Closed", "slot_close "+slot.PlayerReference, () => slot.Closed) }; - if (showBotOptions) + 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.Index, bot), () => slot.Bot == bot)); + options.Add(new SlotDropDownOption("Bot: {0}".F(bot), "slot_bot {0} {1}".F(slot.PlayerReference, bot), () => slot.Bot == bot)); } Func setupItem = (o, itemTemplate) => @@ -325,16 +324,12 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic return true; } - bool ShowRaceDropDown(DropDownButtonWidget dropdown, Session.Slot slot) + bool ShowRaceDropDown(DropDownButtonWidget dropdown, Session.Client client) { - if (Map.Players[slot.MapPlayer].LockRace) - return false; - - var sr = GetClientInSlot(slot).Country; Func setupItem = (race, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - () => sr == race, + () => client.Country == race, () => orderManager.IssueOrder(Order.Command("race "+race))); item.GetWidget("LABEL").GetText = () => CountryNames[race]; var flag = item.GetWidget("FLAG"); @@ -347,13 +342,12 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic return true; } - bool ShowTeamDropDown(DropDownButtonWidget dropdown, Session.Slot slot) + bool ShowTeamDropDown(DropDownButtonWidget dropdown, Session.Client client) { - var c = GetClientInSlot(slot); Func setupItem = (ii, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - () => c.Team == ii, + () => client.Team == ii, () => orderManager.IssueOrder(Order.Command("team "+ii))); item.GetWidget("LABEL").GetText = () => ii == 0 ? "-" : ii.ToString(); return item; @@ -364,11 +358,8 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic return true; } - bool ShowColorDropDown(DropDownButtonWidget color, Session.Slot s) + bool ShowColorDropDown(DropDownButtonWidget color, Session.Client client) { - if (Map.Players[s.MapPlayer].LockColor) - return true; - Action onSelect = c => { Game.Settings.Player.ColorRamp = c; @@ -399,70 +390,74 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic // Todo: handle this nicer Players.RemoveChildren(); - foreach (var slot in orderManager.LobbyInfo.Slots) + foreach (var kv in orderManager.LobbyInfo.Slots) { - var s = slot; - var c = GetClientInSlot(s); + var key = kv.Key; + var slot = kv.Value; + var client = orderManager.LobbyInfo.ClientInSlot(key); Widget template; - if (c == null) + // Empty slot + if (client == null && slot.Bot == null) { + template = EmptySlotTemplate.Clone(); + Func getText = () => slot.Closed ? "Closed" : "Open"; + if (Game.IsHost) { - if (slot.Spectator) - { - template = EmptySlotTemplateHost.Clone(); - var name = template.GetWidget("NAME"); - name.GetText = () => s.Closed ? "Closed" : "Open"; - name.OnMouseDown = _ => ShowSlotDropDown(name, s, false); - var btn = template.GetWidget("JOIN"); - btn.GetText = () => "Spectate in this slot"; - } - else - { - template = EmptySlotTemplateHost.Clone(); - var name = template.GetWidget("NAME"); - name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; - name.OnMouseDown = _ => ShowSlotDropDown(name, s, Map.Players[ s.MapPlayer ].AllowBots); - } + var name = template.GetWidget("NAME_HOST"); + name.IsVisible = () => true; + name.GetText = getText; + name.OnMouseDown = _ => ShowSlotDropDown(name, slot); } else { - template = EmptySlotTemplate.Clone(); var name = template.GetWidget("NAME"); - name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; - - if (slot.Spectator) - { - var btn = template.GetWidget("JOIN"); - btn.GetText = () => "Spectate in this slot"; - } + name.IsVisible = () => true; + name.GetText = getText; } var join = template.GetWidget("JOIN"); if (join != null) { - join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + s.Index)); return true; }; - join.IsVisible = () => !s.Closed && s.Bot == null && orderManager.LocalClient.State != Session.ClientState.Ready; + join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + key)); return true; }; + join.IsVisible = () => !slot.Closed && slot.Bot == null && orderManager.LocalClient.State != Session.ClientState.Ready; } - - var bot = template.GetWidget("BOT"); - if (bot != null) - bot.IsVisible = () => s.Bot != null; } - else if (c.Index == orderManager.LocalClient.Index && c.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; + } + } + // Editable player in slot + else if (client.Index == orderManager.LocalClient.Index && client.State != Session.ClientState.Ready) { template = LocalPlayerTemplate.Clone(); var name = template.GetWidget("NAME"); - name.Text = c.Name; + name.Text = client.Name; name.OnEnterKey = () => { name.Text = name.Text.Trim(); if (name.Text.Length == 0) - name.Text = c.Name; + name.Text = client.Name; name.LoseFocus(); - if (name.Text == c.Name) + if (name.Text == client.Name) return true; orderManager.IssueOrder(Order.Command("name " + name.Text)); @@ -473,84 +468,139 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic name.OnLoseFocus = () => name.OnEnterKey(); var color = template.GetWidget("COLOR"); - color.OnMouseDown = _ => ShowColorDropDown(color, s); + color.IsDisabled = () => slot.LockColor; + color.OnMouseDown = _ => { if (slot.LockColor) return true; return ShowColorDropDown(color, client); }; var colorBlock = color.GetWidget("COLORBLOCK"); - colorBlock.GetColor = () => c.ColorRamp.GetColor(0); + colorBlock.GetColor = () => client.ColorRamp.GetColor(0); var faction = template.GetWidget("FACTION"); - faction.OnMouseDown = _ => ShowRaceDropDown(faction, s); + faction.IsDisabled = () => slot.LockRace; + faction.OnMouseDown = _ => { if (slot.LockRace) return true; return ShowRaceDropDown(faction, client); }; var factionname = faction.GetWidget("FACTIONNAME"); - factionname.GetText = () => CountryNames[c.Country]; + factionname.GetText = () => CountryNames[client.Country]; var factionflag = faction.GetWidget("FACTIONFLAG"); - factionflag.GetImageName = () => c.Country; + factionflag.GetImageName = () => client.Country; factionflag.GetImageCollection = () => "flags"; var team = template.GetWidget("TEAM"); - team.OnMouseDown = _ => ShowTeamDropDown(team, s); - team.GetText = () => (c.Team == 0) ? "-" : c.Team.ToString(); + team.IsDisabled = () => slot.LockTeam; + team.OnMouseDown = _ => { if (slot.LockTeam) return true; return ShowTeamDropDown(team, client); }; + team.GetText = () => (client.Team == 0) ? "-" : client.Team.ToString(); var status = template.GetWidget("STATUS"); - status.IsChecked = () => c.State == Session.ClientState.Ready; + status.IsChecked = () => client.State == Session.ClientState.Ready; status.OnClick += CycleReady; - - var spectator = template.GetWidget("SPECTATOR"); - - Session.Slot slot1 = slot; - color.IsVisible = () => !slot1.Spectator; - colorBlock.IsVisible = () => !slot1.Spectator; - faction.IsVisible = () => !slot1.Spectator; - factionname.IsVisible = () => !slot1.Spectator; - factionflag.IsVisible = () => !slot1.Spectator; - team.IsVisible = () => !slot1.Spectator; - spectator.IsVisible = () => slot1.Spectator || slot1.Bot != null; } + // Non-editable player in slot else { template = RemotePlayerTemplate.Clone(); - template.GetWidget("NAME").GetText = () => c.Name; + template.GetWidget("NAME").GetText = () => client.Name; var color = template.GetWidget("COLOR"); - color.GetColor = () => c.ColorRamp.GetColor(0); + color.GetColor = () => client.ColorRamp.GetColor(0); var faction = template.GetWidget("FACTION"); var factionname = faction.GetWidget("FACTIONNAME"); - factionname.GetText = () => CountryNames[c.Country]; + factionname.GetText = () => CountryNames[client.Country]; var factionflag = faction.GetWidget("FACTIONFLAG"); - factionflag.GetImageName = () => c.Country; + factionflag.GetImageName = () => client.Country; factionflag.GetImageCollection = () => "flags"; var team = template.GetWidget("TEAM"); - team.GetText = () => (c.Team == 0) ? "-" : c.Team.ToString(); + team.GetText = () => (client.Team == 0) ? "-" : client.Team.ToString(); var status = template.GetWidget("STATUS"); - status.IsChecked = () => c.State == Session.ClientState.Ready; - if (c.Index == orderManager.LocalClient.Index) + status.IsChecked = () => client.State == Session.ClientState.Ready; + if (client.Index == orderManager.LocalClient.Index) status.OnClick += CycleReady; - var spectator = template.GetWidget("SPECTATOR"); - - Session.Slot slot1 = slot; - color.IsVisible = () => !slot1.Spectator; - faction.IsVisible = () => !slot1.Spectator; - factionname.IsVisible = () => !slot1.Spectator; - factionflag.IsVisible = () => !slot1.Spectator; - team.IsVisible = () => !slot1.Spectator; - spectator.IsVisible = () => slot1.Spectator || slot1.Bot != null; - var kickButton = template.GetWidget("KICK"); - kickButton.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index; + kickButton.IsVisible = () => Game.IsHost && client.Index != orderManager.LocalClient.Index; kickButton.OnMouseUp = mi => { - orderManager.IssueOrder(Order.Command("kick " + c.Slot)); + orderManager.IssueOrder(Order.Command("kick " + client.Index)); return true; }; } - template.Id = "SLOT_{0}".F(s.Index); template.IsVisible = () => true; Players.AddChild(template); - } + } + + // Add spectators + foreach (var client in orderManager.LobbyInfo.Clients.Where(client => client.Slot == null)) + { + Widget template; + // Editable spectator + if (client.Index == orderManager.LocalClient.Index && client.State != Session.ClientState.Ready) + { + template = LocalSpectatorTemplate.Clone(); + var name = template.GetWidget("NAME"); + 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) + return true; + + orderManager.IssueOrder(Order.Command("name " + name.Text)); + Game.Settings.Player.Name = name.Text; + Game.Settings.Save(); + return true; + }; + name.OnLoseFocus = () => name.OnEnterKey(); + + var color = template.GetWidget("COLOR"); + color.OnMouseDown = _ => ShowColorDropDown(color, client); + + var colorBlock = color.GetWidget("COLORBLOCK"); + colorBlock.GetColor = () => client.ColorRamp.GetColor(0); + + var status = template.GetWidget("STATUS"); + status.IsChecked = () => client.State == Session.ClientState.Ready; + status.OnClick += CycleReady; + } + // Non-editable spectator + else + { + template = RemoteSpectatorTemplate.Clone(); + template.GetWidget("NAME").GetText = () => client.Name; + var color = template.GetWidget("COLOR"); + color.GetColor = () => client.ColorRamp.GetColor(0); + + var status = template.GetWidget("STATUS"); + status.IsChecked = () => client.State == Session.ClientState.Ready; + if (client.Index == orderManager.LocalClient.Index) + status.OnClick += CycleReady; + + var kickButton = template.GetWidget("KICK"); + kickButton.IsVisible = () => Game.IsHost && client.Index != orderManager.LocalClient.Index; + kickButton.OnMouseUp = mi => + { + orderManager.IssueOrder(Order.Command("kick " + client.Index)); + return true; + }; + } + + template.IsVisible = () => true; + Players.AddChild(template); + } + + // Spectate button + if (orderManager.LocalClient.Slot != null && orderManager.LocalClient.State != Session.ClientState.Ready) + { + var spec = NewSpectatorTemplate.Clone(); + var btn = spec.GetWidget("SPECTATE"); + btn.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("spectate")); return true; }; + spec.IsVisible = () => true; + Players.AddChild(spec); + } } bool SpawnPointAvailable(int index) { return (index == 0) || orderManager.LobbyInfo.Clients.All(c => c.SpawnPoint != index); } diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncReplayBrowserLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncReplayBrowserLogic.cs index ef87e705c1..d57a8af814 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncReplayBrowserLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncReplayBrowserLogic.cs @@ -78,7 +78,7 @@ 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) != null || s.Bot != null); + var players = currentSummary.LobbyInfo.Slots.Count(s => currentSummary.LobbyInfo.ClientInSlot(s.Key) != null || s.Value.Bot != 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 dec748eae7..2e216e659f 100644 --- a/OpenRA.Mods.RA/CreateMPPlayers.cs +++ b/OpenRA.Mods.RA/CreateMPPlayers.cs @@ -31,13 +31,13 @@ namespace OpenRA.Mods.RA } // create the players which are bound through slots. - foreach (var slot in w.LobbyInfo.Slots) + foreach (var kv in w.LobbyInfo.Slots) { - var client = w.LobbyInfo.Clients.FirstOrDefault(c => c.Slot == slot.Index && slot.MapPlayer != null); - if (client == null && slot.Bot == null) + var client = w.LobbyInfo.ClientInSlot(kv.Key); + if (client == null && kv.Value.Bot == null) continue; - var player = new Player(w, client, slot, w.Map.Players[slot.MapPlayer]); + var player = new Player(w, client, kv.Value, w.Map.Players[kv.Value.PlayerReference]); w.AddPlayer(player); if (client != null && client.Index == Game.LocalClientId) @@ -60,8 +60,8 @@ namespace OpenRA.Mods.RA if (p.World.LobbyInfo.Slots.Count > 0) { - if (p.World.LobbyInfo.Slots[pc.Slot].Spectator) return Stance.Ally; - if (p.World.LobbyInfo.Slots[qc.Slot].Spectator) return Stance.Ally; + if (p.PlayerRef == null) return Stance.Ally; + if (q.PlayerRef == null) return Stance.Ally; } // Stances set via the player reference diff --git a/OpenRA.Mods.RA/MPStartLocations.cs b/OpenRA.Mods.RA/MPStartLocations.cs index 8a31ffcb93..ec05dc7aa4 100755 --- a/OpenRA.Mods.RA/MPStartLocations.cs +++ b/OpenRA.Mods.RA/MPStartLocations.cs @@ -28,19 +28,17 @@ namespace OpenRA.Mods.RA public void WorldLoaded(World world) { - var taken = world.LobbyInfo.Clients.Where(c => c.SpawnPoint != 0 && c.Slot != -1) + var taken = world.LobbyInfo.Clients.Where(c => c.SpawnPoint != 0 && c.Slot != null) .Select(c => world.Map.SpawnPoints.ElementAt(c.SpawnPoint - 1)).ToList(); var available = world.Map.SpawnPoints.Except(taken).ToList(); // Set spawn - foreach (var slot in world.LobbyInfo.Slots) + foreach (var kv in world.LobbyInfo.Slots) { - if (slot.Spectator) - continue; // Skip spectator slots - var client = world.LobbyInfo.Clients.FirstOrDefault(c => c.Slot == slot.Index); - var player = FindPlayerInSlot(world, slot); + var player = FindPlayerInSlot(world, kv.Key); if (player == null) continue; + var client = world.LobbyInfo.ClientInSlot(kv.Key); var spid = (client == null || client.SpawnPoint == 0) ? ChooseSpawnPoint(world, available, taken) : world.Map.SpawnPoints.ElementAt(client.SpawnPoint - 1); @@ -59,9 +57,9 @@ namespace OpenRA.Mods.RA Game.viewport.Center(Start[world.LocalPlayer]); } - static Player FindPlayerInSlot(World world, Session.Slot slot) + static Player FindPlayerInSlot(World world, string pr) { - return world.Players.FirstOrDefault(p => p.PlayerRef.Name == slot.MapPlayer); + return world.Players.FirstOrDefault(p => p.PlayerRef.Name == pr); } static int2 ChooseSpawnPoint(World world, List available, List taken) diff --git a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs index 15a50b52d6..9a6d732b5e 100644 --- a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs @@ -20,8 +20,6 @@ namespace OpenRA.Mods.RA.Server { public class LobbyCommands : ServerTrait, IInterpretCommand, INotifyServerStart { - public static int MaxSpectators = 4; // How many spectators to allow // @todo Expose this as an option - public bool InterpretCommand(S server, Connection conn, Session.Client client, string cmd) { if (server.GameStarted) @@ -77,33 +75,39 @@ namespace OpenRA.Mods.RA.Server { "slot", s => { - int slot; - if (!int.TryParse(s, out slot)) { Log.Write("server", "Invalid slot: {0}", s ); return false; } + if (!server.lobbyInfo.Slots.ContainsKey(s)) + { + Log.Write("server", "Invalid slot: {0}", s ); + return false; + } + var slot = server.lobbyInfo.Slots[s]; - var slotData = server.lobbyInfo.Slots.FirstOrDefault( x => x.Index == slot ); - if (slotData == null || slotData.Closed || slotData.Bot != null - || server.lobbyInfo.Clients.Any( c => c.Slot == slot )) + if (slot.Closed || slot.Bot != null || + server.lobbyInfo.ClientInSlot(s) != null) return false; - client.Slot = slot; - S.SyncClientToPlayerReference(client, slotData.MapPlayer != null ? server.Map.Players[slotData.MapPlayer] : null); - - // if we're entering a spectator slot, relinquish our spawnpoint. - if (slotData.Spectator) - client.SpawnPoint = 0; + client.Slot = s; + S.SyncClientToPlayerReference(client, server.Map.Players[s]); + server.SyncLobbyInfo(); + return true; + }}, + { "spectate", + s => + { + client.Slot = null; + client.SpawnPoint = 0; server.SyncLobbyInfo(); return true; }}, { "slot_close", s => { - int slot; - if (!int.TryParse(s, out slot)) { Log.Write("server", "Invalid slot: {0}", s ); return false; } - - var slotData = server.lobbyInfo.Slots.FirstOrDefault( x => x.Index == slot ); - if (slotData == null) + if (!server.lobbyInfo.Slots.ContainsKey(s)) + { + Log.Write("server", "Invalid slot: {0}", s ); return false; + } if (conn.PlayerIndex != 0) { @@ -111,11 +115,8 @@ namespace OpenRA.Mods.RA.Server return true; } - slotData.Closed = true; - slotData.Bot = null; - - /* kick any player that's in the slot */ - var occupant = server.lobbyInfo.Clients.FirstOrDefault( c => c.Slot == slotData.Index ); + // kick any player that's in the slot + var occupant = server.lobbyInfo.ClientInSlot(s); if (occupant != null) { var occupantConn = server.conns.FirstOrDefault( c => c.PlayerIndex == occupant.Index ); @@ -125,6 +126,9 @@ namespace OpenRA.Mods.RA.Server server.DropClient(occupantConn); } } + var slot = server.lobbyInfo.Slots[s]; + slot.Closed = true; + slot.Bot = null; server.SyncLobbyInfo(); return true; @@ -132,12 +136,11 @@ namespace OpenRA.Mods.RA.Server { "slot_open", s => { - int slot; - if (!int.TryParse(s, out slot)) { Log.Write("server", "Invalid slot: {0}", s ); return false; } - - var slotData = server.lobbyInfo.Slots.FirstOrDefault( x => x.Index == slot ); - if (slotData == null) + if (!server.lobbyInfo.Slots.ContainsKey(s)) + { + Log.Write("server", "Invalid slot: {0}", s ); return false; + } if (conn.PlayerIndex != 0) { @@ -145,8 +148,9 @@ namespace OpenRA.Mods.RA.Server return true; } - slotData.Closed = false; - slotData.Bot = null; + var slot = server.lobbyInfo.Slots[s]; + slot.Closed = false; + slot.Bot = null; server.SyncLobbyInfo(); return true; @@ -162,12 +166,11 @@ namespace OpenRA.Mods.RA.Server return true; } - int slot; - if (!int.TryParse(parts[0], out slot)) { Log.Write("server", "Invalid slot: {0}", s ); return false; } - - var slotData = server.lobbyInfo.Slots.FirstOrDefault( x => x.Index == slot ); - if (slotData == null) + if (!server.lobbyInfo.Slots.ContainsKey(parts[0])) + { + Log.Write("server", "Invalid slot: {0}", parts[0] ); return false; + } if (conn.PlayerIndex != 0) { @@ -175,8 +178,9 @@ namespace OpenRA.Mods.RA.Server return true; } - slotData.Closed = false; - slotData.Bot = string.Join(" ", parts.Skip(1).ToArray() ); + var slot = server.lobbyInfo.Slots[parts[0]]; + slot.Bot = string.Join(" ", parts.Skip(1).ToArray() ); + slot.Closed = false; server.SyncLobbyInfo(); return true; @@ -192,14 +196,17 @@ namespace OpenRA.Mods.RA.Server server.lobbyInfo.GlobalSettings.Map = s; LoadMap(server); + // Reassign players into slots + int i = 0; foreach(var c in server.lobbyInfo.Clients) { c.SpawnPoint = 0; - var slotData = server.lobbyInfo.Slots.FirstOrDefault( x => x.Index == c.Slot ); - if (slotData != null && slotData.MapPlayer != null) - S.SyncClientToPlayerReference(c, server.Map.Players[slotData.MapPlayer]); - c.State = Session.ClientState.NotReady; + c.Slot = c.Slot == null || i >= server.lobbyInfo.Slots.Count ? + null : server.lobbyInfo.Slots.ElementAt(i++).Key; + + if (c.Slot != null) + S.SyncClientToPlayerReference(c, server.Map.Players[c.Slot]); } server.SyncLobbyInfo(); @@ -234,16 +241,17 @@ namespace OpenRA.Mods.RA.Server { "kick", s => { + if (conn.PlayerIndex != 0) { server.SendChatTo( conn, "Only the host can kick players" ); return true; } - int slot; - int.TryParse( s, out slot ); + int clientID; + int.TryParse( s, out clientID ); - var connToKick = server.conns.SingleOrDefault( c => server.GetClient(c) != null && server.GetClient(c).Slot == slot); + var connToKick = server.conns.SingleOrDefault( c => server.GetClient(c) != null && server.GetClient(c).Index == clientID); if (connToKick == null) { server.SendChatTo( conn, "Noone in that slot." ); @@ -252,9 +260,7 @@ namespace OpenRA.Mods.RA.Server server.SendOrderTo(connToKick, "ServerError", "You have been kicked from the server"); server.DropClient(connToKick); - server.SyncLobbyInfo(); - return true; }}, }; @@ -275,9 +281,13 @@ namespace OpenRA.Mods.RA.Server if (!pr.Playable) return null; return new Session.Slot { - MapPlayer = pr.Name, - Bot = null, /* todo: allow the map to specify a bot class? */ + PlayerReference = pr.Name, + Bot = null, Closed = false, + AllowBots = pr.AllowBots, + LockRace = pr.LockRace, + LockColor = pr.LockColor, + LockTeam = false }; } @@ -287,18 +297,7 @@ namespace OpenRA.Mods.RA.Server server.lobbyInfo.Slots = server.Map.Players .Select(p => MakeSlotFromPlayerReference(p.Value)) .Where(s => s != null) - .Select((s, i) => { s.Index = i; return s; }) - .ToList(); - - // Generate slots for spectators - for (int i = 0; i < MaxSpectators; i++) - server.lobbyInfo.Slots.Add(new Session.Slot - { - Spectator = true, - Index = server.lobbyInfo.Slots.Count(), - MapPlayer = null, - Bot = null - }); + .ToDictionary(s => s.PlayerReference, s => s); } } } diff --git a/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs b/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs index 01ac0a9325..afb03aade3 100644 --- a/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs +++ b/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs @@ -66,7 +66,7 @@ namespace OpenRA.Mods.RA.Server server.lobbyInfo.Clients.Count, string.Join(",", Game.CurrentMods.Select(f => "{0}@{1}".F(f.Key, f.Value.Version)).ToArray()), server.lobbyInfo.GlobalSettings.Map, - server.lobbyInfo.Slots.Count( s => !s.Spectator ))); + server.Map.PlayerCount)); if (isInitialPing) { diff --git a/OpenRA.Mods.RA/ServerTraits/PlayerCommands.cs b/OpenRA.Mods.RA/ServerTraits/PlayerCommands.cs index 04a369188a..9bfa5cdae1 100644 --- a/OpenRA.Mods.RA/ServerTraits/PlayerCommands.cs +++ b/OpenRA.Mods.RA/ServerTraits/PlayerCommands.cs @@ -71,7 +71,7 @@ namespace OpenRA.Mods.RA.Server return false; } - if (server.lobbyInfo.Slots[client.Slot].Spectator) + if (client.Slot == null) { server.SendChatTo( conn, "Can't select a spawnpoint as a spectator" ); return false; diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index dbc286db20..b40944add4 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -22,7 +22,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic { public class LobbyLogic { - Widget lobby, LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, EmptySlotTemplateHost; + Widget lobby, LocalPlayerTemplate, RemotePlayerTemplate, EmptySlotTemplate, EmptySlotTemplateHost, + LocalSpectatorTemplate, RemoteSpectatorTemplate, NewSpectatorTemplate; + ScrollPanelWidget Players; Dictionary CountryNames; string MapUid; @@ -50,6 +52,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic RemotePlayerTemplate = Players.GetWidget("TEMPLATE_REMOTE"); EmptySlotTemplate = Players.GetWidget("TEMPLATE_EMPTY"); EmptySlotTemplateHost = Players.GetWidget("TEMPLATE_EMPTY_HOST"); + LocalSpectatorTemplate = Players.GetWidget("TEMPLATE_LOCAL_SPECTATOR"); + RemoteSpectatorTemplate = Players.GetWidget("TEMPLATE_REMOTE_SPECTATOR"); + NewSpectatorTemplate = Players.GetWidget("TEMPLATE_NEW_SPECTATOR"); var mapPreview = lobby.GetWidget("LOBBY_MAP_PREVIEW"); mapPreview.Map = () => Map; @@ -201,11 +206,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic var title = Widget.RootWidget.GetWidget("LOBBY_TITLE"); title.Text = "OpenRA Multiplayer Lobby - " + orderManager.LobbyInfo.GlobalSettings.ServerName; } - - Session.Client GetClientInSlot(Session.Slot slot) - { - return orderManager.LobbyInfo.ClientInSlot( slot ); - } class SlotDropDownOption { @@ -221,20 +221,21 @@ namespace OpenRA.Mods.RA.Widgets.Logic } } - bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot, bool showBotOptions) + bool ShowSlotDropDown(DropDownButtonWidget dropdown, Session.Slot slot) { var options = new List() { - new SlotDropDownOption("Open", "slot_open "+slot.Index, () => (!slot.Closed && slot.Bot == null)), - new SlotDropDownOption("Closed", "slot_close "+slot.Index, () => slot.Closed) + new SlotDropDownOption("Open", "slot_open "+slot.PlayerReference, () => (!slot.Closed && slot.Bot == null)), + new SlotDropDownOption("Closed", "slot_close "+slot.PlayerReference, () => slot.Closed) }; - if (showBotOptions) + 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.Index, bot), () => slot.Bot == bot)); + options.Add(new SlotDropDownOption("Bot: {0}".F(bot), "slot_bot {0} {1}".F(slot.PlayerReference, bot), () => slot.Bot == bot)); } + Func setupItem = (o, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, @@ -248,16 +249,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic return true; } - bool ShowRaceDropDown(DropDownButtonWidget dropdown, Session.Slot slot) + bool ShowRaceDropDown(DropDownButtonWidget dropdown, Session.Client client) { - if (Map.Players[slot.MapPlayer].LockRace) - return false; - - var sr = GetClientInSlot(slot).Country; Func setupItem = (race, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - () => sr == race, + () => client.Country == race, () => orderManager.IssueOrder(Order.Command("race "+race))); item.GetWidget("LABEL").GetText = () => CountryNames[race]; var flag = item.GetWidget("FLAG"); @@ -270,13 +267,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic return true; } - bool ShowTeamDropDown(DropDownButtonWidget dropdown, Session.Slot slot) + bool ShowTeamDropDown(DropDownButtonWidget dropdown, Session.Client client) { - var c = GetClientInSlot(slot); Func setupItem = (ii, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, - () => c.Team == ii, + () => client.Team == ii, () => orderManager.IssueOrder(Order.Command("team "+ii))); item.GetWidget("LABEL").GetText = () => ii == 0 ? "-" : ii.ToString(); return item; @@ -287,11 +283,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic return true; } - bool ShowColorDropDown(Session.Slot s, DropDownButtonWidget color) + bool ShowColorDropDown(DropDownButtonWidget color, Session.Client client) { - if (Map.Players[s.MapPlayer].LockColor) - return false; - var colorChooser = Game.modData.WidgetLoader.LoadWidget( new WidgetArgs() { {"worldRenderer", worldRenderer} }, null, "COLOR_CHOOSER" ); var hueSlider = colorChooser.GetWidget("HUE_SLIDER"); hueSlider.SetOffset(orderManager.LocalClient.ColorRamp.H / 255f); @@ -328,50 +321,32 @@ namespace OpenRA.Mods.RA.Widgets.Logic // Todo: handle this nicer Players.RemoveChildren(); - foreach (var slot in orderManager.LobbyInfo.Slots) + foreach (var kv in orderManager.LobbyInfo.Slots) { - var s = slot; - var c = GetClientInSlot(s); + var s = kv.Value; + var c = orderManager.LobbyInfo.ClientInSlot(kv.Key); Widget template; if (c == null) { if (Game.IsHost) { - if (slot.Spectator) - { - template = EmptySlotTemplateHost.Clone(); - var name = template.GetWidget("NAME"); - name.GetText = () => s.Closed ? "Closed" : "Open"; - name.OnMouseDown = _ => ShowSlotDropDown(name, s, false); - var btn = template.GetWidget("JOIN"); - btn.GetText = () => "Spectate in this slot"; - } - else - { - template = EmptySlotTemplateHost.Clone(); - var name = template.GetWidget("NAME"); - name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; - name.OnMouseDown = _ => ShowSlotDropDown(name, s, Map.Players[ s.MapPlayer ].AllowBots); - } + template = EmptySlotTemplateHost.Clone(); + var name = template.GetWidget("NAME"); + name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; + name.OnMouseDown = _ => ShowSlotDropDown(name, s); } else { template = EmptySlotTemplate.Clone(); var name = template.GetWidget("NAME"); name.GetText = () => s.Closed ? "Closed" : (s.Bot == null) ? "Open" : s.Bot; - - if (slot.Spectator) - { - var btn = template.GetWidget("JOIN"); - btn.GetText = () => "Spectate in this slot"; - } } var join = template.GetWidget("JOIN"); if (join != null) { - join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + s.Index)); return true; }; + join.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("slot " + s.PlayerReference)); return true; }; join.IsVisible = () => !s.Closed && s.Bot == null && orderManager.LocalClient.State != Session.ClientState.Ready; } @@ -402,13 +377,15 @@ namespace OpenRA.Mods.RA.Widgets.Logic name.OnLoseFocus = () => name.OnEnterKey(); var color = template.GetWidget("COLOR"); - color.OnMouseUp = _ => ShowColorDropDown(s, color); - + color.IsDisabled = () => s.LockColor; + color.OnMouseDown = _ => { if (s.LockColor) return true; return ShowColorDropDown(color, c); }; + var colorBlock = color.GetWidget("COLORBLOCK"); colorBlock.GetColor = () => c.ColorRamp.GetColor(0); var faction = template.GetWidget("FACTION"); - faction.OnMouseDown = _ => ShowRaceDropDown(faction, s); + faction.IsDisabled = () => s.LockRace; + faction.OnMouseDown = _ => { if (s.LockRace) return true; return ShowRaceDropDown(faction, c); }; var factionname = faction.GetWidget("FACTIONNAME"); factionname.GetText = () => CountryNames[c.Country]; @@ -417,7 +394,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic factionflag.GetImageCollection = () => "flags"; var team = template.GetWidget("TEAM"); - team.OnMouseDown = _ => ShowTeamDropDown(team, s); + team.IsDisabled = () => s.LockTeam; + team.OnMouseDown = _ => { if (s.LockTeam) return true; return ShowTeamDropDown(team, c); }; team.GetText = () => (c.Team == 0) ? "-" : c.Team.ToString(); var status = template.GetWidget("STATUS"); @@ -426,14 +404,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic var spectator = template.GetWidget("SPECTATOR"); - Session.Slot slot1 = slot; - color.IsVisible = () => !slot1.Spectator; - colorBlock.IsVisible = () => !slot1.Spectator; - faction.IsVisible = () => !slot1.Spectator; - factionname.IsVisible = () => !slot1.Spectator; - factionflag.IsVisible = () => !slot1.Spectator; - team.IsVisible = () => !slot1.Spectator; - spectator.IsVisible = () => slot1.Spectator || slot1.Bot != null; + Session.Slot ss = s; + spectator.IsVisible = () => ss.Bot != null; } else { @@ -459,13 +431,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic var spectator = template.GetWidget("SPECTATOR"); - Session.Slot slot1 = slot; - color.IsVisible = () => !slot1.Spectator; - faction.IsVisible = () => !slot1.Spectator; - factionname.IsVisible = () => !slot1.Spectator; - factionflag.IsVisible = () => !slot1.Spectator; - team.IsVisible = () => !slot1.Spectator; - spectator.IsVisible = () => slot1.Spectator || slot1.Bot != null; + Session.Slot ss = s; + spectator.IsVisible = () => ss.Bot != null; var kickButton = template.GetWidget("KICK"); kickButton.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index; @@ -476,10 +443,82 @@ namespace OpenRA.Mods.RA.Widgets.Logic }; } - template.Id = "SLOT_{0}".F(s.Index); template.IsVisible = () => true; Players.AddChild(template); - } + } + + // Add spectators + foreach (var client in orderManager.LobbyInfo.Clients.Where(client => client.Slot == null)) + { + Widget template; + // Editable spectator + if (client.Index == orderManager.LocalClient.Index && client.State != Session.ClientState.Ready) + { + template = LocalSpectatorTemplate.Clone(); + var name = template.GetWidget("NAME"); + 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) + return true; + + orderManager.IssueOrder(Order.Command("name " + name.Text)); + Game.Settings.Player.Name = name.Text; + Game.Settings.Save(); + return true; + }; + name.OnLoseFocus = () => name.OnEnterKey(); + + var color = template.GetWidget("COLOR"); + color.OnMouseDown = _ => ShowColorDropDown(color, client); + + var colorBlock = color.GetWidget("COLORBLOCK"); + colorBlock.GetColor = () => client.ColorRamp.GetColor(0); + + var status = template.GetWidget("STATUS"); + status.IsChecked = () => client.State == Session.ClientState.Ready; + status.OnClick += CycleReady; + } + // Non-editable spectator + else + { + template = RemoteSpectatorTemplate.Clone(); + template.GetWidget("NAME").GetText = () => client.Name; + var color = template.GetWidget("COLOR"); + color.GetColor = () => client.ColorRamp.GetColor(0); + + var status = template.GetWidget("STATUS"); + status.IsChecked = () => client.State == Session.ClientState.Ready; + if (client.Index == orderManager.LocalClient.Index) + status.OnClick += CycleReady; + + var kickButton = template.GetWidget("KICK"); + kickButton.IsVisible = () => Game.IsHost && client.Index != orderManager.LocalClient.Index; + kickButton.OnMouseUp = mi => + { + orderManager.IssueOrder(Order.Command("kick " + client.Index)); + return true; + }; + } + + template.IsVisible = () => true; + Players.AddChild(template); + } + + // Spectate button + if (orderManager.LocalClient.Slot != null && orderManager.LocalClient.State != Session.ClientState.Ready) + { + var spec = NewSpectatorTemplate.Clone(); + var btn = spec.GetWidget("SPECTATE"); + btn.OnMouseUp = _ => { orderManager.IssueOrder(Order.Command("spectate")); return true; }; + spec.IsVisible = () => true; + Players.AddChild(spec); + } } bool SpawnPointAvailable(int index) { return (index == 0) || orderManager.LobbyInfo.Clients.All(c => c.SpawnPoint != index); } diff --git a/mods/cnc/chrome/lobby.yaml b/mods/cnc/chrome/lobby.yaml index 13dd3f4a22..68051d7783 100644 --- a/mods/cnc/chrome/lobby.yaml +++ b/mods/cnc/chrome/lobby.yaml @@ -113,15 +113,6 @@ Container@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 @@ -185,17 +176,112 @@ Container@SERVER_LOBBY: Y:2 Width:20 Height:20 - Label@SPECTATOR: - Id:SPECTATOR - Text:Spectator + Container@TEMPLATE_EMPTY: + Id:TEMPLATE_EMPTY + 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 + Button@JOIN: + Id:JOIN + Text:Play in this slot + Font:Regular + Width:278 + 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_EMPTY: - Id:TEMPLATE_EMPTY + Container@TEMPLATE_LOCAL_SPECTATOR: + Id:TEMPLATE_LOCAL_SPECTATOR + X:5 + Y:0 + Width:475 + Height:25 + Visible:false + Children: + TextField@NAME: + Id:NAME + Text:Name + Width:150 + Height:25 + MaxLength:16 + DropDownButton@COLOR: + Id:COLOR + Width:80 + Height:25 + X:160 + Font:Regular + Children: + ColorBlock@COLORBLOCK: + Id:COLORBLOCK + X:5 + Y:6 + Width:PARENT_RIGHT-35 + Height:PARENT_BOTTOM-12 + Label@SPECTATOR: + Text:Spectator + Width:198 + Height:25 + X:240 + Y:0 + Align:Center + Font:Bold + Checkbox@STATUS: + Id:STATUS + X:448 + Y:2 + Width:20 + Height:20 + Container@TEMPLATE_REMOTE_SPECTATOR: + Id:TEMPLATE_REMOTE_SPECTATOR X:5 Y:0 Width:475 @@ -209,54 +295,50 @@ Container@SERVER_LOBBY: Height:25 X:5 Y:0-1 - Button@JOIN: - Id:JOIN - Text:Play in this slot - Font:Regular - Width:278 + Button@KICK: + Id:KICK + Text:X + Width:25 + Height:23 + X:125 + Y:2 + Font:Bold + ColorBlock@COLOR: + Id:COLOR + X:165 + Y:6 + Width:45 + Height:13 + Label@SPECTATOR: + Text:Spectator + Width:198 Height:25 - X:160 - Y:0 - Label@BOT: - Id:BOT - Text:Bot - Width:278 - Height:25 - X:160 + X:240 Y:0 Align:Center Font:Bold - Container@TEMPLATE_EMPTY_HOST: - Id:TEMPLATE_EMPTY_HOST + Checkbox@STATUS: + Id:STATUS + X:448 + Y:2 + Width:20 + Height:20 + Container@TEMPLATE_NEW_SPECTATOR: + Id:TEMPLATE_NEW_SPECTATOR X:5 Y:0 - Width:400 + Width:475 Height:25 Visible:false Children: - DropDownButton@NAME: - Id:NAME - Text:Name - Width:150 - Height:25 - Font:Regular - Button@JOIN: - Id:JOIN - Text:Play in this slot + Button@SPECTATE: + Id:SPECTATE + Text:Spectate Font:Regular Width:278 Height:25 X:160 Y:0 - Label@BOT: - Id:BOT - Text:Bot - Width:278 - Height:25 - X:160 - Y:0 - Align:Center - Font:Bold Container@LABEL_CONTAINER: X:25 Y:5 diff --git a/mods/ra/chrome/gamelobby.yaml b/mods/ra/chrome/gamelobby.yaml index d1581d08d2..44bbb0416b 100644 --- a/mods/ra/chrome/gamelobby.yaml +++ b/mods/ra/chrome/gamelobby.yaml @@ -241,6 +241,106 @@ Background@SERVER_LOBBY: Y:0 Align:Center Font:Bold + Container@TEMPLATE_LOCAL_SPECTATOR: + Id:TEMPLATE_LOCAL_SPECTATOR + X:5 + Y:0 + Width:475 + Height:25 + Visible:false + Children: + TextField@NAME: + Id:NAME + Text:Name + Width:150 + Height:25 + MaxLength:16 + DropDownButton@COLOR: + Id:COLOR + Width:80 + Height:25 + X:160 + Font:Regular + Children: + ColorBlock@COLORBLOCK: + Id:COLORBLOCK + X:5 + Y:6 + Width:PARENT_RIGHT-35 + Height:PARENT_BOTTOM-12 + Label@SPECTATOR: + Text:Spectator + Width:198 + Height:25 + X:240 + Y:0 + Align:Center + Font:Bold + Checkbox@STATUS: + Id:STATUS + X:448 + Y:2 + Width:20 + Height:20 + Container@TEMPLATE_REMOTE_SPECTATOR: + Id:TEMPLATE_REMOTE_SPECTATOR + X:5 + Y:0 + Width:475 + Height:25 + Visible:false + Children: + Label@NAME: + Id:NAME + Text:Name + Width:145 + Height:25 + X:5 + Y:0-1 + Button@KICK: + Id:KICK + Text:X + Width:25 + Height:23 + X:125 + Y:2 + Font:Bold + ColorBlock@COLOR: + Id:COLOR + X:165 + Y:6 + Width:45 + Height:13 + Label@SPECTATOR: + Text:Spectator + Width:198 + Height:25 + X:240 + Y:0 + Align:Center + Font:Bold + Checkbox@STATUS: + Id:STATUS + X:448 + Y:2 + Width:20 + Height:20 + Container@TEMPLATE_NEW_SPECTATOR: + Id:TEMPLATE_NEW_SPECTATOR + X:5 + Y:0 + Width:475 + Height:25 + Visible:false + Children: + Button@SPECTATE: + Id:SPECTATE + Text:Spectate + Font:Regular + Width:278 + Height:25 + X:160 + Y:0 Container@LABEL_CONTAINER: X:25 Y:40