diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 63eeb34af5..a7beed5d53 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -173,6 +173,17 @@ namespace OpenRA.Network } } + public class LobbyOptionState + { + public bool Locked; + public string Value; + public string PreferredValue; + + public LobbyOptionState() { } + + public bool Enabled { get { return Value == "True"; } } + } + public class Global { public string ServerName; @@ -180,32 +191,52 @@ namespace OpenRA.Network public int Timestep = 40; public int OrderLatency = 3; // net tick frames (x 120 = ms) public int RandomSeed = 0; - public bool AllowCheats = false; public bool AllowSpectators = true; - public bool Dedicated; public string Difficulty; - public bool Crates = true; - public bool Creeps = true; - public bool Shroud = true; - public bool Fog = true; - public bool AllyBuildRadius = true; - public int StartingCash = 5000; - public string TechLevel; - public string StartingUnitsClass; public string GameSpeedType = "default"; - public bool ShortGame = true; public bool AllowVersionMismatch; public string GameUid; public bool DisableSingleplayer; + [FieldLoader.Ignore] + public Dictionary LobbyOptions = new Dictionary(); + public static Global Deserialize(MiniYaml data) { - return FieldLoader.Load(data); + var gs = FieldLoader.Load(data); + + var optionsNode = data.Nodes.FirstOrDefault(n => n.Key == "Options"); + if (optionsNode != null) + foreach (var n in optionsNode.Value.Nodes) + gs.LobbyOptions[n.Key] = FieldLoader.Load(n.Value); + + return gs; } public MiniYamlNode Serialize() { - return new MiniYamlNode("GlobalSettings", FieldSaver.Save(this)); + var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this)); + var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value))).ToList(); + data.Value.Nodes.Add(new MiniYamlNode("Options", new MiniYaml(null, options))); + return data; + } + + public bool OptionOrDefault(string id, bool def) + { + LobbyOptionState option; + if (LobbyOptions.TryGetValue(id, out option)) + return option.Enabled; + + return def; + } + + public string OptionOrDefault(string id, string def) + { + LobbyOptionState option; + if (LobbyOptions.TryGetValue(id, out option)) + return option.Value; + + return def; } } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 41ab03c8a1..b430fb34ca 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -149,7 +149,6 @@ namespace OpenRA.Server RandomSeed = randomSeed, Map = settings.Map, ServerName = settings.Name, - Dedicated = dedicated, DisableSingleplayer = settings.DisableSinglePlayer, } }; diff --git a/OpenRA.Game/Traits/Player/DeveloperMode.cs b/OpenRA.Game/Traits/Player/DeveloperMode.cs index e3a1271006..6e927c24d9 100644 --- a/OpenRA.Game/Traits/Player/DeveloperMode.cs +++ b/OpenRA.Game/Traits/Player/DeveloperMode.cs @@ -9,10 +9,12 @@ */ #endregion +using System.Collections.Generic; + namespace OpenRA.Traits { [Desc("Attach this to the player actor.")] - public class DeveloperModeInfo : ITraitInfo + public class DeveloperModeInfo : ITraitInfo, ILobbyOptions { [Desc("Default value of the developer mode checkbox in the lobby.")] public bool Enabled = false; @@ -56,6 +58,11 @@ namespace OpenRA.Traits [Desc("Enable the actor tags overlay by default.")] public bool ShowActorTags; + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) + { + yield return new LobbyBooleanOption("cheats", "Debug Menu", Enabled, Locked); + } + public object Create(ActorInitializer init) { return new DeveloperMode(this); } } @@ -106,7 +113,8 @@ namespace OpenRA.Traits void INotifyCreated.Created(Actor self) { - Enabled = self.World.LobbyInfo.GlobalSettings.AllowCheats || self.World.LobbyInfo.IsSinglePlayer; + Enabled = self.World.LobbyInfo.IsSinglePlayer || self.World.LobbyInfo.GlobalSettings + .OptionOrDefault("cheats", info.Enabled); } public void ResolveOrder(Actor self, Order order) diff --git a/OpenRA.Game/Traits/Player/PlayerResources.cs b/OpenRA.Game/Traits/Player/PlayerResources.cs index 1b32342a2c..d8801772f3 100644 --- a/OpenRA.Game/Traits/Player/PlayerResources.cs +++ b/OpenRA.Game/Traits/Player/PlayerResources.cs @@ -10,11 +10,12 @@ #endregion using System; +using System.Collections.Generic; using System.Linq; namespace OpenRA.Traits { - public class PlayerResourcesInfo : ITraitInfo + public class PlayerResourcesInfo : ITraitInfo, ILobbyOptions { [Desc("Starting cash options that are available in the lobby options.")] public readonly int[] SelectableCash = { 2500, 5000, 10000, 20000 }; @@ -31,6 +32,14 @@ namespace OpenRA.Traits [Desc("Delay (in ticks) during which warnings will be muted.")] public readonly int InsufficientFundsNotificationDelay = 750; + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) + { + var startingCash = SelectableCash.ToDictionary(c => c.ToString(), c => "$" + c.ToString()); + + if (startingCash.Any()) + yield return new LobbyOption("startingcash", "Starting Cash", new ReadOnlyDictionary(startingCash), DefaultCash.ToString(), DefaultCashLocked); + } + public object Create(ActorInitializer init) { return new PlayerResources(init.Self, this); } } @@ -46,7 +55,11 @@ namespace OpenRA.Traits this.info = info; owner = self.Owner; - Cash = self.World.LobbyInfo.GlobalSettings.StartingCash; + var startingCash = self.World.LobbyInfo.GlobalSettings + .OptionOrDefault("startingcash", info.DefaultCash.ToString()); + + if (!int.TryParse(startingCash, out Cash)) + Cash = info.DefaultCash; } [Sync] public int Cash; diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index be07eb8eab..d0a4e40701 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -412,4 +412,50 @@ namespace OpenRA.Traits public interface IRulesetLoaded { void RulesetLoaded(Ruleset rules, TInfo info); } public interface IRulesetLoaded : IRulesetLoaded, ITraitInfoInterface { } + + [RequireExplicitImplementation] + public interface ILobbyOptions : ITraitInfoInterface + { + IEnumerable LobbyOptions(Ruleset rules); + } + + public class LobbyOption + { + public readonly string Id; + public readonly string Name; + public readonly IReadOnlyDictionary Values; + public readonly string DefaultValue; + public readonly bool Locked; + + public LobbyOption(string id, string name, IReadOnlyDictionary values, string defaultValue, bool locked) + { + Id = id; + Name = name; + Values = values; + DefaultValue = defaultValue; + Locked = locked; + } + + public virtual string ValueChangedMessage(string playerName, string newValue) + { + return playerName + " changed " + Name + " to " + Values[newValue] + "."; + } + } + + public class LobbyBooleanOption : LobbyOption + { + static readonly Dictionary BoolValues = new Dictionary() + { + { true.ToString(), "enabled" }, + { false.ToString(), "disabled" } + }; + + public LobbyBooleanOption(string id, string name, bool defaultValue, bool locked) + : base(id, name, new ReadOnlyDictionary(BoolValues), defaultValue.ToString(), locked) { } + + public override string ValueChangedMessage(string playerName, string newValue) + { + return playerName + " " + BoolValues[newValue] + " " + Name + "."; + } + } } diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index 874a94d31a..f33fbdca83 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -12,11 +12,12 @@ using System; using System.Collections.Generic; using System.Linq; +using OpenRA.Network; namespace OpenRA.Traits { [Desc("Required for shroud and fog visibility checks. Add this to the player actor.")] - public class ShroudInfo : ITraitInfo + public class ShroudInfo : ITraitInfo, ILobbyOptions { [Desc("Default value of the fog checkbox in the lobby.")] public bool FogEnabled = true; @@ -30,7 +31,13 @@ namespace OpenRA.Traits [Desc("Prevent the explore map enabled state from being changed in the lobby.")] public bool ExploredMapLocked = false; - public object Create(ActorInitializer init) { return new Shroud(init.Self); } + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) + { + yield return new LobbyBooleanOption("explored", "Explored Map", ExploredMapEnabled, ExploredMapLocked); + yield return new LobbyBooleanOption("fog", "Fog of War", FogEnabled, FogLocked); + } + + public object Create(ActorInitializer init) { return new Shroud(init.Self, this); } } public class Shroud : ISync, INotifyCreated @@ -38,6 +45,7 @@ namespace OpenRA.Traits public event Action> CellsChanged; readonly Actor self; + readonly ShroudInfo info; readonly Map map; readonly CellLayer visibleCount; @@ -72,9 +80,10 @@ namespace OpenRA.Traits public int Hash { get; private set; } - public Shroud(Actor self) + public Shroud(Actor self, ShroudInfo info) { this.self = self; + this.info = info; map = self.World.Map; visibleCount = new CellLayer(map); @@ -84,9 +93,11 @@ namespace OpenRA.Traits void INotifyCreated.Created(Actor self) { - fogEnabled = self.World.LobbyInfo.GlobalSettings.Fog; - var shroudEnabled = self.World.LobbyInfo.GlobalSettings.Shroud; - if (!shroudEnabled) + var gs = self.World.LobbyInfo.GlobalSettings; + fogEnabled = gs.OptionOrDefault("fog", info.FogEnabled); + + var exploreMap = gs.OptionOrDefault("explored", info.ExploredMapEnabled); + if (exploreMap) self.World.AddFrameEndTask(w => ExploreAll()); } diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index 1f3b156171..7375ffd4de 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -424,74 +424,43 @@ namespace OpenRA.Mods.Common.Server return true; } }, - { "allowcheats", + { "option", s => { if (!client.IsAdmin) { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); + server.SendOrderTo(conn, "Message", "Only the host can change the configuration."); return true; } - var devMode = server.Map.Rules.Actors["player"].TraitInfo(); - if (devMode.Locked) + var options = server.Map.Rules.Actors["player"].TraitInfos() + .Concat(server.Map.Rules.Actors["world"].TraitInfos()) + .SelectMany(t => t.LobbyOptions(server.Map.Rules)) + .ToDictionary(o => o.Id, o => o); + + var split = s.Split(' '); + LobbyOption option; + if (split.Length < 2 || !options.TryGetValue(split[0], out option) || + !option.Values.ContainsKey(split[1])) { - server.SendOrderTo(conn, "Message", "Map has disabled cheat configuration."); + server.SendOrderTo(conn, "Message", "Invalid configuration command."); return true; } - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllowCheats); + if (option.Locked) + { + server.SendOrderTo(conn, "Message", "{0} cannot be changed.".F(option.Name)); + return true; + } + + var oo = server.LobbyInfo.GlobalSettings.LobbyOptions[option.Id]; + if (oo.Value == split[1]) + return true; + + oo.Value = oo.PreferredValue = split[1]; + server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} {1} the Debug Menu." - .F(client.Name, server.LobbyInfo.GlobalSettings.AllowCheats ? "enabled" : "disabled")); - - return true; - } - }, - { "shroud", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var shroud = server.Map.Rules.Actors["player"].TraitInfo(); - if (shroud.ExploredMapLocked) - { - server.SendOrderTo(conn, "Message", "Map has disabled shroud configuration."); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Shroud); - server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} {1} Explored map." - .F(client.Name, server.LobbyInfo.GlobalSettings.Shroud ? "disabled" : "enabled")); - - return true; - } - }, - { "fog", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var shroud = server.Map.Rules.Actors["player"].TraitInfo(); - if (shroud.FogLocked) - { - server.SendOrderTo(conn, "Message", "Map has disabled fog configuration."); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Fog); - server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} {1} Fog of War." - .F(client.Name, server.LobbyInfo.GlobalSettings.Fog ? "enabled" : "disabled")); + server.SendMessage(option.ValueChangedMessage(client.Name, split[1])); return true; } @@ -537,78 +506,6 @@ namespace OpenRA.Mods.Common.Server return true; } }, - { "crates", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var crateSpawner = server.Map.Rules.Actors["world"].TraitInfoOrDefault(); - if (crateSpawner == null || crateSpawner.Locked) - { - server.SendOrderTo(conn, "Message", "Map has disabled crate configuration."); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Crates); - server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} {1} Crates." - .F(client.Name, server.LobbyInfo.GlobalSettings.Crates ? "enabled" : "disabled")); - - return true; - } - }, - { "creeps", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var mapCreeps = server.Map.Rules.Actors["world"].TraitInfoOrDefault(); - if (mapCreeps == null || mapCreeps.Locked) - { - server.SendOrderTo(conn, "Message", "Map has disabled Creeps spawning configuration."); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.Creeps); - server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} {1} Creeps spawning." - .F(client.Name, server.LobbyInfo.GlobalSettings.Creeps ? "enabled" : "disabled")); - - return true; - } - }, - { "allybuildradius", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var mapBuildRadius = server.Map.Rules.Actors["world"].TraitInfoOrDefault(); - if (mapBuildRadius == null || mapBuildRadius.AllyBuildRadiusLocked) - { - server.SendOrderTo(conn, "Message", "Map has disabled ally build radius configuration."); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.AllyBuildRadius); - server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} {1} Build off Allies' ConYards." - .F(client.Name, server.LobbyInfo.GlobalSettings.AllyBuildRadius ? "enabled" : "disabled")); - - return true; - } - }, { "difficulty", s => { @@ -642,110 +539,6 @@ namespace OpenRA.Mods.Common.Server return true; } }, - { "startingunits", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var startingUnits = server.Map.Rules.Actors["world"].TraitInfoOrDefault(); - if (startingUnits == null || startingUnits.Locked) - { - server.SendOrderTo(conn, "Message", "Map has disabled start unit configuration."); - return true; - } - - var startUnitsInfo = server.Map.Rules.Actors["world"].TraitInfos(); - var selectedClass = startUnitsInfo.Where(u => u.Class == s).FirstOrDefault(); - if (selectedClass == null) - { - server.SendOrderTo(conn, "Message", "Invalid starting units option selected: {0}".F(s)); - server.SendOrderTo(conn, "Message", "Supported values: {0}".F(startUnitsInfo.Select(su => su.ClassName).JoinWith(", "))); - return true; - } - - if (server.LobbyInfo.GlobalSettings.StartingUnitsClass == selectedClass.Class) - return true; - - server.LobbyInfo.GlobalSettings.StartingUnitsClass = selectedClass.Class; - server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} changed Starting Units to {1}.".F(client.Name, selectedClass.ClassName)); - - return true; - } - }, - { "startingcash", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var playerResources = server.Map.Rules.Actors["player"].TraitInfo(); - if (playerResources.DefaultCashLocked) - { - server.SendOrderTo(conn, "Message", "Map has disabled cash configuration."); - return true; - } - - var startingCashOptions = playerResources.SelectableCash; - var requestedCash = Exts.ParseIntegerInvariant(s); - if (!startingCashOptions.Contains(requestedCash)) - { - server.SendOrderTo(conn, "Message", "Invalid starting cash value selected: {0}".F(s)); - server.SendOrderTo(conn, "Message", "Supported values: {0}".F(startingCashOptions.JoinWith(", "))); - return true; - } - - if (server.LobbyInfo.GlobalSettings.StartingCash == requestedCash) - return true; - - server.LobbyInfo.GlobalSettings.StartingCash = requestedCash; - server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} changed Starting Cash to ${1}.".F(client.Name, requestedCash)); - - return true; - } - }, - { "techlevel", - s => - { - if (server.LobbyInfo.GlobalSettings.TechLevel == s) - return true; - - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var mapOptions = server.Map.Rules.Actors["world"].TraitInfo(); - if (mapOptions.TechLevelLocked) - { - server.SendOrderTo(conn, "Message", "Map has disabled Tech configuration."); - return true; - } - - var techlevels = server.Map.Rules.Actors["player"].TraitInfos().Select(t => t.Name); - if (!techlevels.Contains(s)) - { - server.SendOrderTo(conn, "Message", "Invalid tech level selected: {0}".F(s)); - server.SendOrderTo(conn, "Message", "Supported values: {0}".F(techlevels.JoinWith(", "))); - return true; - } - - server.LobbyInfo.GlobalSettings.TechLevel = s; - server.SyncLobbyInfo(); - server.SendMessage("{0} changed Tech Level to {1}.".F(client.Name, s)); - - return true; - } - }, { "gamespeed", s => { @@ -975,30 +768,6 @@ namespace OpenRA.Mods.Common.Server return true; } }, - { "shortgame", - s => - { - if (!client.IsAdmin) - { - server.SendOrderTo(conn, "Message", "Only the host can set that option."); - return true; - } - - var mapOptions = server.Map.Rules.Actors["world"].TraitInfo(); - if (mapOptions.ShortGameLocked) - { - server.SendOrderTo(conn, "Message", "Map has disabled short game configuration."); - return true; - } - - bool.TryParse(s, out server.LobbyInfo.GlobalSettings.ShortGame); - server.SyncLobbyGlobalSettings(); - server.SendMessage("{0} {1} Short Game." - .F(client.Name, server.LobbyInfo.GlobalSettings.ShortGame ? "enabled" : "disabled")); - - return true; - } - }, { "sync_lobby", s => { @@ -1067,32 +836,36 @@ namespace OpenRA.Mods.Common.Server public static void LoadMapSettings(Session.Global gs, Ruleset rules) { - var devMode = rules.Actors["player"].TraitInfo(); - gs.AllowCheats = devMode.Enabled; + var options = rules.Actors["player"].TraitInfos() + .Concat(rules.Actors["world"].TraitInfos()) + .SelectMany(t => t.LobbyOptions(rules)); - var crateSpawner = rules.Actors["world"].TraitInfoOrDefault(); - gs.Crates = crateSpawner != null && crateSpawner.Enabled; + foreach (var o in options) + { + var value = o.DefaultValue; + var preferredValue = o.DefaultValue; + Session.LobbyOptionState state; + if (gs.LobbyOptions.TryGetValue(o.Id, out state)) + { + // Propagate old state on map change + if (!o.Locked) + { + if (o.Values.Keys.Contains(state.PreferredValue)) + value = state.PreferredValue; + else if (o.Values.Keys.Contains(state.Value)) + value = state.Value; + } - var shroud = rules.Actors["player"].TraitInfo(); - gs.Fog = shroud.FogEnabled; - gs.Shroud = !shroud.ExploredMapEnabled; + preferredValue = state.PreferredValue; + } + else + state = new Session.LobbyOptionState(); - var resources = rules.Actors["player"].TraitInfo(); - gs.StartingCash = resources.DefaultCash; - - var startingUnits = rules.Actors["world"].TraitInfoOrDefault(); - gs.StartingUnitsClass = startingUnits == null ? "none" : startingUnits.StartingUnitsClass; - - var mapBuildRadius = rules.Actors["world"].TraitInfoOrDefault(); - gs.AllyBuildRadius = mapBuildRadius != null && mapBuildRadius.AllyBuildRadiusEnabled; - - var mapCreeps = rules.Actors["world"].TraitInfoOrDefault(); - gs.Creeps = mapCreeps != null && mapCreeps.Enabled; - - var mapOptions = rules.Actors["world"].TraitInfo(); - gs.ShortGame = mapOptions.ShortGameEnabled; - gs.TechLevel = mapOptions.TechLevel; - gs.Difficulty = mapOptions.Difficulty ?? mapOptions.Difficulties.FirstOrDefault(); + state.Locked = o.Locked; + state.Value = value; + state.PreferredValue = preferredValue; + gs.LobbyOptions[o.Id] = state; + } } static HSLColor SanitizePlayerColor(S server, HSLColor askedColor, int playerIndex, Connection connectionToEcho = null) diff --git a/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs b/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs index c6a34d23ba..1ce348c8cb 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs @@ -13,6 +13,7 @@ using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Network; using OpenRA.Server; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Server { @@ -26,40 +27,17 @@ namespace OpenRA.Mods.Common.Server var defaults = new Session.Global(); LobbyCommands.LoadMapSettings(defaults, server.Map.Rules); - if (server.LobbyInfo.GlobalSettings.AllowCheats != defaults.AllowCheats) - server.SendOrderTo(conn, "Message", "Allow Cheats: {0}".F(server.LobbyInfo.GlobalSettings.AllowCheats)); + var options = server.Map.Rules.Actors["player"].TraitInfos() + .Concat(server.Map.Rules.Actors["world"].TraitInfos()) + .SelectMany(t => t.LobbyOptions(server.Map.Rules)) + .ToDictionary(o => o.Id, o => o); - if (server.LobbyInfo.GlobalSettings.Shroud != defaults.Shroud) - server.SendOrderTo(conn, "Message", "Explored map: {0}".F(!server.LobbyInfo.GlobalSettings.Shroud)); - - if (server.LobbyInfo.GlobalSettings.Fog != defaults.Fog) - server.SendOrderTo(conn, "Message", "Fog of war: {0}".F(server.LobbyInfo.GlobalSettings.Fog)); - - if (server.LobbyInfo.GlobalSettings.Crates != defaults.Crates) - server.SendOrderTo(conn, "Message", "Crates Appear: {0}".F(server.LobbyInfo.GlobalSettings.Crates)); - - if (server.LobbyInfo.GlobalSettings.Creeps != defaults.Creeps) - server.SendOrderTo(conn, "Message", "Creeps Spawn: {0}".F(server.LobbyInfo.GlobalSettings.Creeps)); - - if (server.LobbyInfo.GlobalSettings.AllyBuildRadius != defaults.AllyBuildRadius) - server.SendOrderTo(conn, "Message", "Build off Ally ConYards: {0}".F(server.LobbyInfo.GlobalSettings.AllyBuildRadius)); - - if (server.LobbyInfo.GlobalSettings.StartingUnitsClass != defaults.StartingUnitsClass) + foreach (var kv in server.LobbyInfo.GlobalSettings.LobbyOptions) { - var startUnitsInfo = server.Map.Rules.Actors["world"].TraitInfos(); - var selectedClass = startUnitsInfo.Where(u => u.Class == server.LobbyInfo.GlobalSettings.StartingUnitsClass).Select(u => u.ClassName).FirstOrDefault(); - var className = selectedClass != null ? selectedClass : server.LobbyInfo.GlobalSettings.StartingUnitsClass; - server.SendOrderTo(conn, "Message", "Starting Units: {0}".F(className)); + Session.LobbyOptionState def; + if (!defaults.LobbyOptions.TryGetValue(kv.Key, out def) || kv.Value.Value != def.Value) + server.SendOrderTo(conn, "Message", options[kv.Key].Name + ": " + kv.Value.Value); } - - if (server.LobbyInfo.GlobalSettings.StartingCash != defaults.StartingCash) - server.SendOrderTo(conn, "Message", "Starting Cash: ${0}".F(server.LobbyInfo.GlobalSettings.StartingCash)); - - if (server.LobbyInfo.GlobalSettings.TechLevel != defaults.TechLevel) - server.SendOrderTo(conn, "Message", "Tech Level: {0}".F(server.LobbyInfo.GlobalSettings.TechLevel)); - - if (server.LobbyInfo.GlobalSettings.ShortGame != defaults.ShortGame) - server.SendOrderTo(conn, "Message", "Short Game: {0}".F(server.LobbyInfo.GlobalSettings.ShortGame)); } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs index bf7470df0b..9c4c140544 100644 --- a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs @@ -54,7 +54,8 @@ namespace OpenRA.Mods.Common.Traits var map = init.World.Map; // Explore map-placed actors if the "Explore Map" option is enabled - var exploredMap = !init.World.LobbyInfo.GlobalSettings.Shroud; + var shroudInfo = init.World.Map.Rules.Actors["player"].TraitInfo(); + var exploredMap = init.World.LobbyInfo.GlobalSettings.OptionOrDefault("explored", shroudInfo.ExploredMapEnabled); startsRevealed = exploredMap && init.Contains() && !init.Contains(); var footprintCells = FootprintUtils.FrozenUnderFogTiles(init.Self).ToList(); footprint = footprintCells.SelectMany(c => map.ProjectedCellsCovering(c.ToMPos(map))).ToArray(); diff --git a/OpenRA.Mods.Common/Traits/Player/ProvidesTechPrerequisite.cs b/OpenRA.Mods.Common/Traits/Player/ProvidesTechPrerequisite.cs index c35c7a1ac5..130061c2b8 100644 --- a/OpenRA.Mods.Common/Traits/Player/ProvidesTechPrerequisite.cs +++ b/OpenRA.Mods.Common/Traits/Player/ProvidesTechPrerequisite.cs @@ -15,7 +15,14 @@ namespace OpenRA.Mods.Common.Traits { public class ProvidesTechPrerequisiteInfo : ITechTreePrerequisiteInfo { + [Desc("Internal id for this tech level.")] + public readonly string Id; + + [Translate] + [Desc("Name shown in the lobby options.")] public readonly string Name; + + [Desc("Prerequisites to grant when this tech level is active.")] public readonly string[] Prerequisites = { }; public object Create(ActorInitializer init) { return new ProvidesTechPrerequisite(this, init); } @@ -23,8 +30,9 @@ namespace OpenRA.Mods.Common.Traits public class ProvidesTechPrerequisite : ITechTreePrerequisite { - ProvidesTechPrerequisiteInfo info; + readonly ProvidesTechPrerequisiteInfo info; bool enabled; + static readonly string[] NoPrerequisites = new string[0]; public string Name { get { return info.Name; } } @@ -40,7 +48,8 @@ namespace OpenRA.Mods.Common.Traits public ProvidesTechPrerequisite(ProvidesTechPrerequisiteInfo info, ActorInitializer init) { this.info = info; - enabled = info.Name == init.World.LobbyInfo.GlobalSettings.TechLevel; + var mapOptions = init.World.WorldActor.TraitOrDefault(); + enabled = mapOptions != null && mapOptions.TechLevel == info.Id; } } } diff --git a/OpenRA.Mods.Common/Traits/World/CrateSpawner.cs b/OpenRA.Mods.Common/Traits/World/CrateSpawner.cs index f030c469a3..a110da6619 100644 --- a/OpenRA.Mods.Common/Traits/World/CrateSpawner.cs +++ b/OpenRA.Mods.Common/Traits/World/CrateSpawner.cs @@ -13,12 +13,13 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Activities; +using OpenRA.Network; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - public class CrateSpawnerInfo : ITraitInfo + public class CrateSpawnerInfo : ITraitInfo, ILobbyOptions { [Desc("Default value of the crates checkbox in the lobby.")] public readonly bool Enabled = true; @@ -64,27 +65,39 @@ namespace OpenRA.Mods.Common.Traits [Desc("Spawn and remove the plane this far outside the map.")] public readonly WDist Cordon = new WDist(5120); - public object Create(ActorInitializer init) { return new CrateSpawner(this, init.Self); } + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) + { + yield return new LobbyBooleanOption("crates", "Crates", Enabled, Locked); + } + + public object Create(ActorInitializer init) { return new CrateSpawner(init.Self, this); } } - public class CrateSpawner : ITick + public class CrateSpawner : ITick, INotifyCreated { - readonly CrateSpawnerInfo info; readonly Actor self; - int crates = 0; - int ticks = 0; + readonly CrateSpawnerInfo info; + bool enabled; + int crates; + int ticks; - public CrateSpawner(CrateSpawnerInfo info, Actor self) + public CrateSpawner(Actor self, CrateSpawnerInfo info) { - this.info = info; this.self = self; + this.info = info; ticks = info.InitialSpawnDelay; } + void INotifyCreated.Created(Actor self) + { + enabled = self.World.LobbyInfo.GlobalSettings + .OptionOrDefault("crates", info.Enabled); + } + public void Tick(Actor self) { - if (!self.World.LobbyInfo.GlobalSettings.Crates) + if (!enabled) return; if (--ticks <= 0) diff --git a/OpenRA.Mods.Common/Traits/World/MapBuildRadius.cs b/OpenRA.Mods.Common/Traits/World/MapBuildRadius.cs index 1bad95edf3..952e276bf5 100644 --- a/OpenRA.Mods.Common/Traits/World/MapBuildRadius.cs +++ b/OpenRA.Mods.Common/Traits/World/MapBuildRadius.cs @@ -15,22 +15,36 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Controls the build radius checkboxes in the lobby options.")] - public class MapBuildRadiusInfo : TraitInfo + public class MapBuildRadiusInfo : ITraitInfo, ILobbyOptions { [Desc("Default value of the ally build radius checkbox in the lobby.")] public readonly bool AllyBuildRadiusEnabled = true; [Desc("Prevent the ally build radius state from being changed in the lobby.")] public readonly bool AllyBuildRadiusLocked = false; + + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) + { + yield return new LobbyBooleanOption("allybuild", "Build off Allies' ConYards", AllyBuildRadiusEnabled, AllyBuildRadiusLocked); + } + + public object Create(ActorInitializer init) { return new MapBuildRadius(this); } } public class MapBuildRadius : INotifyCreated { + readonly MapBuildRadiusInfo info; public bool AllyBuildRadiusEnabled { get; private set; } + public MapBuildRadius(MapBuildRadiusInfo info) + { + this.info = info; + } + void INotifyCreated.Created(Actor self) { - AllyBuildRadiusEnabled = self.World.LobbyInfo.GlobalSettings.AllyBuildRadius; + AllyBuildRadiusEnabled = self.World.LobbyInfo.GlobalSettings + .OptionOrDefault("allybuild", info.AllyBuildRadiusEnabled); } } } diff --git a/OpenRA.Mods.Common/Traits/World/MapCreeps.cs b/OpenRA.Mods.Common/Traits/World/MapCreeps.cs index 16455aead9..04f5c5cd3e 100644 --- a/OpenRA.Mods.Common/Traits/World/MapCreeps.cs +++ b/OpenRA.Mods.Common/Traits/World/MapCreeps.cs @@ -15,22 +15,36 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Controls the 'Creeps' checkbox in the lobby options.")] - public class MapCreepsInfo : TraitInfo + public class MapCreepsInfo : ITraitInfo, ILobbyOptions { [Desc("Default value of the creeps checkbox in the lobby.")] public readonly bool Enabled = true; [Desc("Prevent the creeps state from being changed in the lobby.")] public readonly bool Locked = false; + + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) + { + yield return new LobbyBooleanOption("creeps", "Worms", Enabled, Locked); + } + + public object Create(ActorInitializer init) { return new MapCreeps(this); } } public class MapCreeps : INotifyCreated { + readonly MapCreepsInfo info; public bool Enabled { get; private set; } + public MapCreeps(MapCreepsInfo info) + { + this.info = info; + } + void INotifyCreated.Created(Actor self) { - Enabled = self.World.LobbyInfo.GlobalSettings.Creeps; + Enabled = self.World.LobbyInfo.GlobalSettings + .OptionOrDefault("creeps", info.Enabled); } } } diff --git a/OpenRA.Mods.Common/Traits/World/MapOptions.cs b/OpenRA.Mods.Common/Traits/World/MapOptions.cs index 51611e7108..70cb14b88d 100644 --- a/OpenRA.Mods.Common/Traits/World/MapOptions.cs +++ b/OpenRA.Mods.Common/Traits/World/MapOptions.cs @@ -10,12 +10,13 @@ #endregion using System.Collections.Generic; +using System.Linq; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Controls the map difficulty, tech level, and short game lobby options.")] - public class MapOptionsInfo : TraitInfo + public class MapOptionsInfo : ITraitInfo, ILobbyOptions { [Desc("Default value of the short game checkbox in the lobby.")] public readonly bool ShortGameEnabled = true; @@ -24,7 +25,7 @@ namespace OpenRA.Mods.Common.Traits public readonly bool ShortGameLocked = false; [Desc("Default tech level.")] - public readonly string TechLevel = "Unrestricted"; + public readonly string TechLevel = "unrestricted"; [Desc("Prevent the tech level from being changed in the lobby.")] public readonly bool TechLevelLocked = false; @@ -37,15 +38,42 @@ namespace OpenRA.Mods.Common.Traits [Desc("Prevent the difficulty from being changed in the lobby.")] public readonly bool DifficultyLocked = false; + + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) + { + yield return new LobbyBooleanOption("shortgame", "Short Game", ShortGameEnabled, ShortGameLocked); + + var techLevels = rules.Actors["player"].TraitInfos() + .ToDictionary(t => t.Id, t => t.Name); + + if (techLevels.Any()) + yield return new LobbyOption("techlevel", "Tech Level", + new ReadOnlyDictionary(techLevels), + TechLevel, TechLevelLocked); + } + + public object Create(ActorInitializer init) { return new MapOptions(this); } } public class MapOptions : INotifyCreated { + readonly MapOptionsInfo info; + public bool ShortGame { get; private set; } + public string TechLevel { get; private set; } + + public MapOptions(MapOptionsInfo info) + { + this.info = info; + } void INotifyCreated.Created(Actor self) { - ShortGame = self.World.LobbyInfo.GlobalSettings.ShortGame; + ShortGame = self.World.LobbyInfo.GlobalSettings + .OptionOrDefault("shortgame", info.ShortGameEnabled); + + TechLevel = self.World.LobbyInfo.GlobalSettings + .OptionOrDefault("techlevel", info.TechLevel); } } } diff --git a/OpenRA.Mods.Common/Traits/World/SpawnMPUnits.cs b/OpenRA.Mods.Common/Traits/World/SpawnMPUnits.cs index 3bbc5e5e02..8d2fa93120 100644 --- a/OpenRA.Mods.Common/Traits/World/SpawnMPUnits.cs +++ b/OpenRA.Mods.Common/Traits/World/SpawnMPUnits.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Primitives; @@ -18,25 +19,48 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Spawn base actor at the spawnpoint and support units in an annulus around the base actor. Both are defined at MPStartUnits. Attach this to the world actor.")] - public class SpawnMPUnitsInfo : TraitInfo, Requires, Requires + public class SpawnMPUnitsInfo : ITraitInfo, Requires, Requires, ILobbyOptions { public readonly string StartingUnitsClass = "none"; [Desc("Prevent the starting units option from being changed in the lobby.")] public bool Locked = false; + + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) + { + var startingUnits = new Dictionary(); + + // Duplicate classes are defined for different race variants + foreach (var t in rules.Actors["world"].TraitInfos()) + startingUnits[t.Class] = t.ClassName; + + if (startingUnits.Any()) + yield return new LobbyOption("startingunits", "Starting Units", new ReadOnlyDictionary(startingUnits), StartingUnitsClass, Locked); + } + + public object Create(ActorInitializer init) { return new SpawnMPUnits(this); } } public class SpawnMPUnits : IWorldLoaded { + readonly SpawnMPUnitsInfo info; + + public SpawnMPUnits(SpawnMPUnitsInfo info) + { + this.info = info; + } + public void WorldLoaded(World world, WorldRenderer wr) { foreach (var s in world.WorldActor.Trait().Start) SpawnUnitsForPlayer(world, s.Key, s.Value); } - static void SpawnUnitsForPlayer(World w, Player p, CPos sp) + void SpawnUnitsForPlayer(World w, Player p, CPos sp) { - var spawnClass = p.PlayerReference.StartingUnitsClass ?? w.LobbyInfo.GlobalSettings.StartingUnitsClass; + var spawnClass = p.PlayerReference.StartingUnitsClass ?? w.LobbyInfo.GlobalSettings + .OptionOrDefault("startingunits", info.StartingUnitsClass); + var unitGroup = w.Map.Rules.Actors["world"].TraitInfos() .Where(g => g.Class == spawnClass && g.Factions != null && g.Factions.Contains(p.Faction.InternalName)) .RandomOrDefault(w.SharedRandom); diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index f3470a259d..da302ea48c 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -123,6 +123,13 @@ namespace OpenRA.Mods.Common.UtilityCommands } } + if (engineVersion < 20160604 && node.Key.StartsWith("ProvidesTechPrerequisite")) + { + var name = node.Value.Nodes.First(n => n.Key == "Name"); + var id = name.Value.Value.ToLowerInvariant().Replace(" ", ""); + node.Value.Nodes.Add(new MiniYamlNode("Id", id)); + } + UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); } } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs index 74cb96165e..298365739a 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs @@ -75,12 +75,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic } // Debug/Cheats tab - if (lp != null && world.LobbyInfo.GlobalSettings.AllowCheats) + // Can't use DeveloperMode.Enabled because there is a hardcoded hack to *always* + // enable developer mode for singleplayer games, but we only want to show the button + // if it has been explicitly enabled + var def = world.Map.Rules.Actors["player"].TraitInfo().Enabled; + var developerEnabled = world.LobbyInfo.GlobalSettings.OptionOrDefault("cheats", def); + if (lp != null && developerEnabled) { numTabs++; var debugTabButton = widget.Get(string.Concat("BUTTON", numTabs.ToString())); debugTabButton.Text = "Debug"; - debugTabButton.IsVisible = () => lp != null && world.LobbyInfo.GlobalSettings.AllowCheats && numTabs > 1 && !hasError; + debugTabButton.IsVisible = () => lp != null && numTabs > 1 && !hasError; debugTabButton.IsDisabled = () => world.IsGameOver; debugTabButton.OnClick = () => activePanel = IngameInfoPanel.Debug; debugTabButton.IsHighlighted = () => activePanel == IngameInfoPanel.Debug; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/MenuButtonsChromeLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/MenuButtonsChromeLogic.cs index ba6728b7a2..de41ecf354 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/MenuButtonsChromeLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/MenuButtonsChromeLogic.cs @@ -12,6 +12,7 @@ using System; using System.Linq; using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic @@ -67,7 +68,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic var debug = widget.GetOrNull("DEBUG_BUTTON"); if (debug != null) { - debug.IsVisible = () => world.LobbyInfo.GlobalSettings.AllowCheats; + // Can't use DeveloperMode.Enabled because there is a hardcoded hack to *always* + // enable developer mode for singleplayer games, but we only want to show the button + // if it has been explicitly enabled + var def = world.Map.Rules.Actors["player"].TraitInfo().Enabled; + var enabled = world.LobbyInfo.GlobalSettings.OptionOrDefault("cheats", def); + debug.IsVisible = () => enabled; debug.IsDisabled = () => disableSystemButtons; debug.OnClick = () => OpenMenuPanel(debug, new WidgetArgs() { diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs index 3ed3a458c4..c2a4dcd39f 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyLogic.cs @@ -336,73 +336,91 @@ namespace OpenRA.Mods.Common.Widgets.Logic forceStartBin.Get("CANCEL_BUTTON").OnClick = () => panel = PanelType.Players; // Options panel - var allowCheats = optionsBin.GetOrNull("ALLOWCHEATS_CHECKBOX"); - if (allowCheats != null) + var optionCheckboxes = new Dictionary() { - var cheatsLocked = new CachedTransform( - map => map.Rules.Actors["player"].TraitInfo().Locked); + { "EXPLORED_MAP_CHECKBOX", "explored" }, + { "CRATES_CHECKBOX", "crates" }, + { "SHORTGAME_CHECKBOX", "shortgame" }, + { "FOG_CHECKBOX", "fog" }, + { "ALLYBUILDRADIUS_CHECKBOX", "allybuild" }, + { "ALLOWCHEATS_CHECKBOX", "cheats" }, + { "CREEPS_CHECKBOX", "creeps" }, + }; - allowCheats.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllowCheats; - allowCheats.IsDisabled = () => configurationDisabled() || cheatsLocked.Update(Map); - allowCheats.OnClick = () => orderManager.IssueOrder(Order.Command( - "allowcheats {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllowCheats))); - } - - var crates = optionsBin.GetOrNull("CRATES_CHECKBOX"); - if (crates != null) + foreach (var kv in optionCheckboxes) { - var cratesLocked = new CachedTransform(map => + var checkbox = optionsBin.GetOrNull(kv.Key); + if (checkbox != null) { - var crateSpawner = map.Rules.Actors["world"].TraitInfoOrDefault(); - return crateSpawner == null || crateSpawner.Locked; - }); + var option = new CachedTransform( + gs => gs.LobbyOptions[kv.Value]); - crates.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Crates; - crates.IsDisabled = () => configurationDisabled() || cratesLocked.Update(Map); - crates.OnClick = () => orderManager.IssueOrder(Order.Command( - "crates {0}".F(!orderManager.LobbyInfo.GlobalSettings.Crates))); + checkbox.IsChecked = () => option.Update(orderManager.LobbyInfo.GlobalSettings).Enabled; + checkbox.IsDisabled = () => configurationDisabled() || + option.Update(orderManager.LobbyInfo.GlobalSettings).Locked; + checkbox.OnClick = () => orderManager.IssueOrder(Order.Command( + "option {0} {1}".F(kv.Value, !option.Update(orderManager.LobbyInfo.GlobalSettings).Enabled))); + } } - var creeps = optionsBin.GetOrNull("CREEPS_CHECKBOX"); - if (creeps != null) + var optionDropdowns = new Dictionary() { - var creepsLocked = new CachedTransform(map => + { "TECHLEVEL", "techlevel" }, + { "STARTINGUNITS", "startingunits" }, + { "STARTINGCASH", "startingcash" }, + }; + + var allOptions = new CachedTransform( + map => map.Rules.Actors["player"].TraitInfos() + .Concat(map.Rules.Actors["world"].TraitInfos()) + .SelectMany(t => t.LobbyOptions(map.Rules)) + .ToArray()); + + foreach (var kv in optionDropdowns) + { + var dropdown = optionsBin.GetOrNull(kv.Key + "_DROPDOWNBUTTON"); + if (dropdown != null) { - var mapCreeps = map.Rules.Actors["world"].TraitInfoOrDefault(); - return mapCreeps == null || mapCreeps.Locked; - }); + var optionValue = new CachedTransform( + gs => gs.LobbyOptions[kv.Value]); - creeps.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Creeps; - creeps.IsDisabled = () => configurationDisabled() || creepsLocked.Update(Map); - creeps.OnClick = () => orderManager.IssueOrder(Order.Command( - "creeps {0}".F(!orderManager.LobbyInfo.GlobalSettings.Creeps))); - } + var option = new CachedTransform( + map => allOptions.Update(map).FirstOrDefault(o => o.Id == kv.Value)); - var allybuildradius = optionsBin.GetOrNull("ALLYBUILDRADIUS_CHECKBOX"); - if (allybuildradius != null) - { - var allyBuildRadiusLocked = new CachedTransform(map => - { - var mapBuildRadius = map.Rules.Actors["world"].TraitInfoOrDefault(); - return mapBuildRadius == null || mapBuildRadius.AllyBuildRadiusLocked; - }); + var getOptionLabel = new CachedTransform(id => + { + string value; + if (id == null || !option.Update(Map).Values.TryGetValue(id, out value)) + return "Not Available"; - allybuildradius.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.AllyBuildRadius; - allybuildradius.IsDisabled = () => configurationDisabled() || allyBuildRadiusLocked.Update(Map); - allybuildradius.OnClick = () => orderManager.IssueOrder(Order.Command( - "allybuildradius {0}".F(!orderManager.LobbyInfo.GlobalSettings.AllyBuildRadius))); - } + return value; + }); - var shortGame = optionsBin.GetOrNull("SHORTGAME_CHECKBOX"); - if (shortGame != null) - { - var shortGameLocked = new CachedTransform( - map => map.Rules.Actors["world"].TraitInfo().ShortGameLocked); + dropdown.GetText = () => getOptionLabel.Update(optionValue.Update(orderManager.LobbyInfo.GlobalSettings).Value); + dropdown.IsVisible = () => option.Update(Map) != null; + dropdown.IsDisabled = () => configurationDisabled() || + optionValue.Update(orderManager.LobbyInfo.GlobalSettings).Locked; - shortGame.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.ShortGame; - shortGame.IsDisabled = () => configurationDisabled() || shortGameLocked.Update(Map); - shortGame.OnClick = () => orderManager.IssueOrder(Order.Command( - "shortgame {0}".F(!orderManager.LobbyInfo.GlobalSettings.ShortGame))); + dropdown.OnMouseDown = _ => + { + Func, ScrollItemWidget, ScrollItemWidget> setupItem = (c, template) => + { + Func isSelected = () => optionValue.Update(orderManager.LobbyInfo.GlobalSettings).Value == c.Key; + Action onClick = () => orderManager.IssueOrder(Order.Command("option {0} {1}".F(kv.Value, c.Key))); + + var item = ScrollItemWidget.Setup(template, isSelected, onClick); + item.Get("LABEL").GetText = () => c.Value; + return item; + }; + + var options = option.Update(Map).Values; + dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem); + }; + + var label = optionsBin.GetOrNull(kv.Key + "_DESC"); + if (label != null) + label.IsVisible = () => option.Update(Map) != null; + } } var difficulty = optionsBin.GetOrNull("DIFFICULTY_DROPDOWNBUTTON"); @@ -434,116 +452,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic optionsBin.Get("DIFFICULTY_DESC").IsVisible = difficulty.IsVisible; } - var startingUnits = optionsBin.GetOrNull("STARTINGUNITS_DROPDOWNBUTTON"); - if (startingUnits != null) - { - var startUnitsInfos = new CachedTransform>( - map => map.Rules.Actors["world"].TraitInfos()); - - var startUnitsLocked = new CachedTransform(map => - { - var spawnUnitsInfo = map.Rules.Actors["world"].TraitInfoOrDefault(); - return spawnUnitsInfo == null || spawnUnitsInfo.Locked; - }); - - Func className = c => - { - var selectedClass = startUnitsInfos.Update(Map).Where(s => s.Class == c).Select(u => u.ClassName).FirstOrDefault(); - return selectedClass != null ? selectedClass : c; - }; - - startingUnits.IsDisabled = () => configurationDisabled() || startUnitsLocked.Update(Map); - startingUnits.GetText = () => !Map.RulesLoaded || startUnitsLocked.Update(Map) ? - "Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass); - startingUnits.OnMouseDown = _ => - { - var classes = startUnitsInfos.Update(Map).Select(a => a.Class).Distinct(); - var options = classes.Select(c => new DropDownOption - { - Title = className(c), - IsSelected = () => orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass == c, - OnClick = () => orderManager.IssueOrder(Order.Command("startingunits {0}".F(c))) - }); - - Func setupItem = (option, template) => - { - var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick); - item.Get("LABEL").GetText = () => option.Title; - return item; - }; - - startingUnits.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem); - }; - - optionsBin.Get("STARTINGUNITS_DESC").IsVisible = startingUnits.IsVisible; - } - - var startingCash = optionsBin.GetOrNull("STARTINGCASH_DROPDOWNBUTTON"); - if (startingCash != null) - { - var playerResources = new CachedTransform( - map => map.Rules.Actors["player"].TraitInfo()); - - startingCash.IsDisabled = () => configurationDisabled() || playerResources.Update(Map).DefaultCashLocked; - startingCash.GetText = () => !Map.RulesLoaded || playerResources.Update(Map).DefaultCashLocked ? - "Not Available" : "${0}".F(orderManager.LobbyInfo.GlobalSettings.StartingCash); - startingCash.OnMouseDown = _ => - { - var options = playerResources.Update(Map).SelectableCash.Select(c => new DropDownOption - { - Title = "${0}".F(c), - IsSelected = () => orderManager.LobbyInfo.GlobalSettings.StartingCash == c, - OnClick = () => orderManager.IssueOrder(Order.Command("startingcash {0}".F(c))) - }); - - Func setupItem = (option, template) => - { - var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick); - item.Get("LABEL").GetText = () => option.Title; - return item; - }; - - startingCash.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem); - }; - } - - var techLevel = optionsBin.GetOrNull("TECHLEVEL_DROPDOWNBUTTON"); - if (techLevel != null) - { - var mapOptions = new CachedTransform( - map => map.Rules.Actors["world"].TraitInfo()); - - var techLevels = new CachedTransform>( - map => map.Rules.Actors["player"].TraitInfos().ToList()); - - techLevel.IsVisible = () => Map.RulesLoaded && techLevels.Update(Map).Any(); - var techLevelDescription = optionsBin.GetOrNull("TECHLEVEL_DESC"); - if (techLevelDescription != null) - techLevelDescription.IsVisible = techLevel.IsVisible; - - techLevel.IsDisabled = () => configurationDisabled() || mapOptions.Update(Map).TechLevelLocked; - techLevel.GetText = () => !Map.RulesLoaded || mapOptions.Update(Map).TechLevelLocked ? - "Not Available" : "{0}".F(orderManager.LobbyInfo.GlobalSettings.TechLevel); - techLevel.OnMouseDown = _ => - { - var options = techLevels.Update(Map).Select(c => new DropDownOption - { - Title = "{0}".F(c.Name), - IsSelected = () => orderManager.LobbyInfo.GlobalSettings.TechLevel == c.Name, - OnClick = () => orderManager.IssueOrder(Order.Command("techlevel {0}".F(c.Name))) - }); - - Func setupItem = (option, template) => - { - var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick); - item.Get("LABEL").GetText = () => option.Title; - return item; - }; - - techLevel.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count() * 30, options, setupItem); - }; - } - var gameSpeed = optionsBin.GetOrNull("GAMESPEED_DROPDOWNBUTTON"); if (gameSpeed != null) { @@ -582,30 +490,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic }; } - var exploredMap = optionsBin.GetOrNull("EXPLORED_MAP_CHECKBOX"); - if (exploredMap != null) - { - var exploredMapLocked = new CachedTransform( - map => map.Rules.Actors["player"].TraitInfo().ExploredMapLocked); - - exploredMap.IsChecked = () => !orderManager.LobbyInfo.GlobalSettings.Shroud; - exploredMap.IsDisabled = () => configurationDisabled() || exploredMapLocked.Update(Map); - exploredMap.OnClick = () => orderManager.IssueOrder(Order.Command( - "shroud {0}".F(!orderManager.LobbyInfo.GlobalSettings.Shroud))); - } - - var enableFog = optionsBin.GetOrNull("FOG_CHECKBOX"); - if (enableFog != null) - { - var fogLocked = new CachedTransform( - map => map.Rules.Actors["player"].TraitInfo().FogLocked); - - enableFog.IsChecked = () => orderManager.LobbyInfo.GlobalSettings.Fog; - enableFog.IsDisabled = () => configurationDisabled() || fogLocked.Update(Map); - enableFog.OnClick = () => orderManager.IssueOrder(Order.Command( - "fog {0}".F(!orderManager.LobbyInfo.GlobalSettings.Fog))); - } - var disconnectButton = lobby.Get("DISCONNECT_BUTTON"); disconnectButton.OnClick = () => { Ui.CloseWindow(); onExit(); }; diff --git a/mods/cnc/rules/player.yaml b/mods/cnc/rules/player.yaml index 38b5d7fa04..db4ae61a3a 100644 --- a/mods/cnc/rules/player.yaml +++ b/mods/cnc/rules/player.yaml @@ -19,14 +19,18 @@ Player: ProvidesTechPrerequisite@low: Name: Low Prerequisites: techlevel.low + Id: low ProvidesTechPrerequisite@medium: Name: Medium Prerequisites: techlevel.low, techlevel.medium + Id: medium ProvidesTechPrerequisite@nosuper: Name: No Powers Prerequisites: techlevel.low, techlevel.medium, techlevel.high + Id: nopowers ProvidesTechPrerequisite@all: Name: Unrestricted Prerequisites: techlevel.low, techlevel.medium, techlevel.high, techlevel.superweapons + Id: unrestricted GlobalUpgradeManager: ResourceStorageWarning: diff --git a/mods/d2k/rules/player.yaml b/mods/d2k/rules/player.yaml index c31633d836..5479a45592 100644 --- a/mods/d2k/rules/player.yaml +++ b/mods/d2k/rules/player.yaml @@ -73,15 +73,19 @@ Player: ProvidesTechPrerequisite@low: Name: Low Prerequisites: techlevel.low + Id: low ProvidesTechPrerequisite@medium: Name: Medium Prerequisites: techlevel.low, techlevel.medium + Id: medium ProvidesTechPrerequisite@nosuper: Name: No Powers Prerequisites: techlevel.low, techlevel.medium, techlevel.high + Id: nopowers ProvidesTechPrerequisite@all: Name: Unrestricted Prerequisites: techlevel.low, techlevel.medium, techlevel.high, techlevel.superweapons + Id: unrestricted EnemyWatcher: HarvesterInsurance: GlobalUpgradeManager: diff --git a/mods/ra/rules/player.yaml b/mods/ra/rules/player.yaml index 05d155f158..cf146302a8 100644 --- a/mods/ra/rules/player.yaml +++ b/mods/ra/rules/player.yaml @@ -55,18 +55,23 @@ Player: ProvidesTechPrerequisite@infonly: Name: Infantry Only Prerequisites: techlevel.infonly + Id: infantryonly ProvidesTechPrerequisite@low: Name: Low Prerequisites: techlevel.infonly, techlevel.low + Id: low ProvidesTechPrerequisite@medium: Name: Medium Prerequisites: techlevel.infonly, techlevel.low, techlevel.medium + Id: medium ProvidesTechPrerequisite@high: Name: No Superweapons Prerequisites: techlevel.infonly, techlevel.low, techlevel.medium, techlevel.high + Id: nosuperweapons ProvidesTechPrerequisite@unrestricted: Name: Unrestricted Prerequisites: techlevel.infonly, techlevel.low, techlevel.medium, techlevel.high, techlevel.unrestricted + Id: unrestricted GlobalUpgradeManager: EnemyWatcher: VeteranProductionIconOverlay: