diff --git a/OpenRA.Game/Traits/LintAttributes.cs b/OpenRA.Game/Traits/LintAttributes.cs index e65029bef7..9c5c9cc209 100644 --- a/OpenRA.Game/Traits/LintAttributes.cs +++ b/OpenRA.Game/Traits/LintAttributes.cs @@ -29,7 +29,7 @@ namespace OpenRA.Traits [AttributeUsage(AttributeTargets.Field)] public sealed class SequenceReferenceAttribute : Attribute { - public readonly string ImageReference; // the field name in the same trait info that contains the image name + public readonly string ImageReference; // The field name in the same trait info that contains the image name. public readonly bool Prefix; public SequenceReferenceAttribute(string imageReference = null, bool prefix = false) { @@ -37,4 +37,10 @@ namespace OpenRA.Traits Prefix = prefix; } } + + [AttributeUsage(AttributeTargets.Field)] + public sealed class UpgradeGrantedReferenceAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Field)] + public sealed class UpgradeUsedReferenceAttribute : Attribute { } } diff --git a/OpenRA.Mods.Common/Lint/CheckUpgrades.cs b/OpenRA.Mods.Common/Lint/CheckUpgrades.cs new file mode 100644 index 0000000000..aedf046b69 --- /dev/null +++ b/OpenRA.Mods.Common/Lint/CheckUpgrades.cs @@ -0,0 +1,153 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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.Generic; +using System.Linq; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Lint +{ + public class CheckUpgrades : ILintPass + { + public void Run(Action emitError, Action emitWarning, Map map) + { + CheckUpgradesValidity(emitError, map); + CheckUpgradesUsage(emitError, emitWarning, map); + } + + private static void CheckUpgradesValidity(Action emitError, Map map) + { + var upgradesGranted = GetAllGrantedUpgrades(emitError, map).ToHashSet(); + + foreach (var actorInfo in map.Rules.Actors) + { + foreach (var trait in actorInfo.Value.Traits) + { + var fields = trait.GetType().GetFields(); + foreach (var field in fields.Where(x => x.HasAttribute())) + { + var values = LintExts.GetFieldValues(trait, field, emitError); + foreach (var value in values.Where(x => !upgradesGranted.Contains(x))) + emitError("Actor type `{0}` uses upgrade `{1}` that is not granted by anything!".F(actorInfo.Key, value)); + } + } + } + } + + private static void CheckUpgradesUsage(Action emitError, Action emitWarning, Map map) + { + var upgradesUsed = GetAllUsedUpgrades(emitError, map).ToHashSet(); + + // Check all upgrades granted by traits. + foreach (var actorInfo in map.Rules.Actors) + { + foreach (var trait in actorInfo.Value.Traits) + { + var fields = trait.GetType().GetFields(); + foreach (var field in fields.Where(x => x.HasAttribute())) + { + var values = LintExts.GetFieldValues(trait, field, emitError); + foreach (var value in values.Where(x => !upgradesUsed.Contains(x))) + emitWarning("Actor type `{0}` grants upgrade `{1}` that is not used by anything!".F(actorInfo.Key, value)); + } + } + } + + // Check all upgrades granted by warheads. + foreach (var weapon in map.Rules.Weapons) + { + foreach (var warhead in weapon.Value.Warheads) + { + var fields = warhead.GetType().GetFields(); + foreach (var field in fields.Where(x => x.HasAttribute())) + { + var values = LintExts.GetFieldValues(warhead, field, emitError); + foreach (var value in values.Where(x => !upgradesUsed.Contains(x))) + emitWarning("Weapon type `{0}` grants upgrade `{1}` that is not used by anything!".F(weapon.Key, value)); + } + } + } + } + + static IEnumerable GetAllGrantedUpgrades(Action emitError, Map map) + { + // Get all upgrades granted by traits. + foreach (var actorInfo in map.Rules.Actors) + { + foreach (var trait in actorInfo.Value.Traits) + { + var fields = trait.GetType().GetFields(); + foreach (var field in fields.Where(x => x.HasAttribute())) + { + var values = LintExts.GetFieldValues(trait, field, emitError); + foreach (var value in values) + yield return value; + } + } + } + + // Get all upgrades granted by warheads. + foreach (var weapon in map.Rules.Weapons) + { + foreach (var warhead in weapon.Value.Warheads) + { + var fields = warhead.GetType().GetFields(); + foreach (var field in fields.Where(x => x.HasAttribute())) + { + var values = LintExts.GetFieldValues(warhead, field, emitError); + foreach (var value in values) + yield return value; + } + } + } + + // TODO: HACK because GainsExperience grants upgrades differently to most other sources. + var gainsExperience = map.Rules.Actors.SelectMany(x => x.Value.Traits.WithInterface() + .SelectMany(y => y.Upgrades.SelectMany(z => z.Value))); + + foreach (var upgrade in gainsExperience) + yield return upgrade; + + // TODO: HACK because Pluggable grants upgrades differently to most other sources. + var pluggable = map.Rules.Actors.SelectMany(x => x.Value.Traits.WithInterface() + .SelectMany(y => y.Upgrades.SelectMany(z => z.Value))); + + foreach (var upgrade in pluggable) + yield return upgrade; + } + + static IEnumerable GetAllUsedUpgrades(Action emitError, Map map) + { + foreach (var actorInfo in map.Rules.Actors) + { + foreach (var trait in actorInfo.Value.Traits) + { + var fields = trait.GetType().GetFields(); + foreach (var field in fields.Where(x => x.HasAttribute())) + { + var values = LintExts.GetFieldValues(trait, field, emitError); + foreach (var value in values) + yield return value; + } + } + } + + // TODO: HACK because GainsExperience and GainsStatUpgrades do not play by the rules... + // We assume everything GainsExperience grants is used by GainsStatUpgrade + var gainsExperience = map.Rules.Actors.SelectMany(x => x.Value.Traits.WithInterface() + .SelectMany(y => y.Upgrades.SelectMany(z => z.Value))); + + foreach (var upgrade in gainsExperience) + yield return upgrade; + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.Common/Lint/LintExts.cs b/OpenRA.Mods.Common/Lint/LintExts.cs index 04dfecb045..e7dcb5d5d1 100644 --- a/OpenRA.Mods.Common/Lint/LintExts.cs +++ b/OpenRA.Mods.Common/Lint/LintExts.cs @@ -10,7 +10,6 @@ using System; using System.Reflection; -using OpenRA.Traits; namespace OpenRA.Mods.Common.Lint { @@ -20,7 +19,7 @@ namespace OpenRA.Mods.Common.Lint { var type = fieldInfo.FieldType; if (type == typeof(string)) - return new string[] { (string)fieldInfo.GetValue(ruleInfo) }; + return new[] { (string)fieldInfo.GetValue(ruleInfo) }; if (type == typeof(string[])) return (string[])fieldInfo.GetValue(ruleInfo); diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index bfd03510a5..67807b257f 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -186,6 +186,7 @@ + @@ -199,6 +200,7 @@ + diff --git a/OpenRA.Mods.Common/Scripting/Properties/UpgradeProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/UpgradeProperties.cs index eba3b40e6e..c0d016b38b 100644 --- a/OpenRA.Mods.Common/Scripting/Properties/UpgradeProperties.cs +++ b/OpenRA.Mods.Common/Scripting/Properties/UpgradeProperties.cs @@ -8,6 +8,9 @@ */ #endregion +using System; +using System.IO; +using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Scripting; using OpenRA.Traits; @@ -17,35 +20,56 @@ namespace OpenRA.Mods.Common.Scripting [ScriptPropertyGroup("General")] public class UpgradeProperties : ScriptActorProperties, Requires { - UpgradeManager um; + readonly UpgradeManager um; + readonly ScriptUpgradesCache validUpgrades; + public UpgradeProperties(ScriptContext context, Actor self) : base(context, self) { um = self.Trait(); + validUpgrades = self.World.WorldActor.TraitOrDefault(); } [Desc("Grant an upgrade to this actor.")] public void GrantUpgrade(string upgrade) { - um.GrantUpgrade(Self, upgrade, this); + if (validUpgrades == null) + throw new InvalidOperationException("Can not grant upgrades because there is no ScriptUpgradesCache defined!"); + + if (validUpgrades.Info.Upgrades.Contains(upgrade)) + um.GrantUpgrade(Self, upgrade, this); + else + throw new InvalidDataException("The ScriptUpgradesCache does not contain a definition for upgrade `{0}`".F(upgrade)); } [Desc("Revoke an upgrade that was previously granted using GrantUpgrade.")] public void RevokeUpgrade(string upgrade) { - um.RevokeUpgrade(Self, upgrade, this); + if (validUpgrades == null) + throw new InvalidOperationException("Can not grant upgrades because there is no ScriptUpgradesCache defined!"); + + if (validUpgrades.Info.Upgrades.Contains(upgrade)) + um.RevokeUpgrade(Self, upgrade, this); + else + throw new InvalidDataException("The ScriptUpgradesCache does not contain a definition for upgrade `{0}`".F(upgrade)); } [Desc("Grant a limited-time upgrade to this actor.")] public void GrantTimedUpgrade(string upgrade, int duration) { - um.GrantTimedUpgrade(Self, upgrade, duration); + if (validUpgrades == null) + throw new InvalidOperationException("Can not grant upgrades because there is no ScriptUpgradesCache defined!"); + + if (validUpgrades.Info.Upgrades.Contains(upgrade)) + um.GrantTimedUpgrade(Self, upgrade, duration); + else + throw new InvalidDataException("The ScriptUpgradesCache does not contain a definition for upgrade `{0}`".F(upgrade)); } [Desc("Check whether this actor accepts a specific upgrade.")] public bool AcceptsUpgrade(string upgrade) { - return um.AcceptsUpgrade(Self, upgrade); + return validUpgrades != null && validUpgrades.Info.Upgrades.Contains(upgrade) && um.AcceptsUpgrade(Self, upgrade); } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/Scripting/ScriptUpgradesCache.cs b/OpenRA.Mods.Common/Scripting/ScriptUpgradesCache.cs new file mode 100644 index 0000000000..2016665991 --- /dev/null +++ b/OpenRA.Mods.Common/Scripting/ScriptUpgradesCache.cs @@ -0,0 +1,34 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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 OpenRA.Traits; + +namespace OpenRA.Mods.Common.Scripting +{ + [Desc("Allows granting upgrades to actors from Lua scripts.")] + public class ScriptUpgradesCacheInfo : ITraitInfo + { + [UpgradeGrantedReference] + [Desc("Upgrades that can be granted from the scripts.")] + public readonly string[] Upgrades = { }; + + public object Create(ActorInitializer init) { return new ScriptUpgradesCache(this); } + } + + public sealed class ScriptUpgradesCache + { + public readonly ScriptUpgradesCacheInfo Info; + + public ScriptUpgradesCache(ScriptUpgradesCacheInfo info) + { + Info = info; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Crates/GrantUpgradeCrateAction.cs b/OpenRA.Mods.Common/Traits/Crates/GrantUpgradeCrateAction.cs index 3c2d20bf5f..52737ec4e1 100644 --- a/OpenRA.Mods.Common/Traits/Crates/GrantUpgradeCrateAction.cs +++ b/OpenRA.Mods.Common/Traits/Crates/GrantUpgradeCrateAction.cs @@ -9,12 +9,14 @@ #endregion using System.Linq; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Grants an upgrade to the collector.")] public class GrantUpgradeCrateActionInfo : CrateActionInfo { + [UpgradeGrantedReference] [Desc("The upgrades to apply.")] public readonly string[] Upgrades = { }; diff --git a/OpenRA.Mods.Common/Traits/GlobalUpgradable.cs b/OpenRA.Mods.Common/Traits/GlobalUpgradable.cs index de0c96f672..a49b854d9b 100644 --- a/OpenRA.Mods.Common/Traits/GlobalUpgradable.cs +++ b/OpenRA.Mods.Common/Traits/GlobalUpgradable.cs @@ -13,9 +13,14 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { + [Desc("Grants upgrades to the actor this is attached to when prerequisites are available.")] public class GlobalUpgradableInfo : ITraitInfo, Requires { + [UpgradeGrantedReference] + [Desc("List of upgrades to apply.")] public readonly string[] Upgrades = { }; + + [Desc("List of required prerequisites.")] public readonly string[] Prerequisites = { }; public object Create(ActorInitializer init) { return new GlobalUpgradable(init.Self, this); } diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs index 18f2e1aaae..2e4b8f3ab1 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs @@ -19,6 +19,7 @@ namespace OpenRA.Mods.Common.Traits { class GrantUpgradePowerInfo : SupportPowerInfo { + [UpgradeGrantedReference] [Desc("The upgrades to apply.")] public readonly string[] Upgrades = { }; diff --git a/OpenRA.Mods.Common/Traits/Upgrades/DeployToUpgrade.cs b/OpenRA.Mods.Common/Traits/Upgrades/DeployToUpgrade.cs index e22fcb2cc2..379b5e62a2 100644 --- a/OpenRA.Mods.Common/Traits/Upgrades/DeployToUpgrade.cs +++ b/OpenRA.Mods.Common/Traits/Upgrades/DeployToUpgrade.cs @@ -18,6 +18,7 @@ namespace OpenRA.Mods.Common.Traits { public class DeployToUpgradeInfo : ITraitInfo, Requires { + [UpgradeGrantedReference] [Desc("The upgrades to grant when deploying and revoke when undeploying.")] public readonly string[] Upgrades = { }; diff --git a/OpenRA.Mods.Common/Traits/Upgrades/UpgradableTrait.cs b/OpenRA.Mods.Common/Traits/Upgrades/UpgradableTrait.cs index 536b15e49a..06e2d4044d 100644 --- a/OpenRA.Mods.Common/Traits/Upgrades/UpgradableTrait.cs +++ b/OpenRA.Mods.Common/Traits/Upgrades/UpgradableTrait.cs @@ -7,6 +7,7 @@ * see COPYING. */ #endregion + using System.Collections.Generic; using System.Linq; using OpenRA.Traits; @@ -16,6 +17,7 @@ namespace OpenRA.Mods.Common.Traits /// Use as base class for *Info to subclass of UpgradableTrait. (See UpgradableTrait.) public abstract class UpgradableTraitInfo { + [UpgradeUsedReference] [Desc("The upgrade types which can enable or disable this trait.")] public readonly string[] UpgradeTypes = { }; diff --git a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeActorsNear.cs b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeActorsNear.cs index 7ed534b575..1dd9bd049c 100644 --- a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeActorsNear.cs +++ b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeActorsNear.cs @@ -18,6 +18,7 @@ namespace OpenRA.Mods.Common.Traits [Desc("Applies an upgrade to actors within a specified range.")] public class UpgradeActorsNearInfo : ITraitInfo { + [UpgradeGrantedReference] [Desc("The upgrades to grant.")] public readonly string[] Upgrades = { }; diff --git a/OpenRA.Mods.Common/Warheads/GrantUpgradeWarhead.cs b/OpenRA.Mods.Common/Warheads/GrantUpgradeWarhead.cs index fb8976fd95..d06509ec11 100644 --- a/OpenRA.Mods.Common/Warheads/GrantUpgradeWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/GrantUpgradeWarhead.cs @@ -20,6 +20,7 @@ namespace OpenRA.Mods.Common.Warheads { public class GrantUpgradeWarhead : Warhead { + [UpgradeGrantedReference] [Desc("The upgrades to apply.")] public readonly string[] Upgrades = { }; diff --git a/mods/d2k/rules/misc.yaml b/mods/d2k/rules/misc.yaml index 230809106e..7a95489235 100644 --- a/mods/d2k/rules/misc.yaml +++ b/mods/d2k/rules/misc.yaml @@ -103,10 +103,6 @@ crate: SelectionShares: 0 NoBaseSelectionShares: 9001 Units: mcv - GrantUpgradeCrateAction@cloak: - SelectionShares: 5 - Effect: cloak - Upgrades: cloak RenderSprites: Palette: effect WithCrateBody: diff --git a/mods/ra/maps/desert-shellmap/map.yaml b/mods/ra/maps/desert-shellmap/map.yaml index f1a3e27e83..04ffff3df0 100644 --- a/mods/ra/maps/desert-shellmap/map.yaml +++ b/mods/ra/maps/desert-shellmap/map.yaml @@ -1259,6 +1259,8 @@ Rules: ValuePerUnit: 0 LuaScript: Scripts: desert-shellmap.lua + ScriptUpgradesCache: + Upgrades: unkillable -StartGameNotification: ^Vehicle: GivesBounty: diff --git a/mods/ra/maps/fort-lonestar/map.yaml b/mods/ra/maps/fort-lonestar/map.yaml index 175e16a0ee..b05d28267f 100644 --- a/mods/ra/maps/fort-lonestar/map.yaml +++ b/mods/ra/maps/fort-lonestar/map.yaml @@ -488,6 +488,8 @@ Rules: -MPStartLocations: LuaScript: Scripts: fort-lonestar.lua + ScriptUpgradesCache: + Upgrades: unkillable FORTCRATE: Inherits: ^Crate SupportPowerCrateAction@parabombs: