From 923883fad736f916aa03b1d411de3b8aeea78123 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 9 Apr 2016 13:40:44 -0400 Subject: [PATCH] Fix and improve custom rule detection. --- OpenRA.Game/GameRules/Ruleset.cs | 50 +++++++++++++++++++ OpenRA.Game/Map/MapPreview.cs | 28 +++++++++-- OpenRA.Game/Server/Server.cs | 2 +- OpenRA.Game/Traits/TraitsInterfaces.cs | 1 + .../ServerTraits/LobbyCommands.cs | 2 +- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/OpenRA.Game/GameRules/Ruleset.cs b/OpenRA.Game/GameRules/Ruleset.cs index ff1be28daf..db26035a65 100644 --- a/OpenRA.Game/GameRules/Ruleset.cs +++ b/OpenRA.Game/GameRules/Ruleset.cs @@ -200,5 +200,55 @@ namespace OpenRA return ruleset; } + + static bool AnyCustomYaml(MiniYaml yaml) + { + return yaml != null && (yaml.Value != null || yaml.Nodes.Any()); + } + + static bool AnyFlaggedTraits(ModData modData, List actors) + { + foreach (var actorNode in actors) + { + foreach (var traitNode in actorNode.Value.Nodes) + { + try + { + var traitName = traitNode.Key.Split('@')[0]; + var traitType = modData.ObjectCreator.FindType(traitName + "Info"); + if (traitType.GetInterface("ILobbyCustomRulesIgnore") == null) + return true; + } + catch { } + } + } + + return false; + } + + public static bool DefinesUnsafeCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, + MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications, MiniYaml mapSequences) + { + // Maps that define any weapon, voice, notification, or sequence overrides are always flagged + if (AnyCustomYaml(mapWeapons) || AnyCustomYaml(mapVoices) || AnyCustomYaml(mapNotifications) || AnyCustomYaml(mapSequences)) + return true; + + // Any trait overrides that aren't explicitly whitelisted are flagged + if (mapRules != null) + { + if (AnyFlaggedTraits(modData, mapRules.Nodes)) + return true; + + if (mapRules.Value != null) + { + var mapFiles = FieldLoader.GetValue("value", mapRules.Value); + foreach (var f in mapFiles) + if (AnyFlaggedTraits(modData, MiniYaml.FromStream(fileSystem.Open(f)))) + return true; + } + } + + return false; + } } } diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index e21fa9df54..8559ebd923 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -21,6 +21,7 @@ using System.Text; using System.Threading; using OpenRA.FileSystem; using OpenRA.Graphics; +using OpenRA.Primitives; namespace OpenRA { @@ -80,12 +81,14 @@ namespace OpenRA Lazy rules; public Ruleset Rules { get { return rules != null ? rules.Value : null; } } public bool InvalidCustomRules { get; private set; } + public bool DefinesUnsafeCustomRules { get; private set; } public bool RulesLoaded { get; private set; } - public void SetRulesetGenerator(ModData modData, Func generator) + public void SetRulesetGenerator(ModData modData, Func> generator) { InvalidCustomRules = false; RulesLoaded = false; + DefinesUnsafeCustomRules = false; // Note: multiple threads may try to access the value at the same time // We rely on the thread-safety guarantees given by Lazy to prevent race conitions. @@ -97,7 +100,9 @@ namespace OpenRA try { - return generator(); + var ret = generator(); + DefinesUnsafeCustomRules = ret.Second; + return ret.First; } catch (Exception e) { @@ -146,6 +151,15 @@ namespace OpenRA public Ruleset Rules { get { return innerData.Rules; } } public bool InvalidCustomRules { get { return innerData.InvalidCustomRules; } } public bool RulesLoaded { get { return innerData.RulesLoaded; } } + public bool DefinesUnsafeCustomRules + { + get + { + // Force lazy rules to be evaluated + var force = innerData.Rules; + return innerData.DefinesUnsafeCustomRules; + } + } Download download; public long DownloadBytes { get; private set; } @@ -297,8 +311,11 @@ namespace OpenRA var musicDefinitions = LoadRuleSection(yaml, "Music"); var notificationDefinitions = LoadRuleSection(yaml, "Notifications"); var sequenceDefinitions = LoadRuleSection(yaml, "Sequences"); - return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, + var rules = Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions); + var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions, + weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions); + return Pair.New(rules, flagged); }); if (p.Contains("map.png")) @@ -384,8 +401,11 @@ namespace OpenRA var musicDefinitions = LoadRuleSection(rulesYaml, "Music"); var notificationDefinitions = LoadRuleSection(rulesYaml, "Notifications"); var sequenceDefinitions = LoadRuleSection(rulesYaml, "Sequences"); - return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, + var rules = Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions); + var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions, + weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions); + return Pair.New(rules, flagged); }); } catch (Exception) { } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index a21e5fd497..41ab03c8a1 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -392,7 +392,7 @@ namespace OpenRA.Server SendOrderTo(newConn, "Message", motd); } - if (Map.Rules != ModData.DefaultRules && !LobbyInfo.IsSinglePlayer) + if (!LobbyInfo.IsSinglePlayer && Map.DefinesUnsafeCustomRules) SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change."); if (Settings.DisableSinglePlayer) diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index c6db694f5b..0411903902 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -319,6 +319,7 @@ namespace OpenRA.Traits public interface ITraitInfo : ITraitInfoInterface { object Create(ActorInitializer init); } public class TraitInfo : ITraitInfo where T : new() { public virtual object Create(ActorInitializer init) { return new T(); } } + public interface ILobbyCustomRulesIgnore { } [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:InterfaceNamesMustBeginWithI", Justification = "Not a real interface, but more like a tag.")] public interface Requires where T : class, ITraitInfoInterface { } diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index ac910a8c73..fef8907eaa 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -398,7 +398,7 @@ namespace OpenRA.Mods.Common.Server server.SendMessage("{0} changed the map to {1}.".F(client.Name, server.Map.Title)); - if (server.Map.Rules.Actors != server.ModData.DefaultRules.Actors) + if (server.Map.DefinesUnsafeCustomRules) server.SendMessage("This map contains custom rules. Game experience may change."); if (server.Settings.DisableSinglePlayer)