From fafd9e81a91eeb8c083019d75f44e4588c9f1a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Sun, 9 Mar 2014 17:47:06 +0100 Subject: [PATCH 01/13] convert to Unix line endings --- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index ad24f1ccf2..729a9cb003 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic readonly Action OnGameStart; readonly Action onExit; - readonly OrderManager orderManager; + readonly OrderManager orderManager; readonly bool skirmishMode; // Listen for connection failures @@ -91,7 +91,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic lobby = widget; this.orderManager = orderManager; this.OnGameStart = () => { CloseWindow(); onStart(); }; - this.onExit = onExit; + this.onExit = onExit; this.skirmishMode = skirmishMode; Game.LobbyInfoChanged += UpdateCurrentMap; From 634567f98e2741d745a03c6d17fd2ba4c1580b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Sun, 9 Mar 2014 19:43:15 +0100 Subject: [PATCH 02/13] don't display headers when player panel is invisible --- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 729a9cb003..d9becb9443 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -114,6 +114,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic EditableSpectatorTemplate = Players.Get("TEMPLATE_EDITABLE_SPECTATOR"); NonEditableSpectatorTemplate = Players.Get("TEMPLATE_NONEDITABLE_SPECTATOR"); NewSpectatorTemplate = Players.Get("TEMPLATE_NEW_SPECTATOR"); + + var playerBinHeaders = lobby.GetOrNull("LABEL_CONTAINER"); + if (playerBinHeaders != null) + playerBinHeaders.IsVisible = () => panel == PanelType.Players; + colorPreview = lobby.Get("COLOR_MANAGER"); colorPreview.Color = Game.Settings.Player.Color; From e4d5404c6cfa858a38eb48b644b886f4269305fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Sun, 9 Mar 2014 20:44:30 +0100 Subject: [PATCH 03/13] StyleCop --- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 165 +++++++++++---------- 1 file changed, 84 insertions(+), 81 deletions(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index d9becb9443..2e80772d6a 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -24,15 +24,17 @@ namespace OpenRA.Mods.RA.Widgets.Logic PanelType panel = PanelType.Players; Widget lobby; - Widget EditablePlayerTemplate, NonEditablePlayerTemplate, EmptySlotTemplate, - EditableSpectatorTemplate, NonEditableSpectatorTemplate, NewSpectatorTemplate; + + Widget editablePlayerTemplate, nonEditablePlayerTemplate, emptySlotTemplate, + editableSpectatorTemplate, nonEditableSpectatorTemplate, newSpectatorTemplate; + ScrollPanelWidget chatPanel; Widget chatTemplate; - ScrollPanelWidget Players; - Dictionary CountryNames; - string MapUid; - Map Map; + ScrollPanelWidget players; + Dictionary countryNames; + string mapUid; + Map map; ColorPreviewManagerWidget colorPreview; @@ -105,54 +107,54 @@ namespace OpenRA.Mods.RA.Widgets.Logic name.GetText = () => orderManager.LobbyInfo.GlobalSettings.ServerName; UpdateCurrentMap(); - Players = Ui.LoadWidget("LOBBY_PLAYER_BIN", lobby.Get("PLAYER_BIN_ROOT"), new WidgetArgs()); - Players.IsVisible = () => panel == PanelType.Players; + players = Ui.LoadWidget("LOBBY_PLAYER_BIN", lobby.Get("PLAYER_BIN_ROOT"), new WidgetArgs()); + players.IsVisible = () => panel == PanelType.Players; - EditablePlayerTemplate = Players.Get("TEMPLATE_EDITABLE_PLAYER"); - NonEditablePlayerTemplate = Players.Get("TEMPLATE_NONEDITABLE_PLAYER"); - EmptySlotTemplate = Players.Get("TEMPLATE_EMPTY"); - EditableSpectatorTemplate = Players.Get("TEMPLATE_EDITABLE_SPECTATOR"); - NonEditableSpectatorTemplate = Players.Get("TEMPLATE_NONEDITABLE_SPECTATOR"); - NewSpectatorTemplate = Players.Get("TEMPLATE_NEW_SPECTATOR"); var playerBinHeaders = lobby.GetOrNull("LABEL_CONTAINER"); if (playerBinHeaders != null) playerBinHeaders.IsVisible = () => panel == PanelType.Players; + editablePlayerTemplate = players.Get("TEMPLATE_EDITABLE_PLAYER"); + nonEditablePlayerTemplate = players.Get("TEMPLATE_NONEDITABLE_PLAYER"); + emptySlotTemplate = players.Get("TEMPLATE_EMPTY"); + editableSpectatorTemplate = players.Get("TEMPLATE_EDITABLE_SPECTATOR"); + nonEditableSpectatorTemplate = players.Get("TEMPLATE_NONEDITABLE_SPECTATOR"); + newSpectatorTemplate = players.Get("TEMPLATE_NEW_SPECTATOR"); colorPreview = lobby.Get("COLOR_MANAGER"); colorPreview.Color = Game.Settings.Player.Color; var mapPreview = lobby.Get("MAP_PREVIEW"); - mapPreview.IsVisible = () => Map != null; - mapPreview.Map = () => Map; - mapPreview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, mapPreview, Map, mi); - mapPreview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, Map); + mapPreview.IsVisible = () => map != null; + mapPreview.Map = () => map; + mapPreview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, mapPreview, map, mi); + mapPreview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, map); var mapTitle = lobby.GetOrNull("MAP_TITLE"); if (mapTitle != null) { - mapTitle.IsVisible = () => Map != null; - mapTitle.GetText = () => Map.Title; + mapTitle.IsVisible = () => map != null; + mapTitle.GetText = () => map.Title; } var mapType = lobby.GetOrNull("MAP_TYPE"); if (mapType != null) { - mapType.IsVisible = () => Map != null; - mapType.GetText = () => Map.Type; + mapType.IsVisible = () => map != null; + mapType.GetText = () => map.Type; } var mapAuthor = lobby.GetOrNull("MAP_AUTHOR"); if (mapAuthor != null) { - mapAuthor.IsVisible = () => Map != null; - mapAuthor.GetText = () => "Created by {0}".F(Map.Author); + mapAuthor.IsVisible = () => map != null; + mapAuthor.GetText = () => "Created by {0}".F(map.Author); } - CountryNames = Rules.Info["world"].Traits.WithInterface() + countryNames = Rules.Info["world"].Traits.WithInterface() .Where(c => c.Selectable) .ToDictionary(a => a.Race, a => a.Name); - CountryNames.Add("random", "Any"); + countryNames.Add("random", "Any"); var gameStarting = false; Func configurationDisabled = () => !Game.IsHost || gameStarting || panel == PanelType.Kick || @@ -173,7 +175,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() { - { "initialMap", Map.Uid }, + { "initialMap", map.Uid }, { "onExit", () => {} }, { "onSelect", onSelect } }); @@ -304,7 +306,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (allowCheats != null) { allowCheats.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllowCheats; - allowCheats.IsDisabled = () => Map.Options.Cheats.HasValue || configurationDisabled(); + allowCheats.IsDisabled = () => map.Options.Cheats.HasValue || configurationDisabled(); allowCheats.OnClick = () => orderManager.IssueOrder(Order.Command( "allowcheats {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllowCheats))); } @@ -313,7 +315,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (crates != null) { crates.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Crates; - crates.IsDisabled = () => Map.Options.Crates.HasValue || configurationDisabled(); + crates.IsDisabled = () => map.Options.Crates.HasValue || configurationDisabled(); crates.OnClick = () => orderManager.IssueOrder(Order.Command( "crates {0}".F(!orderManager.LobbyInfo.GlobalSettings.Crates))); } @@ -322,7 +324,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (allybuildradius != null) { allybuildradius.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllyBuildRadius; - allybuildradius.IsDisabled = () => Map.Options.AllyBuildRadius.HasValue || configurationDisabled(); + allybuildradius.IsDisabled = () => map.Options.AllyBuildRadius.HasValue || configurationDisabled(); allybuildradius.OnClick = () => orderManager.IssueOrder(Order.Command( "allybuildradius {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllyBuildRadius))); } @@ -331,7 +333,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (fragileAlliance != null) { fragileAlliance.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.FragileAlliances; - fragileAlliance.IsDisabled = () => Map.Options.FragileAlliances.HasValue || configurationDisabled(); + fragileAlliance.IsDisabled = () => map.Options.FragileAlliances.HasValue || configurationDisabled(); fragileAlliance.OnClick = () => orderManager.IssueOrder(Order.Command( "fragilealliance {0}".F(!orderManager.LobbyInfo.GlobalSettings.FragileAlliances))); } @@ -339,12 +341,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic var difficulty = optionsBin.GetOrNull("DIFFICULTY_DROPDOWNBUTTON"); if (difficulty != null) { - difficulty.IsVisible = () => Map.Options.Difficulties.Any(); + difficulty.IsVisible = () => map.Options.Difficulties.Any(); difficulty.IsDisabled = configurationDisabled; difficulty.GetText = () => orderManager.LobbyInfo.GlobalSettings.Difficulty; difficulty.OnMouseDown = _ => { - var options = Map.Options.Difficulties.Select(d => new DropDownOption + var options = map.Options.Difficulties.Select(d => new DropDownOption { Title = d, IsSelected = () => orderManager.LobbyInfo.GlobalSettings.Difficulty == d, @@ -376,8 +378,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic var classes = Rules.Info["world"].Traits.WithInterface() .Select(a => a.Class).Distinct(); - startingUnits.IsDisabled = () => !Map.Options.ConfigurableStartingUnits || configurationDisabled(); - startingUnits.GetText = () => !Map.Options.ConfigurableStartingUnits ? "Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass); + startingUnits.IsDisabled = () => !map.Options.ConfigurableStartingUnits || configurationDisabled(); + startingUnits.GetText = () => !map.Options.ConfigurableStartingUnits ? "Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass); startingUnits.OnMouseDown = _ => { var options = classes.Select(c => new DropDownOption @@ -403,8 +405,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic var startingCash = optionsBin.GetOrNull("STARTINGCASH_DROPDOWNBUTTON"); if (startingCash != null) { - startingCash.IsDisabled = () => Map.Options.StartingCash.HasValue || configurationDisabled(); - startingCash.GetText = () => Map.Options.StartingCash.HasValue ? "Not Available" : "${0}".F(orderManager.LobbyInfo.GlobalSettings.StartingCash); + startingCash.IsDisabled = () => map.Options.StartingCash.HasValue || configurationDisabled(); + startingCash.GetText = () => map.Options.StartingCash.HasValue ? "Not Available" : "${0}".F(orderManager.LobbyInfo.GlobalSettings.StartingCash); startingCash.OnMouseDown = _ => { var options = Rules.Info["player"].Traits.Get().SelectableCash.Select(c => new DropDownOption @@ -429,7 +431,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (enableShroud != null) { enableShroud.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Shroud; - enableShroud.IsDisabled = () => Map.Options.Shroud.HasValue || configurationDisabled(); + enableShroud.IsDisabled = () => map.Options.Shroud.HasValue || configurationDisabled(); enableShroud.OnClick = () => orderManager.IssueOrder(Order.Command( "shroud {0}".F(!orderManager.LobbyInfo.GlobalSettings.Shroud))); } @@ -438,7 +440,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (enableFog != null) { enableFog.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Fog; - enableFog.IsDisabled = () => Map.Options.Fog.HasValue || configurationDisabled(); + enableFog.IsDisabled = () => map.Options.Fog.HasValue || configurationDisabled(); enableFog.OnClick = () => orderManager.IssueOrder(Order.Command( "fog {0}".F(!orderManager.LobbyInfo.GlobalSettings.Fog))); } @@ -525,23 +527,24 @@ namespace OpenRA.Mods.RA.Widgets.Logic void UpdateCurrentMap() { - if (MapUid == orderManager.LobbyInfo.GlobalSettings.Map) + if (mapUid == orderManager.LobbyInfo.GlobalSettings.Map) return; - MapUid = orderManager.LobbyInfo.GlobalSettings.Map; - if (!Game.modData.AvailableMaps.ContainsKey (MapUid)) + mapUid = orderManager.LobbyInfo.GlobalSettings.Map; + + if (!Game.modData.AvailableMaps.ContainsKey(mapUid)) if (Game.Settings.Game.AllowDownloading) { - Game.DownloadMap (MapUid); + Game.DownloadMap(mapUid); Game.Debug("A new map has been downloaded..."); } else throw new InvalidOperationException("Server's new map doesn't exist on your system and Downloading turned off"); - Map = new Map(Game.modData.AvailableMaps[MapUid].Path); + map = new Map(Game.modData.AvailableMaps[mapUid].Path); // Restore default starting cash if the last map set it to something invalid var pri = Rules.Info["player"].Traits.Get(); - if (!Map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash)) + if (!map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash)) orderManager.IssueOrder(Order.Command("startingcash {0}".F(pri.DefaultCash))); } @@ -556,14 +559,14 @@ namespace OpenRA.Mods.RA.Widgets.Logic Widget template = null; // get template for possible reuse - if (idx < Players.Children.Count) - template = Players.Children[idx]; + if (idx < players.Children.Count) + template = players.Children[idx]; // Empty slot if (client == null) { - if (template == null || template.Id != EmptySlotTemplate.Id) - template = EmptySlotTemplate.Clone(); + if (template == null || template.Id != emptySlotTemplate.Id) + template = emptySlotTemplate.Clone(); if (Game.IsHost) LobbyUtils.SetupEditableSlotWidget(template, slot, client, orderManager); @@ -580,8 +583,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic else if ((client.Index == orderManager.LocalClient.Index) || (client.Bot != null && Game.IsHost)) { - if (template == null || template.Id != EditablePlayerTemplate.Id) - template = EditablePlayerTemplate.Clone(); + if (template == null || template.Id != editablePlayerTemplate.Id) + template = editablePlayerTemplate.Clone(); LobbyUtils.SetupClientWidget(template, slot, client, orderManager, client.Bot == null); @@ -591,31 +594,31 @@ namespace OpenRA.Mods.RA.Widgets.Logic LobbyUtils.SetupEditableNameWidget(template, slot, client, orderManager); LobbyUtils.SetupEditableColorWidget(template, slot, client, orderManager, colorPreview); - LobbyUtils.SetupEditableFactionWidget(template, slot, client, orderManager, CountryNames); - LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, Map.GetSpawnPoints().Length); + LobbyUtils.SetupEditableFactionWidget(template, slot, client, orderManager, countryNames); + LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, map.GetSpawnPoints().Length); LobbyUtils.SetupEditableReadyWidget(template, slot, client, orderManager); } else { // Non-editable player in slot - if (template == null || template.Id != NonEditablePlayerTemplate.Id) - template = NonEditablePlayerTemplate.Clone(); + if (template == null || template.Id != nonEditablePlayerTemplate.Id) + template = nonEditablePlayerTemplate.Clone(); LobbyUtils.SetupClientWidget(template, slot, client, orderManager, client.Bot == null); LobbyUtils.SetupNameWidget(template, slot, client); LobbyUtils.SetupKickWidget(template, slot, client, orderManager, lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players); LobbyUtils.SetupColorWidget(template, slot, client); - LobbyUtils.SetupFactionWidget(template, slot, client, CountryNames); + LobbyUtils.SetupFactionWidget(template, slot, client, countryNames); LobbyUtils.SetupTeamWidget(template, slot, client); LobbyUtils.SetupReadyWidget(template, slot, client); } template.IsVisible = () => true; - if (idx >= Players.Children.Count) - Players.AddChild(template); - else if (Players.Children[idx].Id != template.Id) - Players.ReplaceChild(Players.Children[idx], template); + if (idx >= players.Children.Count) + players.AddChild(template); + else if (players.Children[idx].Id != template.Id) + players.ReplaceChild(players.Children[idx], template); idx++; } @@ -627,22 +630,22 @@ namespace OpenRA.Mods.RA.Widgets.Logic var c = client; // get template for possible reuse - if (idx < Players.Children.Count) - template = Players.Children[idx]; + if (idx < players.Children.Count) + template = players.Children[idx]; // Editable spectator if (c.Index == orderManager.LocalClient.Index) { - if (template == null || template.Id != EditableSpectatorTemplate.Id) - template = EditableSpectatorTemplate.Clone(); + if (template == null || template.Id != editableSpectatorTemplate.Id) + template = editableSpectatorTemplate.Clone(); LobbyUtils.SetupEditableNameWidget(template, null, c, orderManager); } // Non-editable spectator else { - if (template == null || template.Id != NonEditableSpectatorTemplate.Id) - template = NonEditableSpectatorTemplate.Clone(); + if (template == null || template.Id != nonEditableSpectatorTemplate.Id) + template = nonEditableSpectatorTemplate.Clone(); LobbyUtils.SetupNameWidget(template, null, client); LobbyUtils.SetupKickWidget(template, null, client, orderManager, lobby, @@ -652,10 +655,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic LobbyUtils.SetupClientWidget(template, null, c, orderManager, true); template.IsVisible = () => true; - if (idx >= Players.Children.Count) - Players.AddChild(template); - else if (Players.Children[idx].Id != template.Id) - Players.ReplaceChild(Players.Children[idx], template); + if (idx >= players.Children.Count) + players.AddChild(template); + else if (players.Children[idx].Id != template.Id) + players.ReplaceChild(players.Children[idx], template); idx++; } @@ -664,10 +667,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (orderManager.LocalClient.Slot != null) { Widget spec = null; - if (idx < Players.Children.Count) - spec = Players.Children[idx]; - if (spec == null || spec.Id != NewSpectatorTemplate.Id) - spec = NewSpectatorTemplate.Clone(); + if (idx < players.Children.Count) + spec = players.Children[idx]; + if (spec == null || spec.Id != newSpectatorTemplate.Id) + spec = newSpectatorTemplate.Clone(); LobbyUtils.SetupKickSpectatorsWidget(spec, orderManager, lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players, this.skirmishMode); @@ -679,17 +682,17 @@ namespace OpenRA.Mods.RA.Widgets.Logic || orderManager.LocalClient.IsAdmin; spec.IsVisible = () => true; - - if (idx >= Players.Children.Count) - Players.AddChild(spec); - else if (Players.Children[idx].Id != spec.Id) - Players.ReplaceChild(Players.Children[idx], spec); + + if (idx >= players.Children.Count) + players.AddChild(spec); + else if (players.Children[idx].Id != spec.Id) + players.ReplaceChild(players.Children[idx], spec); idx++; } - while (Players.Children.Count > idx) - Players.RemoveChild(Players.Children[idx]); + while (players.Children.Count > idx) + players.RemoveChild(players.Children[idx]); } class DropDownOption From 3392d00294f1b9654eb6bd4c90a4827039b54dba Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Thu, 13 Mar 2014 12:50:06 +1300 Subject: [PATCH 04/13] More style fixes. Also fixes bogus recursive storage of OnGameStart when a player is disconnected from the server. --- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 76 ++++++++++++---------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 2e80772d6a..f8122710a2 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -20,6 +20,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic { public class LobbyLogic { + readonly Action onStart; + readonly Action onExit; + readonly OrderManager orderManager; + readonly bool skirmishMode; + enum PanelType { Players, Options, Kick } PanelType panel = PanelType.Players; @@ -38,11 +43,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic ColorPreviewManagerWidget colorPreview; - readonly Action OnGameStart; - readonly Action onExit; - readonly OrderManager orderManager; - readonly bool skirmishMode; - // Listen for connection failures void ConnectionStateChanged(OrderManager om) { @@ -56,7 +56,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic Game.OpenWindow("SERVER_LOBBY", new WidgetArgs() { { "onExit", onExit }, - { "onStart", OnGameStart }, + { "onStart", onStart }, { "skirmishMode", false } }); }; @@ -92,7 +92,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { lobby = widget; this.orderManager = orderManager; - this.OnGameStart = () => { CloseWindow(); onStart(); }; + this.onStart = onStart; this.onExit = onExit; this.skirmishMode = skirmishMode; @@ -110,7 +110,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic players = Ui.LoadWidget("LOBBY_PLAYER_BIN", lobby.Get("PLAYER_BIN_ROOT"), new WidgetArgs()); players.IsVisible = () => panel == PanelType.Players; - var playerBinHeaders = lobby.GetOrNull("LABEL_CONTAINER"); if (playerBinHeaders != null) playerBinHeaders.IsVisible = () => panel == PanelType.Players; @@ -176,7 +175,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() { { "initialMap", map.Uid }, - { "onExit", () => {} }, + { "onExit", () => { } }, { "onSelect", onSelect } }); }; @@ -188,7 +187,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic slotsButton.IsDisabled = () => configurationDisabled() || panel != PanelType.Players || !orderManager.LobbyInfo.Slots.Values.Any(s => s.AllowBots || !s.LockTeam); - var aiModes = Rules.Info["player"].Traits.WithInterface().Select(t => t.Name); + var botNames = Rules.Info["player"].Traits.WithInterface().Select(t => t.Name); slotsButton.OnMouseDown = _ => { var options = new Dictionary>(); @@ -196,21 +195,24 @@ namespace OpenRA.Mods.RA.Widgets.Logic var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin); if (orderManager.LobbyInfo.Slots.Values.Any(s => s.AllowBots)) { - var botOptions = new List(){ new DropDownOption() + var botOptions = new List() { - Title = "Add", - IsSelected = () => false, - OnClick = () => + new DropDownOption() { - foreach (var slot in orderManager.LobbyInfo.Slots) + Title = "Add", + IsSelected = () => false, + OnClick = () => { - var bot = aiModes.Random(Game.CosmeticRandom); - var c = orderManager.LobbyInfo.ClientInSlot(slot.Key); - if (slot.Value.AllowBots == true && (c == null || c.Bot != null)) - orderManager.IssueOrder(Order.Command("slot_bot {0} {1} {2}".F(slot.Key, botController.Index, bot))); + foreach (var slot in orderManager.LobbyInfo.Slots) + { + var bot = botNames.Random(Game.CosmeticRandom); + var c = orderManager.LobbyInfo.ClientInSlot(slot.Key); + if (slot.Value.AllowBots == true && (c == null || c.Bot != null)) + orderManager.IssueOrder(Order.Command("slot_bot {0} {1} {2}".F(slot.Key, botController.Index, bot))); + } } } - }}; + }; if (orderManager.LobbyInfo.Clients.Any(c => c.Bot != null)) { @@ -224,7 +226,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { var c = orderManager.LobbyInfo.ClientInSlot(slot.Key); if (c != null && c.Bot != null) - orderManager.IssueOrder(Order.Command("slot_open "+slot.Value.PlayerReference)); + orderManager.IssueOrder(Order.Command("slot_open " + slot.Value.PlayerReference)); } } }); @@ -367,18 +369,18 @@ namespace OpenRA.Mods.RA.Widgets.Logic var startingUnits = optionsBin.GetOrNull("STARTINGUNITS_DROPDOWNBUTTON"); if (startingUnits != null) { - var classNames = new Dictionary() + var classNames = new Dictionary() { - {"none", "MCV Only"}, - {"light", "Light Support"}, - {"heavy", "Heavy Support"}, + { "none", "MCV Only" }, + { "light", "Light Support" }, + { "heavy", "Heavy Support" }, }; Func className = c => classNames.ContainsKey(c) ? classNames[c] : c; var classes = Rules.Info["world"].Traits.WithInterface() .Select(a => a.Class).Distinct(); - startingUnits.IsDisabled = () => !map.Options.ConfigurableStartingUnits || configurationDisabled(); + startingUnits.IsDisabled = () => !map.Options.ConfigurableStartingUnits || configurationDisabled(); startingUnits.GetText = () => !map.Options.ConfigurableStartingUnits ? "Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass); startingUnits.OnMouseDown = _ => { @@ -465,7 +467,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic chatTextField.OnTabKey = () => { teamChat ^= true; - chatLabel.Text = (teamChat) ? "Team:" : "Chat:"; + chatLabel.Text = teamChat ? "Team:" : "Chat:"; return true; }; @@ -475,8 +477,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic var musicButton = lobby.GetOrNull("MUSIC_BUTTON"); if (musicButton != null) - musicButton.OnClick = () => Ui.OpenWindow("MUSIC_PANEL", new WidgetArgs - { { "onExit", () => {} } }); + musicButton.OnClick = () => Ui.OpenWindow("MUSIC_PANEL", + new WidgetArgs { { "onExit", () => { } } }); // Add a bot on the first lobbyinfo update if (this.skirmishMode) @@ -562,9 +564,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (idx < players.Children.Count) template = players.Children[idx]; - // Empty slot if (client == null) { + // Empty slot if (template == null || template.Id != emptySlotTemplate.Id) template = emptySlotTemplate.Clone(); @@ -578,11 +580,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic join.IsDisabled = () => orderManager.LocalClient.IsReady; join.OnClick = () => orderManager.IssueOrder(Order.Command("slot " + key)); } - - // Editable player in slot else if ((client.Index == orderManager.LocalClient.Index) || (client.Bot != null && Game.IsHost)) { + // Editable player in slot if (template == null || template.Id != editablePlayerTemplate.Id) template = editablePlayerTemplate.Clone(); @@ -599,7 +600,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic LobbyUtils.SetupEditableReadyWidget(template, slot, client, orderManager); } else - { // Non-editable player in slot + { + // Non-editable player in slot if (template == null || template.Id != nonEditablePlayerTemplate.Id) template = nonEditablePlayerTemplate.Clone(); @@ -641,9 +643,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic LobbyUtils.SetupEditableNameWidget(template, null, c, orderManager); } - // Non-editable spectator else { + // Non-editable spectator if (template == null || template.Id != nonEditableSpectatorTemplate.Id) template = nonEditableSpectatorTemplate.Clone(); @@ -695,6 +697,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic players.RemoveChild(players.Children[idx]); } + void OnGameStart() + { + CloseWindow(); + onStart(); + } + class DropDownOption { public string Title; From 63068d5a7cc6aeddd420370a371a5be366be068a Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 14 Mar 2014 20:42:44 +1300 Subject: [PATCH 05/13] Fix Map.GetSpawnPoints to return CPos. --- OpenRA.Game/Map.cs | 6 +++--- OpenRA.Game/Widgets/MapPreviewWidget.cs | 4 ++-- OpenRA.Mods.RA/MPStartLocations.cs | 7 ++++--- OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/OpenRA.Game/Map.cs b/OpenRA.Game/Map.cs index f2ec745328..1a9fbee044 100644 --- a/OpenRA.Game/Map.cs +++ b/OpenRA.Game/Map.cs @@ -228,12 +228,12 @@ namespace OpenRA Uid = ComputeHash(); } - public int2[] GetSpawnPoints() + public CPos[] GetSpawnPoints() { return Actors.Value.Values .Where(a => a.Type == "mpspawn") - .Select(a => a.InitDict.Get().value) - .ToArray(); + .Select(a => (CPos)a.InitDict.Get().value) + .ToArray(); } public void Save(string toPath) diff --git a/OpenRA.Game/Widgets/MapPreviewWidget.cs b/OpenRA.Game/Widgets/MapPreviewWidget.cs index 538ac9453a..cecd76880e 100644 --- a/OpenRA.Game/Widgets/MapPreviewWidget.cs +++ b/OpenRA.Game/Widgets/MapPreviewWidget.cs @@ -22,7 +22,7 @@ namespace OpenRA.Widgets public class MapPreviewWidget : Widget { public Func Map = () => null; - public Func> SpawnClients = () => new Dictionary(); + public Func> SpawnClients = () => new Dictionary(); public Action OnMouseDown = _ => {}; public bool IgnoreMouseInput = false; public bool ShowSpawnPoints = true; @@ -75,7 +75,7 @@ namespace OpenRA.Widgets tooltipContainer.Value.RemoveTooltip(); } - public int2 ConvertToPreview(int2 point) + public int2 ConvertToPreview(CPos point) { var map = Map(); return new int2(MapRect.X + (int)(PreviewScale*(point.X - map.Bounds.Left)) , MapRect.Y + (int)(PreviewScale*(point.Y - map.Bounds.Top))); diff --git a/OpenRA.Mods.RA/MPStartLocations.cs b/OpenRA.Mods.RA/MPStartLocations.cs index e2f8c15a4f..2be4f1b75e 100755 --- a/OpenRA.Mods.RA/MPStartLocations.cs +++ b/OpenRA.Mods.RA/MPStartLocations.cs @@ -28,9 +28,10 @@ namespace OpenRA.Mods.RA public void WorldLoaded(World world, WorldRenderer wr) { + var spawns = world.Map.GetSpawnPoints(); var taken = world.LobbyInfo.Clients.Where(c => c.SpawnPoint != 0 && c.Slot != null) - .Select(c => (CPos) world.Map.GetSpawnPoints()[c.SpawnPoint-1]).ToList(); - var available = world.Map.GetSpawnPoints().Select(c => (CPos)c).Except(taken).ToList(); + .Select(c => spawns[c.SpawnPoint-1]).ToList(); + var available = spawns.Except(taken).ToList(); // Set spawn foreach (var kv in world.LobbyInfo.Slots) @@ -41,7 +42,7 @@ namespace OpenRA.Mods.RA var client = world.LobbyInfo.ClientInSlot(kv.Key); var spid = (client == null || client.SpawnPoint == 0) ? ChooseSpawnPoint(world, available, taken) - : (CPos)world.Map.GetSpawnPoints()[client.SpawnPoint-1]; + : world.Map.GetSpawnPoints()[client.SpawnPoint-1]; Start.Add(player, spid); } diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs index 76f3e5fa13..8ed0c7b3b5 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs @@ -130,7 +130,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic color.AttachPanel(colorChooser, onExit); } - public static Dictionary GetSpawnClients(OrderManager orderManager, Map map) + public static Dictionary GetSpawnClients(OrderManager orderManager, Map map) { var spawns = map.GetSpawnPoints(); return orderManager.LobbyInfo.Clients From c30b18a9d6d6e54b98a0cc21a80868bc32685ab1 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 14 Mar 2014 13:30:03 +1300 Subject: [PATCH 06/13] Introduce MapCache and MapPreview for improved UI map previews. --- OpenRA.Game/Game.cs | 13 +- OpenRA.Game/Graphics/SheetBuilder.cs | 8 + OpenRA.Game/Graphics/Util.cs | 32 ++++ OpenRA.Game/MapCache.cs | 148 ++++++++++++++++++ OpenRA.Game/MapPreview.cs | 91 +++++++++++ OpenRA.Game/ModData.cs | 61 +------- OpenRA.Game/Network/GameServer.cs | 2 +- OpenRA.Game/Network/Replay.cs | 5 +- OpenRA.Game/OpenRA.Game.csproj | 2 + OpenRA.Game/Widgets/MapPreviewWidget.cs | 148 ++++-------------- OpenRA.Game/Widgets/WidgetUtils.cs | 19 ++- OpenRA.Lint/YamlChecker.cs | 15 +- OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs | 4 +- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 70 ++++----- OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs | 15 +- .../Widgets/Logic/MapChooserLogic.cs | 14 +- .../Widgets/Logic/ReplayBrowserLogic.cs | 8 +- .../Widgets/Logic/ServerBrowserLogic.cs | 21 +-- .../Widgets/Logic/ServerCreationLogic.cs | 15 +- OpenRA.Utility/UpgradeRules.cs | 6 +- 20 files changed, 407 insertions(+), 290 deletions(-) create mode 100755 OpenRA.Game/MapCache.cs create mode 100755 OpenRA.Game/MapPreview.cs diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index e385e98739..be4b56cb58 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -370,7 +370,7 @@ namespace OpenRA modData = new ModData(mod); Renderer.InitializeFonts(modData.Manifest); modData.InitializeLoaders(); - modData.LoadMaps(); + modData.MapCache.LoadMaps(); PerfHistory.items["render"].hasNormalTick = false; PerfHistory.items["batches"].hasNormalTick = false; @@ -401,7 +401,7 @@ namespace OpenRA if (Settings.Server.DedicatedLoop) { Console.WriteLine("Starting a new server instance..."); - modData.LoadMaps(); + modData.MapCache.LoadMaps(); continue; } @@ -426,13 +426,14 @@ namespace OpenRA static string ChooseShellmap() { - var shellmaps = modData.AvailableMaps - .Where(m => m.Value.UseAsShellmap); + var shellmaps = modData.MapCache + .Where(m => m.Status == MapStatus.Available && m.Map.UseAsShellmap) + .Select(m => m.Uid); if (!shellmaps.Any()) throw new InvalidDataException("No valid shellmaps available"); - return shellmaps.Random(CosmeticRandom).Key; + return shellmaps.Random(CosmeticRandom); } static bool quit; @@ -553,7 +554,7 @@ namespace OpenRA WebClient webClient = new WebClient(); webClient.DownloadFile(url, tempFile); File.Move(tempFile, mapPath); - Game.modData.AvailableMaps.Add(mapHash, new Map(mapPath)); + Game.modData.MapCache[mapHash].UpdateFromMap(new Map(mapPath)); Log.Write("debug", "New map has been downloaded to '{0}'", mapPath); return true; diff --git a/OpenRA.Game/Graphics/SheetBuilder.cs b/OpenRA.Game/Graphics/SheetBuilder.cs index 0cee8661ef..7915f4fd8d 100644 --- a/OpenRA.Game/Graphics/SheetBuilder.cs +++ b/OpenRA.Game/Graphics/SheetBuilder.cs @@ -67,6 +67,14 @@ namespace OpenRA.Graphics return rect; } + public Sprite Add(Bitmap src) + { + var rect = Allocate(src.Size); + Util.FastCopyIntoSprite(rect, src); + current.CommitData(); + return rect; + } + public Sprite Add(Size size, byte paletteIndex) { var data = new byte[size.Width * size.Height]; diff --git a/OpenRA.Game/Graphics/Util.cs b/OpenRA.Game/Graphics/Util.cs index a997d1b6fa..4f179834c8 100644 --- a/OpenRA.Game/Graphics/Util.cs +++ b/OpenRA.Game/Graphics/Util.cs @@ -9,6 +9,8 @@ #endregion using System; +using System.Drawing; +using System.Drawing.Imaging; using OpenRA.FileFormats.Graphics; namespace OpenRA.Graphics @@ -59,6 +61,36 @@ namespace OpenRA.Graphics } } + public static void FastCopyIntoSprite(Sprite dest, Bitmap src) + { + var destStride = dest.sheet.Size.Width; + var width = dest.bounds.Width; + var height = dest.bounds.Height; + + var srcData = src.LockBits(src.Bounds(), + ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); + + unsafe + { + var c = (int*)srcData.Scan0; + + // Cast the data to an int array so we can copy the src data directly + fixed (byte* bd = &dest.sheet.Data[0]) + { + var data = (int*)bd; + var x = dest.bounds.Left; + var y = dest.bounds.Top; + + for (var j = 0; j < height; j++) + for (var i = 0; i < width; i++) + data[(y + j) * destStride + x + i] = *(c + (j * srcData.Stride >> 2) + i); + } + } + + src.UnlockBits(srcData); + + } + public static float[] IdentityMatrix() { return Exts.MakeArray(16, j => (j % 5 == 0) ? 1.0f : 0); diff --git a/OpenRA.Game/MapCache.cs b/OpenRA.Game/MapCache.cs new file mode 100755 index 0000000000..fa751824f1 --- /dev/null +++ b/OpenRA.Game/MapCache.cs @@ -0,0 +1,148 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Widgets; + +namespace OpenRA +{ + public class MapCache : IEnumerable + { + public static readonly MapPreview UnknownMap = new MapPreview(null, null); + readonly Cache previews; + readonly Manifest manifest; + readonly SheetBuilder sheetBuilder; + Thread previewLoaderThread; + object syncRoot = new object(); + Queue generateMinimap = new Queue(); + + public MapCache(Manifest m) + { + manifest = m; + previews = new Cache(uid => new MapPreview(uid, this)); + sheetBuilder = new SheetBuilder(SheetType.BGRA); + } + + public void LoadMaps() + { + var paths = manifest.MapFolders.SelectMany(f => FindMapsIn(f)); + foreach (var path in paths) + { + try + { + var map = new Map(path, manifest.Mod.Id); + if (manifest.MapCompatibility.Contains(map.RequiresMod)) + previews[map.Uid].UpdateFromMap(map); + } + catch (Exception e) + { + Console.WriteLine("Failed to load map: {0}", path); + Console.WriteLine("Details: {0}", e); + } + } + } + + static IEnumerable FindMapsIn(string dir) + { + string[] noMaps = { }; + + // Ignore optional flag + if (dir.StartsWith("~")) + dir = dir.Substring(1); + + // Paths starting with ^ are relative to the user directory + if (dir.StartsWith("^")) + dir = Platform.SupportDir + dir.Substring(1); + + if (!Directory.Exists(dir)) + return noMaps; + + var dirsWithMaps = Directory.GetDirectories(dir) + .Where(d => Directory.GetFiles(d, "map.yaml").Any() && Directory.GetFiles(d, "map.bin").Any()); + + return dirsWithMaps.Concat(Directory.GetFiles(dir, "*.oramap")); + } + + void LoadAsyncInternal() + { + for (;;) + { + MapPreview p; + lock (syncRoot) + { + if (generateMinimap.Count == 0) + break; + + p = generateMinimap.Peek(); + + // Preview already exists + if (p.Minimap != null) + { + generateMinimap.Dequeue(); + continue; + } + } + + // Render the minimap into the shared sheet + // Note: this is not generally thread-safe, but it works here because: + // (a) This worker is the only thread writing to this sheet + // (b) The main thread is the only thread reading this sheet + // (c) The sheet is marked dirty after the write is completed, + // which causes the main thread to copy this to the texture during + // the next render cycle. + // (d) Any partially written bytes from the next minimap is in an + // unallocated area, and will be committed in the next cycle. + var bitmap = Minimap.RenderMapPreview(p.Map, true); + p.Minimap = sheetBuilder.Add(bitmap); + + lock (syncRoot) + generateMinimap.Dequeue(); + + // Yuck... But this helps the UI Jank when opening the map selector significantly. + Thread.Sleep(50); + } + } + + public void CacheMinimap(MapPreview preview) + { + lock (syncRoot) + generateMinimap.Enqueue(preview); + + if (previewLoaderThread == null || !previewLoaderThread.IsAlive) + { + previewLoaderThread = new Thread(LoadAsyncInternal); + previewLoaderThread.Start(); + } + } + + public MapPreview this[string key] + { + get { return previews[key]; } + } + + public IEnumerator GetEnumerator() + { + return previews.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/OpenRA.Game/MapPreview.cs b/OpenRA.Game/MapPreview.cs new file mode 100755 index 0000000000..1bb8352d89 --- /dev/null +++ b/OpenRA.Game/MapPreview.cs @@ -0,0 +1,91 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Widgets; + +namespace OpenRA +{ + public enum MapStatus { Available, Unavailable } + public class MapPreview + { + static readonly List NoSpawns = new List(); + + public readonly string Uid; + public string Title { get; private set; } + public string Type { get; private set; } + public string Author { get; private set; } + public int PlayerCount { get; private set; } + public List SpawnPoints { get; private set; } + public Rectangle Bounds { get; private set; } + public Map Map { get; private set; } + public MapStatus Status { get; private set; } + + Sprite minimap; + bool generatingMinimap; + public Sprite Minimap + { + get + { + if (minimap != null) + return minimap; + + if (!generatingMinimap && Status == MapStatus.Available) + { + generatingMinimap = true; + cache.CacheMinimap(this); + } + + return null; + } + + set + { + minimap = value; + generatingMinimap = false; + } + } + + MapCache cache; + public MapPreview(string uid, MapCache cache) + { + this.cache = cache; + Uid = uid; + Title = "Unknown Map"; + Type = "Unknown"; + Author = "Unknown Author"; + PlayerCount = 0; + Bounds = Rectangle.Empty; + SpawnPoints = NoSpawns; + Status = MapStatus.Unavailable; + } + + public void UpdateFromMap(Map m) + { + Map = m; + Title = m.Title; + Type = m.Type; + Type = m.Type; + Author = m.Author; + PlayerCount = m.Players.Count(x => x.Value.Playable); + Bounds = m.Bounds; + SpawnPoints = m.GetSpawnPoints().ToList(); + Status = MapStatus.Available; + } + } +} diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 55ad688f56..f5a67f5b5a 100755 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -22,34 +22,13 @@ namespace OpenRA { public readonly Manifest Manifest; public readonly ObjectCreator ObjectCreator; - public Dictionary AvailableMaps { get; private set; } public readonly WidgetLoader WidgetLoader; + public readonly MapCache MapCache; public ILoadScreen LoadScreen = null; public SheetBuilder SheetBuilder; public SpriteLoader SpriteLoader; public VoxelLoader VoxelLoader; - public static IEnumerable FindMapsIn(string dir) - { - string[] noMaps = { }; - - // ignore optional flag - if (dir.StartsWith("~")) - dir = dir.Substring(1); - - // paths starting with ^ are relative to the user directory - if (dir.StartsWith("^")) - dir = Platform.SupportDir + dir.Substring(1); - - if (!Directory.Exists(dir)) - return noMaps; - - var dirsWithMaps = Directory.GetDirectories(dir) - .Where(d => Directory.GetFiles(d, "map.yaml").Any() && Directory.GetFiles(d, "map.bin").Any()); - - return dirsWithMaps.Concat(Directory.GetFiles(dir, "*.oramap")); - } - public ModData(string mod) { Languages = new string[0]; @@ -59,6 +38,7 @@ namespace OpenRA LoadScreen.Init(Manifest, Manifest.LoadScreen.NodesDict.ToDictionary(x => x.Key, x => x.Value.Value)); LoadScreen.Display(); WidgetLoader = new WidgetLoader(this); + MapCache = new MapCache(Manifest); // HACK: Mount only local folders so we have a half-working environment for the asset installer FileSystem.UnmountAll(); @@ -119,17 +99,13 @@ namespace OpenRA FieldLoader.Translations = translations; } - public void LoadMaps() - { - AvailableMaps = FindMaps(); - } - public Map PrepareMap(string uid) { LoadScreen.Display(); - if (!AvailableMaps.ContainsKey(uid)) + + var map = MapCache[uid].Map; + if (map == null) throw new InvalidDataException("Invalid map uid: {0}".F(uid)); - var map = new Map(AvailableMaps[uid].Path); LoadTranslations(map); @@ -148,33 +124,6 @@ namespace OpenRA VoxelProvider.Initialize(Manifest.VoxelSequences, map.VoxelSequences); return map; } - - public Dictionary FindMaps() - { - var paths = Manifest.MapFolders.SelectMany(f => FindMapsIn(f)); - var ret = new Dictionary(); - foreach (var path in paths) - { - try - { - var map = new Map(path, Manifest.Mod.Id); - if (Manifest.MapCompatibility.Contains(map.RequiresMod)) - ret.Add(map.Uid, map); - } - catch (Exception e) - { - Console.WriteLine("Failed to load map: {0}", path); - Console.WriteLine("Details: {0}", e); - } - } - - return ret; - } - - public Map FindMapByUid(string uid) - { - return AvailableMaps.ContainsKey(uid) ? AvailableMaps[uid] : null; - } } public interface ILoadScreen diff --git a/OpenRA.Game/Network/GameServer.cs b/OpenRA.Game/Network/GameServer.cs index 2cd7eb7754..e289325511 100644 --- a/OpenRA.Game/Network/GameServer.cs +++ b/OpenRA.Game/Network/GameServer.cs @@ -37,7 +37,7 @@ namespace OpenRA.Network // Don't have the map locally // TODO: We allow joining, then drop on game start if the map isn't available - if (!Game.modData.AvailableMaps.ContainsKey(Map) && !Game.Settings.Game.AllowDownloading) + if (Game.modData.MapCache[Map].Status != MapStatus.Available && !Game.Settings.Game.AllowDownloading) return false; return true; diff --git a/OpenRA.Game/Network/Replay.cs b/OpenRA.Game/Network/Replay.cs index 155489bcc4..a62f628860 100644 --- a/OpenRA.Game/Network/Replay.cs +++ b/OpenRA.Game/Network/Replay.cs @@ -35,10 +35,7 @@ namespace OpenRA.Network return null; var map = LobbyInfo.GlobalSettings.Map; - if (!Game.modData.AvailableMaps.ContainsKey(map)) - return null; - - return Game.modData.AvailableMaps[map]; + return Game.modData.MapCache[map].Map; } } } diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 331c6c1829..0085f53d30 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -232,6 +232,8 @@ + + diff --git a/OpenRA.Game/Widgets/MapPreviewWidget.cs b/OpenRA.Game/Widgets/MapPreviewWidget.cs index cecd76880e..dd4eb9e876 100644 --- a/OpenRA.Game/Widgets/MapPreviewWidget.cs +++ b/OpenRA.Game/Widgets/MapPreviewWidget.cs @@ -21,7 +21,7 @@ namespace OpenRA.Widgets { public class MapPreviewWidget : Widget { - public Func Map = () => null; + public Func Preview = () => null; public Func> SpawnClients = () => new Dictionary(); public Action OnMouseDown = _ => {}; public bool IgnoreMouseInput = false; @@ -32,6 +32,9 @@ namespace OpenRA.Widgets Lazy tooltipContainer; public int TooltipSpawnIndex = -1; + Rectangle MapRect; + float PreviewScale = 0; + public MapPreviewWidget() { tooltipContainer = Lazy.New(() => Ui.Root.Get(TooltipContainer)); @@ -40,8 +43,7 @@ namespace OpenRA.Widgets protected MapPreviewWidget(MapPreviewWidget other) : base(other) { - lastMap = other.lastMap; - Map = other.Map; + Preview = other.Preview; SpawnClients = other.SpawnClients; ShowSpawnPoints = other.ShowSpawnPoints; TooltipTemplate = other.TooltipTemplate; @@ -65,74 +67,51 @@ namespace OpenRA.Widgets public override void MouseEntered() { - if (TooltipContainer == null) return; - tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs() {{ "preview", this }}); + if (TooltipContainer != null) + tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs() {{ "preview", this }}); } public override void MouseExited() { - if (TooltipContainer == null) return; - tooltipContainer.Value.RemoveTooltip(); + if (TooltipContainer != null) + tooltipContainer.Value.RemoveTooltip(); } public int2 ConvertToPreview(CPos point) { - var map = Map(); - return new int2(MapRect.X + (int)(PreviewScale*(point.X - map.Bounds.Left)) , MapRect.Y + (int)(PreviewScale*(point.Y - map.Bounds.Top))); + var preview = Preview(); + return new int2(MapRect.X + (int)(PreviewScale*(point.X - preview.Bounds.Left)) , MapRect.Y + (int)(PreviewScale*(point.Y - preview.Bounds.Top))); } - Sheet mapChooserSheet; - Sprite mapChooserSprite; - Map lastMap; - Rectangle MapRect; - float PreviewScale = 0; - + Sprite minimap; public override void Draw() { - var map = Map(); - if (map == null) + var preview = Preview(); + if (preview == null) return; - // Preview unavailable - if (!Loaded) - { - GeneratePreview(); + // Stash a copy of the minimap to ensure consistency + // (it may be modified by another thread) + minimap = preview.Minimap; + if (minimap == null) return; - } - - if (lastMap != map) - { - lastMap = map; - - // Update image data - Bitmap preview; - lock (syncRoot) - preview = Previews[map.Uid]; - - if (mapChooserSheet == null || mapChooserSheet.Size.Width != preview.Width || mapChooserSheet.Size.Height != preview.Height) - mapChooserSheet = new Sheet(new Size(preview.Width, preview.Height)); - - mapChooserSheet.Texture.SetData(preview); - mapChooserSprite = new Sprite(mapChooserSheet, new Rectangle(0, 0, map.Bounds.Width, map.Bounds.Height), TextureChannel.Alpha); - } // Update map rect - PreviewScale = Math.Min(RenderBounds.Width * 1.0f / map.Bounds.Width, RenderBounds.Height * 1.0f / map.Bounds.Height); - var size = Math.Max(map.Bounds.Width, map.Bounds.Height); - var dw = (int)(PreviewScale * (size - map.Bounds.Width)) / 2; - var dh = (int)(PreviewScale * (size - map.Bounds.Height)) / 2; - MapRect = new Rectangle(RenderBounds.X + dw, RenderBounds.Y + dh, (int)(map.Bounds.Width * PreviewScale), (int)(map.Bounds.Height * PreviewScale)); + PreviewScale = Math.Min(RenderBounds.Width / minimap.size.X, RenderBounds.Height / minimap.size.Y); + var w = (int)(PreviewScale * minimap.size.X); + var h = (int)(PreviewScale * minimap.size.Y); + var x = RenderBounds.X + (RenderBounds.Width - w) / 2; + var y = RenderBounds.Y + (RenderBounds.Height - h) / 2; + MapRect = new Rectangle(x, y, w, h); - Game.Renderer.RgbaSpriteRenderer.DrawSprite(mapChooserSprite, - new float2(MapRect.Location), - new float2(MapRect.Size)); + Game.Renderer.RgbaSpriteRenderer.DrawSprite(minimap, new float2(MapRect.Location), new float2(MapRect.Size)); TooltipSpawnIndex = -1; if (ShowSpawnPoints) { var colors = SpawnClients().ToDictionary(c => c.Key, c => c.Value.Color.RGB); - var spawnPoints = map.GetSpawnPoints().ToList(); + var spawnPoints = preview.SpawnPoints; foreach (var p in spawnPoints) { var owned = colors.ContainsKey(p); @@ -151,79 +130,6 @@ namespace OpenRA.Widgets } } - // Async map preview generation bits - enum PreviewStatus { Invalid, Uncached, Generating, Cached } - static Thread previewLoaderThread; - static object syncRoot = new object(); - static Queue cacheUids = new Queue(); - static readonly Dictionary Previews = new Dictionary(); - - void LoadAsyncInternal() - { - for (;;) - { - string uid; - lock (syncRoot) - { - if (cacheUids.Count == 0) - break; - uid = cacheUids.Peek(); - } - - var bitmap = Minimap.RenderMapPreview(Game.modData.AvailableMaps[uid], false); - lock (syncRoot) - { - // TODO: We should add previews to a sheet here (with multiple previews per sheet) - Previews.Add(uid, bitmap); - cacheUids.Dequeue(); - } - - // Yuck... But this helps the UI Jank when opening the map selector significantly. - Thread.Sleep(50); - } - } - - bool compatibleTileset; - - void GeneratePreview() - { - var m = Map(); - if (m == null) - return; - - compatibleTileset = Rules.TileSets.Values.Any(t => t.Id == m.Tileset); - - var status = Status(m); - if (status == PreviewStatus.Uncached) - lock (syncRoot) - cacheUids.Enqueue(m.Uid); - - if (previewLoaderThread == null || !previewLoaderThread.IsAlive) - { - previewLoaderThread = new Thread(LoadAsyncInternal); - previewLoaderThread.Start(); - } - } - - PreviewStatus Status(Map m) - { - if (m == null) - return PreviewStatus.Invalid; - - lock (syncRoot) - { - if (Previews.ContainsKey(m.Uid)) - return PreviewStatus.Cached; - - if (cacheUids.Contains(m.Uid)) - return PreviewStatus.Generating; - - if (!compatibleTileset) - return PreviewStatus.Invalid; - } - return PreviewStatus.Uncached; - } - - public bool Loaded { get { return Status(Map()) == PreviewStatus.Cached; } } + public bool Loaded { get { return minimap != null; } } } } diff --git a/OpenRA.Game/Widgets/WidgetUtils.cs b/OpenRA.Game/Widgets/WidgetUtils.cs index 63f49a0955..f4f70cd0f9 100644 --- a/OpenRA.Game/Widgets/WidgetUtils.cs +++ b/OpenRA.Game/Widgets/WidgetUtils.cs @@ -225,14 +225,13 @@ namespace OpenRA.Widgets public static Action Once( Action a ) { return () => { if (a != null) { a(); a = null; } }; } - public static string ChooseInitialMap(string map) + public static string ChooseInitialMap(string initialUid) { - var availableMaps = Game.modData.AvailableMaps; - if (string.IsNullOrEmpty(map) || !availableMaps.ContainsKey(map)) + if (string.IsNullOrEmpty(initialUid) || Game.modData.MapCache[initialUid].Status != MapStatus.Available) { - Func isIdealMap = m => + Func isIdealMap = m => { - if (!m.Selectable) + if (m.Status != MapStatus.Available || !m.Map.Selectable) return false; // Other map types may have confusing settings or gameplay @@ -240,22 +239,22 @@ namespace OpenRA.Widgets return false; // Maps with bots disabled confuse new players - if (m.Players.Any(s => !s.Value.AllowBots)) + if (m.Map.Players.Any(s => !s.Value.AllowBots)) return false; // Large maps expose unfortunate performance problems - if (m.MapSize.X > 128 || m.MapSize.Y > 128) + if (m.Bounds.Width > 128 || m.Bounds.Height > 128) return false; return true; }; - var selected = availableMaps.Values.Where(m => isIdealMap(m)).RandomOrDefault(Game.CosmeticRandom) ?? - availableMaps.Values.First(m => m.Selectable); + var selected = Game.modData.MapCache.Where(m => isIdealMap(m)).RandomOrDefault(Game.CosmeticRandom) ?? + Game.modData.MapCache.First(m => m.Status == MapStatus.Available && m.Map.Selectable); return selected.Uid; } - return map; + return initialUid; } } diff --git a/OpenRA.Lint/YamlChecker.cs b/OpenRA.Lint/YamlChecker.cs index 679f7fbe8c..e35fd3fa3d 100644 --- a/OpenRA.Lint/YamlChecker.cs +++ b/OpenRA.Lint/YamlChecker.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Collections.Generic; using System.Linq; using OpenRA.FileFormats; using OpenRA.Traits; @@ -52,14 +53,16 @@ namespace OpenRA.Lint AppDomain.CurrentDomain.AssemblyResolve += FileSystem.ResolveAssembly; Game.modData = new ModData(mod); - var maps = new Map[] { new Map() }; - if (!string.IsNullOrEmpty(map)) - maps = new Map[] { new Map(map) }; - else + IEnumerable maps; + if (string.IsNullOrEmpty(map)) { - Game.modData.LoadMaps(); - maps = Game.modData.AvailableMaps.Values.ToArray(); + Game.modData.MapCache.LoadMaps(); + maps = Game.modData.MapCache + .Where(m => m.Status == MapStatus.Available) + .Select(m => m.Map); } + else + maps = new [] { new Map(map) }; foreach (var testMap in maps) { diff --git a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs index 37ef9d3b4f..6565b1b796 100644 --- a/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.RA/ServerTraits/LobbyCommands.cs @@ -280,7 +280,7 @@ namespace OpenRA.Mods.RA.Server return true; } - if (!server.ModData.AvailableMaps.ContainsKey(s)) + if (server.ModData.MapCache[s].Status != MapStatus.Available) { server.SendOrderTo(conn, "Message", "Map was not found on server"); return true; @@ -720,7 +720,7 @@ namespace OpenRA.Mods.RA.Server static void LoadMap(S server) { - server.Map = new Map(server.ModData.AvailableMaps[server.LobbyInfo.GlobalSettings.Map].Path); + server.Map = server.ModData.MapCache[server.LobbyInfo.GlobalSettings.Map].Map; server.LobbyInfo.Slots = server.Map.Players .Select(p => MakeSlotFromPlayerReference(p.Value)) .Where(s => s != null) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index f8122710a2..7c64cbf925 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -38,8 +38,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic ScrollPanelWidget players; Dictionary countryNames; - string mapUid; - Map map; + MapPreview preview = MapCache.UnknownMap; ColorPreviewManagerWidget colorPreview; @@ -124,31 +123,21 @@ namespace OpenRA.Mods.RA.Widgets.Logic colorPreview.Color = Game.Settings.Player.Color; var mapPreview = lobby.Get("MAP_PREVIEW"); - mapPreview.IsVisible = () => map != null; - mapPreview.Map = () => map; - mapPreview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, mapPreview, map, mi); - mapPreview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, map); + mapPreview.Preview = () => preview; + mapPreview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, mapPreview, preview, mi); + mapPreview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, preview); var mapTitle = lobby.GetOrNull("MAP_TITLE"); if (mapTitle != null) - { - mapTitle.IsVisible = () => map != null; - mapTitle.GetText = () => map.Title; - } + mapTitle.GetText = () => preview.Title; var mapType = lobby.GetOrNull("MAP_TYPE"); if (mapType != null) - { - mapType.IsVisible = () => map != null; - mapType.GetText = () => map.Type; - } + mapType.GetText = () => preview.Type; var mapAuthor = lobby.GetOrNull("MAP_AUTHOR"); if (mapAuthor != null) - { - mapAuthor.IsVisible = () => map != null; - mapAuthor.GetText = () => "Created by {0}".F(map.Author); - } + mapAuthor.GetText = () => "Created by {0}".F(preview.Author); countryNames = Rules.Info["world"].Traits.WithInterface() .Where(c => c.Selectable) @@ -174,7 +163,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() { - { "initialMap", map.Uid }, + { "initialMap", preview.Uid }, { "onExit", () => { } }, { "onSelect", onSelect } }); @@ -308,7 +297,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (allowCheats != null) { allowCheats.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllowCheats; - allowCheats.IsDisabled = () => map.Options.Cheats.HasValue || configurationDisabled(); + allowCheats.IsDisabled = () => preview.Status != MapStatus.Available || preview.Map.Options.Cheats.HasValue || configurationDisabled(); allowCheats.OnClick = () => orderManager.IssueOrder(Order.Command( "allowcheats {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllowCheats))); } @@ -317,7 +306,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (crates != null) { crates.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Crates; - crates.IsDisabled = () => map.Options.Crates.HasValue || configurationDisabled(); + crates.IsDisabled = () => preview.Status != MapStatus.Available || preview.Map.Options.Crates.HasValue || configurationDisabled(); crates.OnClick = () => orderManager.IssueOrder(Order.Command( "crates {0}".F(!orderManager.LobbyInfo.GlobalSettings.Crates))); } @@ -326,7 +315,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (allybuildradius != null) { allybuildradius.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllyBuildRadius; - allybuildradius.IsDisabled = () => map.Options.AllyBuildRadius.HasValue || configurationDisabled(); + allybuildradius.IsDisabled = () => preview.Status != MapStatus.Available || preview.Map.Options.AllyBuildRadius.HasValue || configurationDisabled(); allybuildradius.OnClick = () => orderManager.IssueOrder(Order.Command( "allybuildradius {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllyBuildRadius))); } @@ -335,7 +324,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (fragileAlliance != null) { fragileAlliance.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.FragileAlliances; - fragileAlliance.IsDisabled = () => map.Options.FragileAlliances.HasValue || configurationDisabled(); + fragileAlliance.IsDisabled = () => preview.Status != MapStatus.Available || preview.Map.Options.FragileAlliances.HasValue || configurationDisabled(); fragileAlliance.OnClick = () => orderManager.IssueOrder(Order.Command( "fragilealliance {0}".F(!orderManager.LobbyInfo.GlobalSettings.FragileAlliances))); } @@ -343,12 +332,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic var difficulty = optionsBin.GetOrNull("DIFFICULTY_DROPDOWNBUTTON"); if (difficulty != null) { - difficulty.IsVisible = () => map.Options.Difficulties.Any(); - difficulty.IsDisabled = configurationDisabled; + difficulty.IsVisible = () => preview.Status == MapStatus.Available && preview.Map.Options.Difficulties.Any(); + difficulty.IsDisabled = () => preview.Status != MapStatus.Available || configurationDisabled(); difficulty.GetText = () => orderManager.LobbyInfo.GlobalSettings.Difficulty; difficulty.OnMouseDown = _ => { - var options = map.Options.Difficulties.Select(d => new DropDownOption + var options = preview.Map.Options.Difficulties.Select(d => new DropDownOption { Title = d, IsSelected = () => orderManager.LobbyInfo.GlobalSettings.Difficulty == d, @@ -380,8 +369,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic var classes = Rules.Info["world"].Traits.WithInterface() .Select(a => a.Class).Distinct(); - startingUnits.IsDisabled = () => !map.Options.ConfigurableStartingUnits || configurationDisabled(); - startingUnits.GetText = () => !map.Options.ConfigurableStartingUnits ? "Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass); + startingUnits.IsDisabled = () => preview.Status != MapStatus.Available || !preview.Map.Options.ConfigurableStartingUnits || configurationDisabled(); + startingUnits.GetText = () => preview.Status != MapStatus.Available || !preview.Map.Options.ConfigurableStartingUnits ? "Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass); startingUnits.OnMouseDown = _ => { var options = classes.Select(c => new DropDownOption @@ -407,8 +396,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic var startingCash = optionsBin.GetOrNull("STARTINGCASH_DROPDOWNBUTTON"); if (startingCash != null) { - startingCash.IsDisabled = () => map.Options.StartingCash.HasValue || configurationDisabled(); - startingCash.GetText = () => map.Options.StartingCash.HasValue ? "Not Available" : "${0}".F(orderManager.LobbyInfo.GlobalSettings.StartingCash); + startingCash.IsDisabled = () => preview.Status != MapStatus.Available || preview.Map.Options.StartingCash.HasValue || configurationDisabled(); + startingCash.GetText = () => preview.Status != MapStatus.Available || preview.Map.Options.StartingCash.HasValue ? "Not Available" : "${0}".F(orderManager.LobbyInfo.GlobalSettings.StartingCash); startingCash.OnMouseDown = _ => { var options = Rules.Info["player"].Traits.Get().SelectableCash.Select(c => new DropDownOption @@ -433,7 +422,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (enableShroud != null) { enableShroud.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Shroud; - enableShroud.IsDisabled = () => map.Options.Shroud.HasValue || configurationDisabled(); + enableShroud.IsDisabled = () => preview.Status != MapStatus.Available || preview.Map.Options.Shroud.HasValue || configurationDisabled(); enableShroud.OnClick = () => orderManager.IssueOrder(Order.Command( "shroud {0}".F(!orderManager.LobbyInfo.GlobalSettings.Shroud))); } @@ -442,7 +431,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (enableFog != null) { enableFog.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Fog; - enableFog.IsDisabled = () => map.Options.Fog.HasValue || configurationDisabled(); + enableFog.IsDisabled = () => preview.Status != MapStatus.Available || preview.Map.Options.Fog.HasValue || configurationDisabled(); enableFog.OnClick = () => orderManager.IssueOrder(Order.Command( "fog {0}".F(!orderManager.LobbyInfo.GlobalSettings.Fog))); } @@ -529,24 +518,25 @@ namespace OpenRA.Mods.RA.Widgets.Logic void UpdateCurrentMap() { - if (mapUid == orderManager.LobbyInfo.GlobalSettings.Map) + var uid = orderManager.LobbyInfo.GlobalSettings.Map; + if (preview.Uid == uid) return; - mapUid = orderManager.LobbyInfo.GlobalSettings.Map; - - if (!Game.modData.AvailableMaps.ContainsKey(mapUid)) + preview = Game.modData.MapCache[uid]; + if (preview.Status != MapStatus.Available) + { if (Game.Settings.Game.AllowDownloading) { - Game.DownloadMap(mapUid); + Game.DownloadMap(uid); Game.Debug("A new map has been downloaded..."); } else throw new InvalidOperationException("Server's new map doesn't exist on your system and Downloading turned off"); - map = new Map(Game.modData.AvailableMaps[mapUid].Path); + } // Restore default starting cash if the last map set it to something invalid var pri = Rules.Info["player"].Traits.Get(); - if (!map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash)) + if (!preview.Map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash)) orderManager.IssueOrder(Order.Command("startingcash {0}".F(pri.DefaultCash))); } @@ -596,7 +586,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic LobbyUtils.SetupEditableColorWidget(template, slot, client, orderManager, colorPreview); LobbyUtils.SetupEditableFactionWidget(template, slot, client, orderManager, countryNames); - LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, map.GetSpawnPoints().Length); + LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, preview.SpawnPoints.Count); LobbyUtils.SetupEditableReadyWidget(template, slot, client, orderManager); } else diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs index 8ed0c7b3b5..0e5da7bdfa 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs @@ -130,26 +130,23 @@ namespace OpenRA.Mods.RA.Widgets.Logic color.AttachPanel(colorChooser, onExit); } - public static Dictionary GetSpawnClients(OrderManager orderManager, Map map) + public static Dictionary GetSpawnClients(OrderManager orderManager, MapPreview preview) { - var spawns = map.GetSpawnPoints(); + var spawns = preview.SpawnPoints; return orderManager.LobbyInfo.Clients .Where(c => c.SpawnPoint != 0) - .ToDictionary( - c => spawns[c.SpawnPoint - 1], - c => c); + .ToDictionary(c => spawns[c.SpawnPoint - 1], c => c); } - public static void SelectSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, Map map, MouseInput mi) + public static void SelectSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, MapPreview preview, MouseInput mi) { - if (map == null) - return; if (mi.Button != MouseButton.Left) return; + if (!orderManager.LocalClient.IsObserver && orderManager.LocalClient.State == Session.ClientState.Ready) return; - var selectedSpawn = map.GetSpawnPoints() + var selectedSpawn = preview.SpawnPoints .Select((sp, i) => Pair.New(mapPreview.ConvertToPreview(sp), i)) .Where(a => (a.First - mi.Location).LengthSquared < 64) .Select(a => a.Second + 1) diff --git a/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs index 44628aa683..198e9b1db1 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic [ObjectCreator.UseCtor] internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action onSelect) { - map = Game.modData.AvailableMaps[WidgetUtils.ChooseInitialMap(initialMap)]; + map = Game.modData.MapCache[WidgetUtils.ChooseInitialMap(initialMap)].Map; widget.Get("BUTTON_OK").OnClick = () => { Ui.CloseWindow(); onSelect(map); }; widget.Get("BUTTON_CANCEL").OnClick = () => { Ui.CloseWindow(); onExit(); }; @@ -44,9 +44,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic var gameModeDropdown = widget.GetOrNull("GAMEMODE_FILTER"); if (gameModeDropdown != null) { - var selectableMaps = Game.modData.AvailableMaps.Where(m => m.Value.Selectable).ToList(); + var selectableMaps = Game.modData.MapCache.Where(m => m.Status == MapStatus.Available && m.Map.Selectable); var gameModes = selectableMaps - .GroupBy(m => m.Value.Type) + .GroupBy(m => m.Type) .Select(g => Pair.New(g.Key, g.Count())).ToList(); // 'all game types' extra item @@ -87,8 +87,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic void EnumerateMaps(Action onSelect) { - var maps = Game.modData.AvailableMaps - .Where(kv => kv.Value.Selectable) + var maps = Game.modData.MapCache + .Where(m => m.Status == MapStatus.Available && m.Map.Selectable) + .ToDictionary(m => m.Uid, m => m.Map) .Where(kv => kv.Value.Type == gameMode || gameMode == null) .OrderBy(kv => kv.Value.PlayerCount) .ThenBy(kv => kv.Value.Title); @@ -102,10 +103,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic var titleLabel = item.Get("TITLE"); titleLabel.GetText = () => m.Title; + var preview = Game.modData.MapCache[m.Uid]; var previewWidget = item.Get("PREVIEW"); previewWidget.IgnoreMouseOver = true; previewWidget.IgnoreMouseInput = true; - previewWidget.Map = () => m; + previewWidget.Preview = () => preview; previewWidget.IsVisible = () => previewWidget.RenderBounds.IntersectsWith(scrollpanel.RenderBounds); var previewLoadingWidget = item.GetOrNull("PREVIEW_PLACEHOLDER"); diff --git a/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs index e55fe67f53..a1a23ff6a2 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs @@ -51,7 +51,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic } Replay currentReplay; - Map currentMap; + MapPreview currentMap = MapCache.UnknownMap; void SelectReplay(string filename) { @@ -61,11 +61,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic try { currentReplay = new Replay(filename); - currentMap = currentReplay.Map(); + currentMap = Game.modData.MapCache[currentReplay.LobbyInfo.GlobalSettings.Map]; panel.Get("DURATION").GetText = () => WidgetUtils.FormatTime(currentReplay.Duration); - panel.Get("MAP_PREVIEW").Map = () => currentMap; + panel.Get("MAP_PREVIEW").Preview = () => currentMap; panel.Get("MAP_TITLE").GetText = () => currentMap != null ? currentMap.Title : "(Unknown Map)"; @@ -77,7 +77,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { Log.Write("debug", "Exception while parsing replay: {0}", e); currentReplay = null; - currentMap = null; + currentMap = MapCache.UnknownMap; } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index 7787ea071d..efe0dd4371 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -155,10 +155,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (game == null || game.Players == 0) return ""; - var map = Game.modData.FindMapByUid(game.Map); - - var maxPlayers = map == null ? "?" : (object)map.PlayerCount; - return "{0} / {1}".F(game.Players, maxPlayers); + var map = Game.modData.MapCache[game.Map]; + return "{0} / {1}".F(game.Players, map.PlayerCount == 0 ? "?" : map.PlayerCount.ToString()); } string GetStateLabel(GameServer game) @@ -176,11 +174,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic return "Unknown server state"; } - Map GetMapPreview(GameServer game) - { - return (game == null) ? null : Game.modData.FindMapByUid(game.Map); - } - public static string GenerateModLabel(GameServer s) { Mod mod; @@ -241,20 +234,16 @@ namespace OpenRA.Mods.RA.Widgets.Logic var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => currentServer = game, () => Join(game)); + var map = Game.modData.MapCache[game.Map]; var preview = item.Get("MAP_PREVIEW"); - preview.Map = () => GetMapPreview(game); - preview.IsVisible = () => GetMapPreview(game) != null; + preview.Preview = () => map; var title = item.Get("TITLE"); title.GetText = () => game.Name; // TODO: Use game.MapTitle once the server supports it var maptitle = item.Get("MAP"); - maptitle.GetText = () => - { - var map = Game.modData.FindMapByUid(game.Map); - return map == null ? "Unknown Map" : map.Title; - }; + maptitle.GetText = () => map.Title; // TODO: Use game.MaxPlayers once the server supports it var players = item.Get("PLAYERS"); diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs index c70f39f428..c4d22eaebf 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic Widget panel; Action onCreate; Action onExit; - Map map; + MapPreview preview = MapCache.UnknownMap; bool advertiseOnline; bool allowPortForward; @@ -32,12 +32,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic this.onExit = onExit; var settings = Game.Settings; + preview = Game.modData.MapCache[WidgetUtils.ChooseInitialMap(Game.Settings.Server.Map)]; panel.Get("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; panel.Get("CREATE_BUTTON").OnClick = CreateAndJoin; - map = Game.modData.AvailableMaps[ WidgetUtils.ChooseInitialMap(Game.Settings.Server.Map) ]; - var mapButton = panel.GetOrNull("MAP_BUTTON"); if (mapButton != null) { @@ -45,14 +44,14 @@ namespace OpenRA.Mods.RA.Widgets.Logic { Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() { - { "initialMap", map.Uid }, + { "initialMap", preview.Uid }, { "onExit", () => {} }, - { "onSelect", (Action)(m => map = m) } + { "onSelect", (Action)(m => preview = Game.modData.MapCache[m.Uid]) } }); }; - panel.Get("MAP_PREVIEW").Map = () => map; - panel.Get("MAP_NAME").GetText = () => map.Title; + panel.Get("MAP_PREVIEW").Preview = () => preview; + panel.Get("MAP_NAME").GetText = () => preview.Title; } panel.Get("SERVER_NAME").Text = settings.Server.Name ?? ""; @@ -97,7 +96,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic Game.Settings.Server.ExternalPort = externalPort; Game.Settings.Server.AdvertiseOnline = advertiseOnline; Game.Settings.Server.AllowPortForward = allowPortForward; - Game.Settings.Server.Map = map.Uid; + Game.Settings.Server.Map = preview.Uid; Game.Settings.Server.Password = password; Game.Settings.Save(); diff --git a/OpenRA.Utility/UpgradeRules.cs b/OpenRA.Utility/UpgradeRules.cs index f87cbe2c13..753acd11bd 100644 --- a/OpenRA.Utility/UpgradeRules.cs +++ b/OpenRA.Utility/UpgradeRules.cs @@ -301,7 +301,11 @@ namespace OpenRA.Utility } Console.WriteLine("Processing Maps:"); - foreach (var map in Game.modData.FindMaps().Values) + var maps = Game.modData.MapCache + .Where(m => m.Status == MapStatus.Available) + .Select(m => m.Map); + + foreach (var map in maps) { Console.WriteLine("\t" + map.Path); UpgradeActorRules(engineDate, ref map.Rules, null, 0); From 4c4783262f51e2080391c41c35b18b9d137b60a4 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 14 Mar 2014 17:40:06 +1300 Subject: [PATCH 07/13] Simplify the replay browser code. --- OpenRA.Game/Network/Replay.cs | 41 ---------------- OpenRA.Game/OpenRA.Game.csproj | 1 - .../Widgets/Logic/ReplayBrowserLogic.cs | 49 ++++++++++--------- 3 files changed, 27 insertions(+), 64 deletions(-) delete mode 100644 OpenRA.Game/Network/Replay.cs diff --git a/OpenRA.Game/Network/Replay.cs b/OpenRA.Game/Network/Replay.cs deleted file mode 100644 index a62f628860..0000000000 --- a/OpenRA.Game/Network/Replay.cs +++ /dev/null @@ -1,41 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -using System; - -namespace OpenRA.Network -{ - public class Replay - { - public readonly string Filename; - public readonly int Duration; - public readonly Session LobbyInfo; - - public Replay(string filename) - { - Filename = filename; - - using (var conn = new ReplayConnection(filename)) - { - Duration = conn.TickCount * Game.NetTickScale; - LobbyInfo = conn.LobbyInfo; - } - } - - public Map Map() - { - if (LobbyInfo == null) - return null; - - var map = LobbyInfo.GlobalSettings.Map; - return Game.modData.MapCache[map].Map; - } - } -} diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 0085f53d30..c0d217d017 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -129,7 +129,6 @@ - diff --git a/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs index a1a23ff6a2..38d1c009bb 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ReplayBrowserLogic.cs @@ -19,6 +19,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic public class ReplayBrowserLogic { Widget panel; + MapPreview selectedMap = MapCache.UnknownMap; + string selectedFilename; + string selectedDuration; + string selectedPlayers; + bool selectedValid; [ObjectCreator.UseCtor] public ReplayBrowserLogic(Widget widget, Action onExit, Action onStart) @@ -44,15 +49,16 @@ namespace OpenRA.Mods.RA.Widgets.Logic } var watch = panel.Get("WATCH_BUTTON"); - watch.IsDisabled = () => currentReplay == null || currentMap == null || currentReplay.Duration == 0; + watch.IsDisabled = () => !selectedValid || selectedMap.Status != MapStatus.Available; watch.OnClick = () => { WatchReplay(); onStart(); }; - panel.Get("REPLAY_INFO").IsVisible = () => currentReplay != null; + panel.Get("REPLAY_INFO").IsVisible = () => selectedFilename != null;; + panel.Get("DURATION").GetText = () => selectedDuration; + panel.Get("MAP_PREVIEW").Preview = () => selectedMap; + panel.Get("MAP_TITLE").GetText = () => selectedMap.Title; + panel.Get("PLAYERS").GetText = () => selectedPlayers; } - Replay currentReplay; - MapPreview currentMap = MapCache.UnknownMap; - void SelectReplay(string filename) { if (filename == null) @@ -60,32 +66,31 @@ namespace OpenRA.Mods.RA.Widgets.Logic try { - currentReplay = new Replay(filename); - currentMap = Game.modData.MapCache[currentReplay.LobbyInfo.GlobalSettings.Map]; - - panel.Get("DURATION").GetText = - () => WidgetUtils.FormatTime(currentReplay.Duration); - panel.Get("MAP_PREVIEW").Preview = () => currentMap; - panel.Get("MAP_TITLE").GetText = - () => currentMap != null ? currentMap.Title : "(Unknown Map)"; - - var players = currentReplay.LobbyInfo.Slots - .Count(s => currentReplay.LobbyInfo.ClientInSlot(s.Key) != null); - panel.Get("PLAYERS").GetText = () => players.ToString(); + using (var conn = new ReplayConnection(filename)) + { + selectedFilename = filename; + selectedMap = Game.modData.MapCache[conn.LobbyInfo.GlobalSettings.Map]; + selectedDuration = WidgetUtils.FormatTime(conn.TickCount * Game.NetTickScale); + selectedPlayers = conn.LobbyInfo.Slots + .Count(s => conn.LobbyInfo.ClientInSlot(s.Key) != null) + .ToString(); + selectedValid = conn.TickCount > 0; + } } catch (Exception e) { Log.Write("debug", "Exception while parsing replay: {0}", e); - currentReplay = null; - currentMap = MapCache.UnknownMap; + selectedFilename = null; + selectedValid = false; + selectedMap = MapCache.UnknownMap; } } void WatchReplay() { - if (currentReplay != null) + if (selectedFilename != null) { - Game.JoinReplay(currentReplay.Filename); + Game.JoinReplay(selectedFilename); Ui.CloseWindow(); } } @@ -93,7 +98,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic void AddReplay(ScrollPanelWidget list, string filename, ScrollItemWidget template) { var item = ScrollItemWidget.Setup(template, - () => currentReplay != null && currentReplay.Filename == filename, + () => selectedFilename == filename, () => SelectReplay(filename), () => WatchReplay()); var f = Path.GetFileName(filename); From a501828660d5092041c6db85deb61ba7aa135746 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 14 Mar 2014 18:00:35 +1300 Subject: [PATCH 08/13] Simplify the map chooser code. --- OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 6 +-- .../Widgets/Logic/MapChooserLogic.cs | 48 +++++++++---------- .../Widgets/Logic/ServerCreationLogic.cs | 2 +- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 7c64cbf925..2aedd54815 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -154,10 +154,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic mapButton.IsDisabled = configurationDisabled; mapButton.OnClick = () => { - var onSelect = new Action(m => + var onSelect = new Action(uid => { - orderManager.IssueOrder(Order.Command("map " + m.Uid)); - Game.Settings.Server.Map = m.Uid; + orderManager.IssueOrder(Order.Command("map " + uid)); + Game.Settings.Server.Map = uid; Game.Settings.Save(); }); diff --git a/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs index 198e9b1db1..9e4b829bfc 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/MapChooserLogic.cs @@ -18,21 +18,21 @@ namespace OpenRA.Mods.RA.Widgets.Logic { public class MapChooserLogic { - Map map; + string selectedUid; // May be a subset of available maps if a mode filter is active - Dictionary visibleMaps; + List visibleMaps; ScrollPanelWidget scrollpanel; ScrollItemWidget itemTemplate; string gameMode; [ObjectCreator.UseCtor] - internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action onSelect) + internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action onSelect) { - map = Game.modData.MapCache[WidgetUtils.ChooseInitialMap(initialMap)].Map; + selectedUid = WidgetUtils.ChooseInitialMap(initialMap); - widget.Get("BUTTON_OK").OnClick = () => { Ui.CloseWindow(); onSelect(map); }; + widget.Get("BUTTON_OK").OnClick = () => { Ui.CloseWindow(); onSelect(selectedUid); }; widget.Get("BUTTON_CANCEL").OnClick = () => { Ui.CloseWindow(); onExit(); }; scrollpanel = widget.Get("MAP_LIST"); @@ -75,9 +75,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic { randomMapButton.OnClick = () => { - var kv = visibleMaps.Random(Game.CosmeticRandom); - map = kv.Value; - scrollpanel.ScrollToItem(kv.Key); + var uid = visibleMaps.Random(Game.CosmeticRandom); + selectedUid = uid; + scrollpanel.ScrollToItem(uid); }; randomMapButton.IsDisabled = () => visibleMaps == null || visibleMaps.Count == 0; } @@ -85,25 +85,23 @@ namespace OpenRA.Mods.RA.Widgets.Logic EnumerateMaps(onSelect); } - void EnumerateMaps(Action onSelect) + void EnumerateMaps(Action onSelect) { var maps = Game.modData.MapCache .Where(m => m.Status == MapStatus.Available && m.Map.Selectable) - .ToDictionary(m => m.Uid, m => m.Map) - .Where(kv => kv.Value.Type == gameMode || gameMode == null) - .OrderBy(kv => kv.Value.PlayerCount) - .ThenBy(kv => kv.Value.Title); + .Where(m => m.Type == gameMode || gameMode == null) + .OrderBy(m => m.PlayerCount) + .ThenBy(m => m.Title); scrollpanel.RemoveChildren(); - foreach (var kv in maps) + foreach (var loop in maps) { - var m = kv.Value; - var item = ScrollItemWidget.Setup(kv.Key, itemTemplate, () => m == map, () => map = m, () => { Ui.CloseWindow(); onSelect(m); }); + var preview = loop; + var item = ScrollItemWidget.Setup(preview.Uid, itemTemplate, () => selectedUid == preview.Uid, () => selectedUid = preview.Uid, () => { Ui.CloseWindow(); onSelect(preview.Uid); }); var titleLabel = item.Get("TITLE"); - titleLabel.GetText = () => m.Title; + titleLabel.GetText = () => preview.Title; - var preview = Game.modData.MapCache[m.Uid]; var previewWidget = item.Get("PREVIEW"); previewWidget.IgnoreMouseOver = true; previewWidget.IgnoreMouseInput = true; @@ -116,17 +114,17 @@ namespace OpenRA.Mods.RA.Widgets.Logic var detailsWidget = item.GetOrNull("DETAILS"); if (detailsWidget != null) - detailsWidget.GetText = () => "{0} ({1} players)".F(m.Type, m.PlayerCount); + detailsWidget.GetText = () => "{0} ({1} players)".F(preview.Type, preview.PlayerCount); var authorWidget = item.GetOrNull("AUTHOR"); if (authorWidget != null) - authorWidget.GetText = () => "Created by {0}".F(m.Author); + authorWidget.GetText = () => "Created by {0}".F(preview.Author); var sizeWidget = item.GetOrNull("SIZE"); if (sizeWidget != null) { - var size = m.Bounds.Width + "x" + m.Bounds.Height; - var numberPlayableCells = m.Bounds.Width * m.Bounds.Height; + var size = preview.Bounds.Width + "x" + preview.Bounds.Height; + var numberPlayableCells = preview.Bounds.Width * preview.Bounds.Height; if (numberPlayableCells >= 120 * 120) size += " (Huge)"; else if (numberPlayableCells >= 90 * 90) size += " (Large)"; else if (numberPlayableCells >= 60 * 60) size += " (Medium)"; @@ -137,9 +135,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic scrollpanel.AddChild(item); } - visibleMaps = maps.ToDictionary(kv => kv.Key, kv => kv.Value); - if (visibleMaps.ContainsValue(map)) - scrollpanel.ScrollToItem(visibleMaps.First(m => m.Value == map).Key); + visibleMaps = maps.Select(m => m.Uid).ToList(); + if (visibleMaps.Contains(selectedUid)) + scrollpanel.ScrollToItem(selectedUid); } } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs index c4d22eaebf..f693929bb6 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerCreationLogic.cs @@ -46,7 +46,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic { { "initialMap", preview.Uid }, { "onExit", () => {} }, - { "onSelect", (Action)(m => preview = Game.modData.MapCache[m.Uid]) } + { "onSelect", (Action)(uid => preview = Game.modData.MapCache[uid]) } }); }; From a4a285bef525f31d601565bc889368d3c309fc83 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Thu, 13 Mar 2014 20:00:32 +1300 Subject: [PATCH 09/13] Add data support to Download. --- OpenRA.Game/Download.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/OpenRA.Game/Download.cs b/OpenRA.Game/Download.cs index 5955d3b588..4cd997ae7d 100644 --- a/OpenRA.Game/Download.cs +++ b/OpenRA.Game/Download.cs @@ -52,6 +52,20 @@ namespace OpenRA wc.DownloadFileAsync(new Uri(url), path); } + public Download(string url, Action onProgress, Action onComplete) + { + wc = new WebClient(); + wc.Proxy = null; + + wc.DownloadProgressChanged += (_, a) => onProgress(a); + wc.DownloadDataCompleted += (_, a) => onComplete(a, cancelled); + + Game.OnQuit += Cancel; + wc.DownloadDataCompleted += (_, a) => { Game.OnQuit -= Cancel; }; + + wc.DownloadDataAsync(new Uri(url)); + } + public void Cancel() { Game.OnQuit -= Cancel; From 21063931222ba56bcdde8fa473eed9ebfb447fa4 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 14 Mar 2014 19:08:59 +1300 Subject: [PATCH 10/13] Simplify the server browser code. --- OpenRA.Game/Network/ServerList.cs | 52 ------ OpenRA.Game/OpenRA.Game.csproj | 1 - .../Widgets/Logic/ServerBrowserLogic.cs | 156 ++++++++++++++++-- 3 files changed, 144 insertions(+), 65 deletions(-) delete mode 100644 OpenRA.Game/Network/ServerList.cs diff --git a/OpenRA.Game/Network/ServerList.cs b/OpenRA.Game/Network/ServerList.cs deleted file mode 100644 index 55cff8dc47..0000000000 --- a/OpenRA.Game/Network/ServerList.cs +++ /dev/null @@ -1,52 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation. For more information, - * see COPYING. - */ -#endregion - -using System; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using OpenRA.FileFormats; - -namespace OpenRA.Network -{ - public static class ServerList - { - public static void Query(Action onComplete) - { - var masterServerUrl = Game.Settings.Server.MasterServer; - - new Thread(() => - { - GameServer[] games = null; - try - { - var str = GetData(new Uri(masterServerUrl + "list.php")); - - var yaml = MiniYaml.FromString(str); - - games = yaml.Select(a => FieldLoader.Load(a.Value)) - .Where(gs => gs.Address != null).ToArray(); - } - catch { } - - Game.RunAfterTick(() => onComplete(games)); - }) { IsBackground = true }.Start(); - } - - static string GetData(Uri uri) - { - var wc = new WebClient(); - wc.Proxy = null; - var data = wc.DownloadData(uri); - return Encoding.UTF8.GetString(data); - } - } -} diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index c0d217d017..6d13a70cd9 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -130,7 +130,6 @@ - diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index efe0dd4371..7e09dc7ccd 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -12,6 +12,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Drawing; +using System.Net; +using System.Text; using OpenRA.FileFormats; using OpenRA.Network; using OpenRA.Server; @@ -29,6 +31,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic enum SearchStatus { Fetching, Failed, NoGames, Hidden } SearchStatus searchStatus = SearchStatus.Fetching; + Download currentQuery; + Widget panel, serverList; bool showWaiting = true; bool showEmpty = true; @@ -39,7 +43,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic { switch (searchStatus) { - case SearchStatus.Fetching: return "Fetching game list..."; case SearchStatus.Failed: return "Failed to contact master server."; case SearchStatus.NoGames: return "No games found."; default: return ""; @@ -49,16 +52,18 @@ namespace OpenRA.Mods.RA.Widgets.Logic [ObjectCreator.UseCtor] public ServerBrowserLogic(Widget widget, Action onStart, Action onExit) { - var panel = widget; + panel = widget; this.onStart = onStart; this.onExit = onExit; - var sl = panel.Get("SERVER_LIST"); + serverList = panel.Get("SERVER_LIST"); + serverTemplate = serverList.Get("SERVER_TEMPLATE"); // Menu buttons var refreshButton = panel.Get("REFRESH_BUTTON"); refreshButton.IsDisabled = () => searchStatus == SearchStatus.Fetching; - refreshButton.OnClick = () => ServerList.Query(games => RefreshServerList(panel, games)); + refreshButton.GetText = () => searchStatus == SearchStatus.Fetching ? "Refreshing..." : "Refresh"; + refreshButton.OnClick = RefreshServerList; panel.Get("DIRECTCONNECT_BUTTON").OnClick = OpenDirectConnectPanel; panel.Get("CREATE_BUTTON").OnClick = OpenCreateServerPanel; @@ -69,9 +74,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic panel.Get("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; - // Server list - serverTemplate = sl.Get("SERVER_TEMPLATE"); - // Display the progress label over the server list // The text is only visible when the list is empty var progressText = panel.Get("PROGRESS_LABEL"); @@ -82,33 +84,163 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (showWaitingCheckbox != null) { showWaitingCheckbox.IsChecked = () => showWaiting; - showWaitingCheckbox.OnClick = () => { showWaiting ^= true; ServerList.Query(games => RefreshServerList(panel, games)); }; + showWaitingCheckbox.OnClick = () => { showWaiting ^= true; RefreshServerList(); }; } var showEmptyCheckbox = panel.GetOrNull("EMPTY"); if (showEmptyCheckbox != null) { showEmptyCheckbox.IsChecked = () => showEmpty; - showEmptyCheckbox.OnClick = () => { showEmpty ^= true; ServerList.Query(games => RefreshServerList(panel, games)); }; + showEmptyCheckbox.OnClick = () => { showEmpty ^= true; RefreshServerList(); }; } var showAlreadyStartedCheckbox = panel.GetOrNull("ALREADY_STARTED"); if (showAlreadyStartedCheckbox != null) { showAlreadyStartedCheckbox.IsChecked = () => showStarted; - showAlreadyStartedCheckbox.OnClick = () => { showStarted ^= true; ServerList.Query(games => RefreshServerList(panel, games)); }; + showAlreadyStartedCheckbox.OnClick = () => { showStarted ^= true; RefreshServerList(); }; } var showIncompatibleCheckbox = panel.GetOrNull("INCOMPATIBLE_VERSION"); if (showIncompatibleCheckbox != null) { showIncompatibleCheckbox.IsChecked = () => showIncompatible; - showIncompatibleCheckbox.OnClick = () => { showIncompatible ^= true; ServerList.Query(games => RefreshServerList(panel, games)); }; + showIncompatibleCheckbox.OnClick = () => { showIncompatible ^= true; RefreshServerList(); }; } // Game.LoadWidget(null, "SERVERBROWSER_IRC", panel.Get("IRC_ROOT"), new WidgetArgs()); + RefreshServerList(); + } - ServerList.Query(games => RefreshServerList(panel, games)); + void RefreshServerList() + { + // Query in progress + if (currentQuery != null) + return; + + searchStatus = SearchStatus.Fetching; + + Action onComplete = (i, cancelled) => + { + currentQuery = null; + + if (i.Error != null || cancelled) + { + RefreshServerListInner(null); + return; + } + + var data = Encoding.UTF8.GetString(i.Result); + var yaml = MiniYaml.FromString(data); + + var games = yaml.Select(a => FieldLoader.Load(a.Value)) + .Where(gs => gs.Address != null); + + RefreshServerListInner(games); + Game.RunAfterTick(() => RefreshServerListInner(games)); + }; + + currentQuery = new Download(Game.Settings.Server.MasterServer + "list.php", _ => {}, onComplete); + } + + public void RefreshServerListInner(IEnumerable games) + { + List rows = new List(); + + Game.RunAfterTick(() => + { + serverList.RemoveChildren(); + currentServer = null; + + if (games == null) + { + searchStatus = SearchStatus.Failed; + return; + } + + if (!games.Any()) + { + searchStatus = SearchStatus.NoGames; + return; + } + + currentServer = games.FirstOrDefault(); + searchStatus = SearchStatus.Hidden; + + foreach (var row in rows) + serverList.AddChild(row); + }); + + foreach (var loop in games.OrderByDescending(g => g.CanJoin()).ThenByDescending(g => g.Players)) + { + var game = loop; + if (game == null) + continue; + + var canJoin = game.CanJoin(); + + var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => currentServer = game, () => Join(game)); + + var map = Game.modData.MapCache[game.Map]; + var preview = item.GetOrNull("MAP_PREVIEW"); + if (preview != null) + preview.Preview = () => map; + + var title = item.GetOrNull("TITLE"); + if (title != null) + { + title.GetText = () => game.Name; + title.GetColor = () => canJoin ? title.TextColor : Color.Gray; + } + + var maptitle = item.GetOrNull("MAP"); + if (title != null) + { + maptitle.GetText = () => map.Title; + maptitle.GetColor = () => canJoin ? maptitle.TextColor : Color.Gray; + } + + var players = item.GetOrNull("PLAYERS"); + if (players != null) + { + players.GetText = () => "{0} / {1}".F(game.Players, map.PlayerCount); + players.GetColor = () => canJoin ? players.TextColor : Color.Gray; + } + + var state = item.GetOrNull("STATE"); + if (state != null) + { + state.GetText = () => GetStateLabel(game); + state.GetColor = () => canJoin ? state.TextColor : Color.Gray; + } + + var ip = item.GetOrNull("IP"); + if (ip != null) + { + ip.GetText = () => game.Address; + ip.GetColor = () => canJoin ? ip.TextColor : Color.Gray; + } + + var version = item.GetOrNull("VERSION"); + if (version != null) + { + version.GetText = () => GenerateModLabel(game); + version.IsVisible = () => !game.CompatibleVersion(); + version.GetColor = () => canJoin ? version.TextColor : Color.Gray; + } + + var location = item.GetOrNull("LOCATION"); + if (location != null) + { + var cachedServerLocation = LobbyUtils.LookupCountry(game.Address.Split(':')[0]); + location.GetText = () => cachedServerLocation; + location.IsVisible = () => game.CompatibleVersion(); + location.GetColor = () => canJoin ? location.TextColor : Color.Gray; + } + + if (!Filtered(game)) + rows.Add(item); + } } void OpenLobby() From 8fc78603bcd943f321420ba5a72392e13b108870 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 15 Mar 2014 19:12:00 +1300 Subject: [PATCH 11/13] Support custom map previews. Add map.png to the map archive. --- OpenRA.Game/Map.cs | 4 ++++ OpenRA.Game/MapCache.cs | 2 +- OpenRA.Game/MapPreview.cs | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/OpenRA.Game/Map.cs b/OpenRA.Game/Map.cs index 1a9fbee044..bf1f26d78a 100644 --- a/OpenRA.Game/Map.cs +++ b/OpenRA.Game/Map.cs @@ -70,6 +70,7 @@ namespace OpenRA public string Author; public string Tileset; public bool AllowStartUnitConfig = true; + public Bitmap CustomPreview; [FieldLoader.LoadUsing("LoadOptions")] public MapOptions Options; @@ -226,6 +227,9 @@ namespace OpenRA Save(path); Uid = ComputeHash(); + + if (Container.Exists("map.png")) + CustomPreview = new Bitmap(Container.GetContent("map.png")); } public CPos[] GetSpawnPoints() diff --git a/OpenRA.Game/MapCache.cs b/OpenRA.Game/MapCache.cs index fa751824f1..2bc12027e5 100755 --- a/OpenRA.Game/MapCache.cs +++ b/OpenRA.Game/MapCache.cs @@ -107,7 +107,7 @@ namespace OpenRA // the next render cycle. // (d) Any partially written bytes from the next minimap is in an // unallocated area, and will be committed in the next cycle. - var bitmap = Minimap.RenderMapPreview(p.Map, true); + var bitmap = p.CustomPreview ?? Minimap.RenderMapPreview(p.Map, true); p.Minimap = sheetBuilder.Add(bitmap); lock (syncRoot) diff --git a/OpenRA.Game/MapPreview.cs b/OpenRA.Game/MapPreview.cs index 1bb8352d89..de0e3d1425 100755 --- a/OpenRA.Game/MapPreview.cs +++ b/OpenRA.Game/MapPreview.cs @@ -33,6 +33,7 @@ namespace OpenRA public int PlayerCount { get; private set; } public List SpawnPoints { get; private set; } public Rectangle Bounds { get; private set; } + public Bitmap CustomPreview { get; private set; } public Map Map { get; private set; } public MapStatus Status { get; private set; } @@ -85,6 +86,7 @@ namespace OpenRA PlayerCount = m.Players.Count(x => x.Value.Playable); Bounds = m.Bounds; SpawnPoints = m.GetSpawnPoints().ToList(); + CustomPreview = m.CustomPreview; Status = MapStatus.Available; } } From f528ff5c6f79b2a3680b33bf532d203e0761c648 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 16 Mar 2014 10:06:49 +1300 Subject: [PATCH 12/13] Update changelog. --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d21165516a..8e40dd1188 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -117,6 +117,7 @@ NEW: Updated RenderBuildingCharge so you can set a custom charge sequence name (default is active). Added IEffectiveOwner interface for traits/logic that temporarily alter an actor's apparent owner. Renamed Spy trait to Disguise, SpyToolTip trait to DisguiseToolTip, and RenderSpy trait to RenderDisguise. + Overhauled the internal map management and preview generation code. Server: Message of the day is now shared between all mods and a default motd.txt gets created in the user directory. Asset Browser: @@ -137,6 +138,7 @@ NEW: If you spot black tiles in your Dune 2000 ARRAKIS maps, replace them with the remaining sand and rock tiles. Go to Map → Fix Open Areas to randomize them. The TestFile check in mod.yaml has been renamed to TestFiles (plural!) and now supports a comma-separated list of assets that are required to load the game. DisabledOverlay has been split from RenderBuilding. Use it together with RequiresPower and CanPowerDown for buildings or directly with Husk. + Added support for custom map previews that replace the minimap in the game browser and lobby. Add map.png inside the map package. Packaging: Removed portable install option from Windows installer as the game left without write access breaks content download and error log generation. Added HTML documentation to the Windows installer. From 8cd643e06a64c2ac7f9eea967e1f2deed2e9116f Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 16 Mar 2014 18:12:38 +1300 Subject: [PATCH 13/13] Fix editor compilation. --- OpenRA.Editor/MapSelect.cs | 2 +- OpenRA.Game/MapCache.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenRA.Editor/MapSelect.cs b/OpenRA.Editor/MapSelect.cs index 68d1514497..4f2c3613b0 100644 --- a/OpenRA.Editor/MapSelect.cs +++ b/OpenRA.Editor/MapSelect.cs @@ -45,7 +45,7 @@ namespace OpenRA.Editor if (DirectoryIsEmpty(MapFolderPath)) return; - foreach (var map in ModData.FindMapsIn(MapFolderPath)) + foreach (var map in MapCache.FindMapsIn(MapFolderPath)) { ListViewItem map1 = new ListViewItem(); map1.Tag = map; diff --git a/OpenRA.Game/MapCache.cs b/OpenRA.Game/MapCache.cs index 2bc12027e5..cd0beec736 100755 --- a/OpenRA.Game/MapCache.cs +++ b/OpenRA.Game/MapCache.cs @@ -57,7 +57,7 @@ namespace OpenRA } } - static IEnumerable FindMapsIn(string dir) + public static IEnumerable FindMapsIn(string dir) { string[] noMaps = { };