From 3e92c1944a8e7b7221ba6d7dba553f1d35a8014a Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 20 Apr 2016 00:40:49 -0400 Subject: [PATCH] Add plumbing for trait-defined lobby options. --- OpenRA.Game/Network/Session.cs | 46 +++++++++++- OpenRA.Game/Traits/TraitsInterfaces.cs | 46 ++++++++++++ .../ServerTraits/LobbyCommands.cs | 72 +++++++++++++++++++ .../ServerTraits/LobbySettingsNotification.cs | 13 ++++ 4 files changed, 175 insertions(+), 2 deletions(-) diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 63eeb34af5..926448e4be 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; @@ -198,14 +209,45 @@ namespace OpenRA.Network 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/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.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index 1f3b156171..998f19a502 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -424,6 +424,47 @@ namespace OpenRA.Mods.Common.Server return true; } }, + { "option", + s => + { + if (!client.IsAdmin) + { + server.SendOrderTo(conn, "Message", "Only the host can change the configuration."); + return true; + } + + 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", "Invalid configuration command."); + return true; + } + + 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(option.ValueChangedMessage(client.Name, split[1])); + + return true; + } + }, { "allowcheats", s => { @@ -1067,6 +1108,37 @@ namespace OpenRA.Mods.Common.Server public static void LoadMapSettings(Session.Global gs, Ruleset rules) { + var options = rules.Actors["player"].TraitInfos() + .Concat(rules.Actors["world"].TraitInfos()) + .SelectMany(t => t.LobbyOptions(rules)); + + 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; + } + + preferredValue = state.PreferredValue; + } + else + state = new Session.LobbyOptionState(); + + state.Locked = o.Locked; + state.Value = value; + state.PreferredValue = preferredValue; + gs.LobbyOptions[o.Id] = state; + } + var devMode = rules.Actors["player"].TraitInfo(); gs.AllowCheats = devMode.Enabled; diff --git a/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs b/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs index c6a34d23ba..bfceab2ccb 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,6 +27,18 @@ namespace OpenRA.Mods.Common.Server var defaults = new Session.Global(); LobbyCommands.LoadMapSettings(defaults, server.Map.Rules); + 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); + + foreach (var kv in server.LobbyInfo.GlobalSettings.LobbyOptions) + { + 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.AllowCheats != defaults.AllowCheats) server.SendOrderTo(conn, "Message", "Allow Cheats: {0}".F(server.LobbyInfo.GlobalSettings.AllowCheats));