diff --git a/AUTHORS b/AUTHORS index 07efee67ae..3cd8f10102 100644 --- a/AUTHORS +++ b/AUTHORS @@ -145,6 +145,7 @@ Also thanks to: * Tirili * Tomas Einarsson (Mesacer) * Tom van Leth (tovl) + * Trevor Nichols (ocdi) * Tristan Keating (Kilkakon) * Tristan Mühlbacher (MicroBit) * UnknownProgrammer diff --git a/OpenRA.Game/GameInformation.cs b/OpenRA.Game/GameInformation.cs index 5ee57ec54b..57765eb28a 100644 --- a/OpenRA.Game/GameInformation.cs +++ b/OpenRA.Game/GameInformation.cs @@ -35,6 +35,7 @@ namespace OpenRA /// Gets the game's duration, from the time the game started until the replay recording stopped. public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } } public IList Players { get; private set; } + public List DisabledSpawnPoints = new List(); public MapPreview MapPreview { get { return Game.ModData.MapCache[MapUid]; } } public IEnumerable HumanPlayers { get { return Players.Where(p => p.IsHuman); } } public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } } diff --git a/OpenRA.Game/Network/GameServer.cs b/OpenRA.Game/Network/GameServer.cs index e442d1e60c..4468beaf38 100644 --- a/OpenRA.Game/Network/GameServer.cs +++ b/OpenRA.Game/Network/GameServer.cs @@ -56,7 +56,7 @@ namespace OpenRA.Network "Mod", "Version", "ModTitle", "ModWebsite", "ModIcon32", // Current server state - "Map", "State", "MaxPlayers", "Protected", "Authentication" + "Map", "State", "MaxPlayers", "Protected", "Authentication", "DisabledSpawnPoints" }; public const int ProtocolVersion = 2; @@ -132,6 +132,9 @@ namespace OpenRA.Network [FieldLoader.LoadUsing("LoadClients")] public readonly GameClient[] Clients; + /// The list of spawnpoints that are disabled for this game + public readonly int[] DisabledSpawnPoints = { }; + public string ModLabel { get { return "{0} ({1})".F(ModTitle, Version); } } static object LoadClients(MiniYaml yaml) @@ -226,6 +229,7 @@ namespace OpenRA.Network Protected = !string.IsNullOrEmpty(server.Settings.Password); Authentication = server.Settings.RequireAuthentication || server.Settings.ProfileIDWhitelist.Any(); Clients = server.LobbyInfo.Clients.Select(c => new GameClient(c)).ToArray(); + DisabledSpawnPoints = server.LobbyInfo.DisabledSpawnPoints?.ToArray() ?? Array.Empty(); } public string ToPOSTData(bool lanGame) diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 0db45b31dc..9e439e9328 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -26,6 +26,8 @@ namespace OpenRA.Network // Keyed by the PlayerReference id that the slot corresponds to public Dictionary Slots = new Dictionary(); + public List DisabledSpawnPoints = new List(); + public Global GlobalSettings = new Global(); public static string AnonymizeIP(IPAddress ip) @@ -69,6 +71,9 @@ namespace OpenRA.Network var s = Slot.Deserialize(node.Value); session.Slots.Add(s.PlayerReference, s); break; + case "DisabledSpawnPoints": + session.DisabledSpawnPoints = FieldLoader.GetValue>("DisabledSpawnPoints", node.Value.Value); + break; } } @@ -267,7 +272,10 @@ namespace OpenRA.Network public string Serialize() { - var sessionData = new List(); + var sessionData = new List() + { + new MiniYamlNode("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints)) + }; foreach (var client in Clients) sessionData.Add(client.Serialize()); diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 1e0255ba68..eb6f01e12a 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -321,6 +321,8 @@ namespace OpenRA foreach (var player in Players) gameInfo.AddPlayer(player, OrderManager.LobbyInfo); + gameInfo.DisabledSpawnPoints = OrderManager.LobbyInfo.DisabledSpawnPoints; + var echo = OrderManager.Connection as EchoConnection; var rc = echo != null ? echo.Recorder : null; diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index 4f8a1a9638..afdda0b8f2 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -43,6 +43,7 @@ namespace OpenRA.Mods.Common.Server { "faction", Faction }, { "team", Team }, { "spawn", Spawn }, + { "clear_spawn", ClearPlayerSpawn }, { "color", PlayerColor }, { "sync_lobby", SyncLobby } }; @@ -123,6 +124,11 @@ namespace OpenRA.Mods.Common.Server if (server.LobbyInfo.Slots.Any(sl => sl.Value.Required && server.LobbyInfo.ClientInSlot(sl.Key) == null)) return; + // Can't have insufficient spawns + var availableSpawnPointCount = server.Map.SpawnPoints.Length - server.LobbyInfo.DisabledSpawnPoints.Count; + if (availableSpawnPointCount < server.LobbyInfo.Clients.Count(c => !c.IsObserver)) + return; + server.StartGame(); } } @@ -170,6 +176,13 @@ namespace OpenRA.Mods.Common.Server return true; } + var availableSpawnPointCount = server.Map.SpawnPoints.Length - server.LobbyInfo.DisabledSpawnPoints.Count; + if (availableSpawnPointCount < server.LobbyInfo.Clients.Count(c => !c.IsObserver)) + { + server.SendOrderTo(conn, "Message", "Unable to start the game until more spawn points are enabled."); + return true; + } + server.StartGame(); return true; @@ -473,6 +486,8 @@ namespace OpenRA.Mods.Common.Server if (c.Slot != null && !server.LobbyInfo.Slots[c.Slot].LockColor) c.Color = c.PreferredColor = SanitizePlayerColor(server, c.Color, c.Index, conn); + server.LobbyInfo.DisabledSpawnPoints.Clear(); + server.SyncLobbyInfo(); server.SendMessage("{0} changed the map to {1}.".F(client.Name, server.Map.Title)); @@ -811,6 +826,45 @@ namespace OpenRA.Mods.Common.Server } } + static bool ClearPlayerSpawn(S server, Connection conn, Session.Client client, string s) + { + var spawnPoint = Exts.ParseIntegerInvariant(s); + if (spawnPoint == 0) + return true; + + var existingClient = server.LobbyInfo.Clients.FirstOrDefault(cc => cc.SpawnPoint == spawnPoint); + if (client != existingClient && !client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only admins can clear spawn points."); + return true; + } + + // Clearing a selected spawn point removes the player + if (existingClient != null) + { + // Prevent a map-defined lock spawn from being affected + if (existingClient.Slot != null && server.LobbyInfo.Slots[existingClient.Slot].LockSpawn) + return true; + + existingClient.SpawnPoint = 0; + if (existingClient.State == Session.ClientState.Ready) + existingClient.State = Session.ClientState.NotReady; + + server.SyncLobbyClients(); + return true; + } + + // Clearing an empty spawn point prevents it from being selected + // Clearing a disabled spawn restores it for use + if (!server.LobbyInfo.DisabledSpawnPoints.Contains(spawnPoint)) + server.LobbyInfo.DisabledSpawnPoints.Add(spawnPoint); + else + server.LobbyInfo.DisabledSpawnPoints.Remove(spawnPoint); + + server.SyncLobbyInfo(); + return true; + } + static bool Spawn(S server, Connection conn, Session.Client client, string s) { lock (server.LobbyInfo) diff --git a/OpenRA.Mods.Common/Traits/World/MPStartLocations.cs b/OpenRA.Mods.Common/Traits/World/MPStartLocations.cs index a91e1aeaf8..7044ee3aed 100644 --- a/OpenRA.Mods.Common/Traits/World/MPStartLocations.cs +++ b/OpenRA.Mods.Common/Traits/World/MPStartLocations.cs @@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.Traits // Initialize the list of unoccupied spawn points for AssignSpawnLocations to pick from state.SpawnLocations = map.SpawnPoints; - state.AvailableSpawnPoints = Enumerable.Range(1, map.SpawnPoints.Length).ToList(); + state.AvailableSpawnPoints = Enumerable.Range(1, map.SpawnPoints.Length).Except(lobbyInfo.DisabledSpawnPoints).ToList(); foreach (var kv in lobbyInfo.Slots) { var client = lobbyInfo.ClientInSlot(kv.Key); @@ -130,7 +130,7 @@ namespace OpenRA.Mods.Common.Traits spawnLocations = spawns.ToArray(); // Initialize the list of unoccupied spawn points for AssignSpawnLocations to pick from - availableSpawnPoints = Enumerable.Range(1, spawnLocations.Length).ToList(); + availableSpawnPoints = Enumerable.Range(1, spawnLocations.Length).Except(self.World.LobbyInfo.DisabledSpawnPoints).ToList(); foreach (var kv in self.World.LobbyInfo.Slots) { var client = self.World.LobbyInfo.ClientInSlot(kv.Key); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs index da9757196a..bba9bdc2c8 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs @@ -60,6 +60,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic MapPreview map; bool addBotOnMapLoad; bool disableTeamChat; + bool insufficientPlayerSpawns; bool teamChat; bool updateDiscordStatus = true; Dictionary spawnOccupants = new Dictionary(); @@ -136,6 +137,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic LobbyUtils.SelectSpawnPoint(orderManager, preview, mapPreview, mi)) }, { "getSpawnOccupants", (Func>)(() => spawnOccupants) }, + { "getDisabledSpawnPoints", (Func>)(() => orderManager.LobbyInfo.DisabledSpawnPoints) }, { "showUnoccupiedSpawnpoints", true }, }); @@ -367,7 +369,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic { startGameButton.IsDisabled = () => configurationDisabled() || map.Status != MapStatus.Available || orderManager.LobbyInfo.Slots.Any(sl => sl.Value.Required && orderManager.LobbyInfo.ClientInSlot(sl.Key) == null) || - (!orderManager.LobbyInfo.GlobalSettings.EnableSingleplayer && orderManager.LobbyInfo.NonBotPlayers.Count() < 2); + (!orderManager.LobbyInfo.GlobalSettings.EnableSingleplayer && orderManager.LobbyInfo.NonBotPlayers.Count() < 2) || + insufficientPlayerSpawns; startGameButton.OnClick = () => { @@ -570,6 +573,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic c.Bot == null && c.Team == orderManager.LocalClient.Team); + var availableSpawnPointCount = map.SpawnPoints.Length - orderManager.LobbyInfo.DisabledSpawnPoints.Count; + insufficientPlayerSpawns = availableSpawnPointCount < orderManager.LobbyInfo.Clients.Count(c => !c.IsObserver); + if (disableTeamChat) teamChat = false; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs index d3b004415b..4bba88bc68 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyUtils.cs @@ -229,29 +229,48 @@ namespace OpenRA.Mods.Common.Widgets.Logic public static void SelectSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, MapPreview preview, MouseInput mi) { - if (mi.Button != MouseButton.Left) + if (orderManager.LocalClient.State == Session.ClientState.Ready) return; - if (!orderManager.LocalClient.IsObserver && orderManager.LocalClient.State == Session.ClientState.Ready) - return; + if (mi.Button == MouseButton.Left) + SelectPlayerSpawnPoint(orderManager, mapPreview, preview, mi); - var spawnSize = ChromeProvider.GetImage("lobby-bits", "spawn-unclaimed").Size.XY; - var selectedSpawn = preview.SpawnPoints - .Select((sp, i) => (SpawnLocation: mapPreview.ConvertToPreview(sp, preview.GridType), Index: i)) - .Where(a => ((a.SpawnLocation - mi.Location).ToFloat2() / spawnSize * 2).LengthSquared <= 1) - .Select(a => a.Index + 1) - .FirstOrDefault(); + if (mi.Button == MouseButton.Right) + ClearPlayerSpawnPoint(orderManager, mapPreview, preview, mi); + } + + static void SelectPlayerSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, MapPreview preview, MouseInput mi) + { + var selectedSpawn = DetermineSelectedSpawnPoint(mapPreview, preview, mi); var locals = orderManager.LobbyInfo.Clients.Where(c => c.Index == orderManager.LocalClient.Index || (Game.IsHost && c.Bot != null)); var playerToMove = locals.FirstOrDefault(c => ((selectedSpawn == 0) ^ (c.SpawnPoint == 0) && !c.IsObserver)); SetSpawnPoint(orderManager, playerToMove, selectedSpawn); } - private static void SetSpawnPoint(OrderManager orderManager, Session.Client playerToMove, int selectedSpawn) + static void ClearPlayerSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, MapPreview preview, MouseInput mi) { - var owned = orderManager.LobbyInfo.Clients.Any(c => c.SpawnPoint == selectedSpawn); - if (selectedSpawn == 0 || !owned) - orderManager.IssueOrder(Order.Command("spawn {0} {1}".F((playerToMove ?? orderManager.LocalClient).Index, selectedSpawn))); + var selectedSpawn = DetermineSelectedSpawnPoint(mapPreview, preview, mi); + if (Game.IsHost || orderManager.LobbyInfo.Clients.FirstOrDefault(cc => cc.SpawnPoint == selectedSpawn) == orderManager.LocalClient) + orderManager.IssueOrder(Order.Command("clear_spawn {0}".F(selectedSpawn))); + } + + static int DetermineSelectedSpawnPoint(MapPreviewWidget mapPreview, MapPreview preview, MouseInput mi) + { + var spawnSize = ChromeProvider.GetImage("lobby-bits", "spawn-unclaimed").Size.XY; + var selectedSpawn = preview.SpawnPoints + .Select((sp, i) => (SpawnLocation: mapPreview.ConvertToPreview(sp, preview.GridType), Index: i)) + .Where(a => ((a.SpawnLocation - mi.Location).ToFloat2() / spawnSize * 2).LengthSquared <= 1) + .Select(a => a.Index + 1) + .FirstOrDefault(); + return selectedSpawn; + } + + static void SetSpawnPoint(OrderManager orderManager, Session.Client playerToMove, int selectedSpawnPoint) + { + var owned = orderManager.LobbyInfo.Clients.Any(c => c.SpawnPoint == selectedSpawnPoint) || orderManager.LobbyInfo.DisabledSpawnPoints.Contains(selectedSpawnPoint); + if (selectedSpawnPoint == 0 || !owned) + orderManager.IssueOrder(Order.Command("spawn {0} {1}".F((playerToMove ?? orderManager.LocalClient).Index, selectedSpawnPoint))); } public static Color LatencyColor(Session.ClientPing ping) @@ -537,7 +556,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic { var spawnPoints = Enumerable.Range(0, map.SpawnPoints.Length + 1).Except( orderManager.LobbyInfo.Clients.Where( - client => client != c && client.SpawnPoint != 0).Select(client => client.SpawnPoint)); + client => client != c && client.SpawnPoint != 0).Select(client => client.SpawnPoint)) + .Except(orderManager.LobbyInfo.DisabledSpawnPoints); ShowSpawnDropDown(dropdown, c, orderManager, spawnPoints); }; dropdown.GetText = () => (c.SpawnPoint == 0) ? "-" : Convert.ToChar('A' - 1 + c.SpawnPoint).ToString(); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/MapPreviewLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/MapPreviewLogic.cs index 06e0ec1b1c..346d719af0 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/MapPreviewLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/MapPreviewLogic.cs @@ -24,8 +24,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic int blinkTick; [ObjectCreator.UseCtor] - internal MapPreviewLogic(Widget widget, ModData modData, OrderManager orderManager, Func getMap, - Action onMouseDown, Func> getSpawnOccupants, bool showUnoccupiedSpawnpoints) + internal MapPreviewLogic(Widget widget, ModData modData, OrderManager orderManager, Func getMap, Action onMouseDown, + Func> getSpawnOccupants, Func> getDisabledSpawnPoints, bool showUnoccupiedSpawnpoints) { var mapRepository = modData.Manifest.Get().MapRepository; @@ -38,7 +38,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic return map.Status == MapStatus.Available && (!map.RulesLoaded || !map.InvalidCustomRules); }; - SetupWidgets(available, getMap, onMouseDown, getSpawnOccupants, showUnoccupiedSpawnpoints); + SetupWidgets(available, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints); } var invalid = widget.GetOrNull("MAP_INVALID"); @@ -50,14 +50,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic return map.Status == MapStatus.Available && map.InvalidCustomRules; }; - SetupWidgets(invalid, getMap, onMouseDown, getSpawnOccupants, showUnoccupiedSpawnpoints); + SetupWidgets(invalid, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints); } var download = widget.GetOrNull("MAP_DOWNLOADABLE"); if (download != null) { download.IsVisible = () => getMap().Status == MapStatus.DownloadAvailable; - SetupWidgets(download, getMap, onMouseDown, getSpawnOccupants, showUnoccupiedSpawnpoints); + SetupWidgets(download, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints); var install = download.GetOrNull("MAP_INSTALL"); if (install != null) @@ -86,7 +86,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic return map.Status != MapStatus.Available && map.Status != MapStatus.DownloadAvailable; }; - SetupWidgets(progress, getMap, onMouseDown, getSpawnOccupants, showUnoccupiedSpawnpoints); + SetupWidgets(progress, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints); var statusSearching = progress.GetOrNull("MAP_STATUS_SEARCHING"); if (statusSearching != null) @@ -172,12 +172,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic } void SetupWidgets(Widget parent, Func getMap, - Action onMouseDown, Func> getSpawnOccupants, bool showUnoccupiedSpawnpoints) + Action onMouseDown, Func> getSpawnOccupants, Func> getDisabledSpawnPoints, bool showUnoccupiedSpawnpoints) { var preview = parent.Get("MAP_PREVIEW"); preview.Preview = () => getMap(); preview.OnMouseDown = mi => onMouseDown(preview, getMap(), mi); preview.SpawnOccupants = getSpawnOccupants; + preview.DisabledSpawnPoints = getDisabledSpawnPoints; preview.ShowUnoccupiedSpawnpoints = showUnoccupiedSpawnpoints; var titleLabel = parent.GetOrNull("MAP_TITLE"); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/SpawnSelectorTooltipLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/SpawnSelectorTooltipLogic.cs index f9f17c8e98..2114898d80 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/SpawnSelectorTooltipLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/SpawnSelectorTooltipLogic.cs @@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic return; } - labelText = "Available spawn"; + labelText = preview.DisabledSpawnPoints().Contains(preview.TooltipSpawnIndex) ? "Disabled spawn" : "Available spawn"; playerFaction = null; playerTeam = 0; widget.Bounds.Height = singleHeight; diff --git a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs index 94f0949369..65ef6d4b9d 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs @@ -87,12 +87,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic return occupants; }); + var noSpawns = new List(); + var disabledSpawnPoints = new CachedTransform>(r => r.GameInfo.DisabledSpawnPoints ?? noSpawns); + Ui.LoadWidget("MAP_PREVIEW", mapPreviewRoot, new WidgetArgs { { "orderManager", null }, { "getMap", (Func)(() => map) }, { "onMouseDown", (Action)((preview, mapPreview, mi) => { }) }, { "getSpawnOccupants", (Func>)(() => spawnOccupants.Update(selectedReplay)) }, + { "getDisabledSpawnPoints", (Func>)(() => disabledSpawnPoints.Update(selectedReplay)) }, { "showUnoccupiedSpawnpoints", false }, }); diff --git a/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs index 369bbae2d0..8eb1aef40b 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs @@ -427,6 +427,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic .ToDictionary(c => c.SpawnPoint, c => new SpawnOccupant(c, server.Mod != modData.Manifest.Id)); mapPreview.SpawnOccupants = () => occupants; + mapPreview.DisabledSpawnPoints = () => server.DisabledSpawnPoints; } if (server == null || !server.Clients.Any()) diff --git a/OpenRA.Mods.Common/Widgets/MapPreviewWidget.cs b/OpenRA.Mods.Common/Widgets/MapPreviewWidget.cs index b44ff03cc2..ca2887f93f 100644 --- a/OpenRA.Mods.Common/Widgets/MapPreviewWidget.cs +++ b/OpenRA.Mods.Common/Widgets/MapPreviewWidget.cs @@ -57,6 +57,8 @@ namespace OpenRA.Mods.Common.Widgets public class MapPreviewWidget : Widget { + static readonly int[] NoDisabledSpawnPoints = Array.Empty(); + public readonly bool IgnoreMouseInput = false; public readonly bool ShowSpawnPoints = true; @@ -64,13 +66,14 @@ namespace OpenRA.Mods.Common.Widgets public readonly string TooltipTemplate = "SPAWN_TOOLTIP"; readonly Lazy tooltipContainer; - readonly Sprite spawnClaimed, spawnUnclaimed; + readonly Sprite spawnClaimed, spawnUnclaimed, spawnDisabled; readonly SpriteFont spawnFont; readonly Color spawnColor, spawnContrastColor; readonly int2 spawnLabelOffset; public Func Preview = () => null; public Func> SpawnOccupants = () => new Dictionary(); + public Func> DisabledSpawnPoints = () => NoDisabledSpawnPoints; public Action OnMouseDown = _ => { }; public int TooltipSpawnIndex = -1; public bool ShowUnoccupiedSpawnpoints = true; @@ -85,6 +88,7 @@ namespace OpenRA.Mods.Common.Widgets spawnClaimed = ChromeProvider.GetImage("lobby-bits", "spawn-claimed"); spawnUnclaimed = ChromeProvider.GetImage("lobby-bits", "spawn-unclaimed"); + spawnDisabled = ChromeProvider.GetImage("lobby-bits", "spawn-disabled") ?? spawnUnclaimed; spawnFont = Game.Renderer.Fonts[ChromeMetrics.Get("SpawnFont")]; spawnColor = ChromeMetrics.Get("SpawnColor"); spawnContrastColor = ChromeMetrics.Get("SpawnContrastColor"); @@ -184,6 +188,7 @@ namespace OpenRA.Mods.Common.Widgets { var spawnPoints = preview.SpawnPoints; var occupants = SpawnOccupants(); + var disabledSpawnPoints = DisabledSpawnPoints(); var gridType = preview.GridType; for (var i = 0; i < spawnPoints.Length; i++) { @@ -191,21 +196,30 @@ namespace OpenRA.Mods.Common.Widgets // Spawn numbers are 1 indexed with 0 meaning "random spawn". var occupied = occupants.TryGetValue(i + 1, out var occupant); + var disabled = disabledSpawnPoints.Contains(i + 1); var pos = ConvertToPreview(p, gridType); - var sprite = occupied ? spawnClaimed : spawnUnclaimed; + + var sprite = disabled ? spawnDisabled : occupied ? spawnClaimed : spawnUnclaimed; var offset = sprite.Size.XY.ToInt2() / 2; + if (((pos - Viewport.LastMousePos).ToFloat2() / offset.ToFloat2()).LengthSquared <= 1) + TooltipSpawnIndex = spawnPoints.IndexOf(p) + 1; + + if (disabled) + { + Game.Renderer.RgbaSpriteRenderer.DrawSprite(spawnDisabled, pos - offset); + continue; + } + if (occupied) WidgetUtils.FillEllipseWithColor(new Rectangle(pos.X - offset.X + 1, pos.Y - offset.Y + 1, (int)sprite.Size.X - 2, (int)sprite.Size.Y - 2), occupant.Color); Game.Renderer.RgbaSpriteRenderer.DrawSprite(sprite, pos - offset); + var number = Convert.ToChar('A' + spawnPoints.IndexOf(p)).ToString(); var textOffset = spawnFont.Measure(number) / 2 + spawnLabelOffset; spawnFont.DrawTextWithContrast(number, pos - textOffset, spawnColor, spawnContrastColor, 1); - - if (((pos - Viewport.LastMousePos).ToFloat2() / offset.ToFloat2()).LengthSquared <= 1) - TooltipSpawnIndex = spawnPoints.IndexOf(p) + 1; } } } diff --git a/mods/cnc/chrome.yaml b/mods/cnc/chrome.yaml index 9bfaacc1ee..81c81f6cac 100644 --- a/mods/cnc/chrome.yaml +++ b/mods/cnc/chrome.yaml @@ -306,8 +306,9 @@ music: lobby-bits: Inherits: ^Chrome Regions: - spawn-unclaimed: 777, 423, 19, 19 - spawn-claimed: 777, 443, 19, 19 + spawn-claimed: 744, 215, 18, 18 + spawn-unclaimed: 744, 234, 18, 18 + spawn-disabled: 744, 253, 18, 18 admin: 938, 0, 6, 5 colorpicker: 887, 0, 14, 14 huepicker: 904, 0, 7, 15 diff --git a/mods/cnc/uibits/chrome-2x.png b/mods/cnc/uibits/chrome-2x.png index 0963ef7b21..7c49f0c5b9 100644 Binary files a/mods/cnc/uibits/chrome-2x.png and b/mods/cnc/uibits/chrome-2x.png differ diff --git a/mods/cnc/uibits/chrome-3x.png b/mods/cnc/uibits/chrome-3x.png index 35b269f5b3..2ce438b6da 100644 Binary files a/mods/cnc/uibits/chrome-3x.png and b/mods/cnc/uibits/chrome-3x.png differ diff --git a/mods/cnc/uibits/chrome.png b/mods/cnc/uibits/chrome.png index d9aeedcc6d..97e460b5b8 100644 Binary files a/mods/cnc/uibits/chrome.png and b/mods/cnc/uibits/chrome.png differ diff --git a/mods/d2k/chrome.yaml b/mods/d2k/chrome.yaml index 657a7da9bd..ac8091b071 100644 --- a/mods/d2k/chrome.yaml +++ b/mods/d2k/chrome.yaml @@ -241,10 +241,11 @@ dialog4: lobby-bits: Inherits: ^Glyphs Regions: - spawn-unclaimed: 91, 119, 22, 22 - spawn-claimed: 68, 119, 22, 22 + spawn-claimed: 27, 119, 22, 22 + spawn-unclaimed: 50, 119, 22, 22 + spawn-disabled: 73, 119, 22, 22 admin: 170, 0, 6, 5 - colorpicker: 68, 119, 22, 22 + colorpicker: 27, 119, 22, 22 huepicker: 136, 0, 7, 15 kick: 153, 0, 11, 11 protected: 0, 17, 12, 13 diff --git a/mods/d2k/uibits/glyphs-2x.png b/mods/d2k/uibits/glyphs-2x.png index 57ab80a7c4..44afbc20d9 100644 Binary files a/mods/d2k/uibits/glyphs-2x.png and b/mods/d2k/uibits/glyphs-2x.png differ diff --git a/mods/d2k/uibits/glyphs-3x.png b/mods/d2k/uibits/glyphs-3x.png index ea75d0b8ba..5cf0ea5bb5 100644 Binary files a/mods/d2k/uibits/glyphs-3x.png and b/mods/d2k/uibits/glyphs-3x.png differ diff --git a/mods/d2k/uibits/glyphs.png b/mods/d2k/uibits/glyphs.png index 9d22f2724f..2530ce2579 100644 Binary files a/mods/d2k/uibits/glyphs.png and b/mods/d2k/uibits/glyphs.png differ diff --git a/mods/ra/chrome.yaml b/mods/ra/chrome.yaml index a3a6a191ab..39b475b37f 100644 --- a/mods/ra/chrome.yaml +++ b/mods/ra/chrome.yaml @@ -135,7 +135,7 @@ sidebar-bits: production-tooltip-time: 136, 51, 16, 16 production-tooltip-power: 102, 51, 16, 16 production-tooltip-cost: 68, 51, 16, 16 - indicator-muted: 68, 145, 26, 24 + indicator-muted: 221, 17, 26, 24 commandbar: Inherits: ^Sidebar @@ -326,8 +326,9 @@ dialog5: lobby-bits: Inherits: ^Glyphs Regions: - spawn-unclaimed: 91, 119, 22, 22 spawn-claimed: 68, 119, 22, 22 + spawn-unclaimed: 91, 119, 22, 22 + spawn-disabled: 114, 119, 22, 22 admin: 170, 0, 6, 5 colorpicker: 68, 119, 22, 22 huepicker: 136, 0, 7, 15 @@ -362,9 +363,9 @@ strategic: Inherits: ^Glyphs Regions: unowned: 68, 119, 22, 22 - critical_unowned: 114, 119, 22, 22 - enemy_owned: 137, 119, 22, 22 - player_owned: 183, 119, 22, 22 + critical_unowned: 137, 119, 22, 22 + enemy_owned: 160, 119, 22, 22 + player_owned: 160, 142, 22, 22 flags: Inherits: ^Glyphs diff --git a/mods/ra/uibits/glyphs-2x.png b/mods/ra/uibits/glyphs-2x.png index 30fc3b12be..095928302f 100644 Binary files a/mods/ra/uibits/glyphs-2x.png and b/mods/ra/uibits/glyphs-2x.png differ diff --git a/mods/ra/uibits/glyphs-3x.png b/mods/ra/uibits/glyphs-3x.png index 28d48c67d0..1aa93b2175 100644 Binary files a/mods/ra/uibits/glyphs-3x.png and b/mods/ra/uibits/glyphs-3x.png differ diff --git a/mods/ra/uibits/glyphs.png b/mods/ra/uibits/glyphs.png index 29b7334497..da163f54a0 100644 Binary files a/mods/ra/uibits/glyphs.png and b/mods/ra/uibits/glyphs.png differ diff --git a/mods/ts/chrome.yaml b/mods/ts/chrome.yaml index e06908ddfa..dd33ca3386 100644 --- a/mods/ts/chrome.yaml +++ b/mods/ts/chrome.yaml @@ -458,10 +458,11 @@ flags: lobby-bits: Inherits: ^Glyphs Regions: - spawn-unclaimed: 91, 119, 22, 22 - spawn-claimed: 68, 119, 22, 22 + spawn-claimed: 27, 119, 22, 22 + spawn-unclaimed: 50, 119, 22, 22 + spawn-disabled: 73, 119, 22, 22 admin: 170, 0, 6, 5 - colorpicker: 68, 119, 22, 22 + colorpicker: 27, 119, 22, 22 huepicker: 136, 0, 7, 15 kick: 153, 0, 11, 11 protected: 0, 17, 12, 13 diff --git a/mods/ts/uibits/glyphs-2x.png b/mods/ts/uibits/glyphs-2x.png index dd8d867777..d68a440c7e 100644 Binary files a/mods/ts/uibits/glyphs-2x.png and b/mods/ts/uibits/glyphs-2x.png differ diff --git a/mods/ts/uibits/glyphs-3x.png b/mods/ts/uibits/glyphs-3x.png index 1ecb77456b..f73511e49f 100644 Binary files a/mods/ts/uibits/glyphs-3x.png and b/mods/ts/uibits/glyphs-3x.png differ diff --git a/mods/ts/uibits/glyphs.png b/mods/ts/uibits/glyphs.png index b3cbc79629..1c16c776db 100644 Binary files a/mods/ts/uibits/glyphs.png and b/mods/ts/uibits/glyphs.png differ