diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 5a1e2c55d7..12f913b9e7 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -776,6 +776,7 @@
+
diff --git a/OpenRA.Mods.Common/Traits/Upgrades/StackedCondition.cs b/OpenRA.Mods.Common/Traits/Upgrades/StackedCondition.cs
new file mode 100644
index 0000000000..3d0e6d2c69
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Upgrades/StackedCondition.cs
@@ -0,0 +1,32 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2016 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, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ [Desc("Grant additional conditions when a specified condition has been granted multiple times.")]
+ public class StackedConditionInfo : TraitInfo
+ {
+ [FieldLoader.Require]
+ [UpgradeUsedReference]
+ [Desc("Condition to monitor.")]
+ public readonly string Condition = null;
+
+ [FieldLoader.Require]
+ [UpgradeGrantedReference]
+ [Desc("Conditions to grant when the monitored condition is granted multiple times.",
+ "The first entry is activated at 2x grants, second entry at 3x grants, and so on.")]
+ public readonly string[] StackedConditions = { };
+ }
+
+ public class StackedCondition { }
+}
diff --git a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs
index 29ff0cc0ef..4c49bbbfc8 100644
--- a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs
+++ b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs
@@ -31,7 +31,6 @@ namespace OpenRA.Mods.Common.Traits
{
/// Value used to represent an invalid token.
public static readonly int InvalidConditionToken = -1;
- string[] externalConditions = { };
class ConditionTimer
{
@@ -64,6 +63,15 @@ namespace OpenRA.Mods.Common.Traits
/// Each granted condition receives a unique token that is used when revoking.
Dictionary tokens = new Dictionary();
+ /// Set of whitelisted externally grantable conditions cached from ExternalConditions traits.
+ string[] externalConditions = { };
+
+ /// Set of conditions that are monitored for stacked bonuses, and the bonus conditions that they grant.
+ readonly Dictionary stackedConditions = new Dictionary();
+
+ /// Tokens granted by the stacked condition bonuses defined in stackedConditions.
+ readonly Dictionary> stackedTokens = new Dictionary>();
+
int nextToken = 1;
/// Temporary shim between the old and new upgrade/condition grant and revoke methods.
@@ -109,15 +117,21 @@ namespace OpenRA.Mods.Common.Traits
conditionCache[kv.Value] = conditionState.Tokens.Count > 0;
}
- // Update all traits with their initial condition state
- foreach (var consumer in allConsumers)
- consumer.ConditionsChanged(self, readOnlyConditionCache);
-
// Build external condition whitelist
externalConditions = self.Info.TraitInfos()
.SelectMany(t => t.Conditions)
.Distinct()
.ToArray();
+
+ foreach (var sc in self.Info.TraitInfos())
+ {
+ stackedConditions[sc.Condition] = sc.StackedConditions;
+ stackedTokens[sc.Condition] = new Stack();
+ }
+
+ // Update all traits with their initial condition state
+ foreach (var consumer in allConsumers)
+ consumer.ConditionsChanged(self, readOnlyConditionCache);
}
void UpdateConditionState(Actor self, string condition, int token, bool isRevoke)
@@ -135,6 +149,18 @@ namespace OpenRA.Mods.Common.Traits
foreach (var t in conditionState.Consumers)
t.ConditionsChanged(self, readOnlyConditionCache);
+
+ string[] sc;
+ if (stackedConditions.TryGetValue(condition, out sc))
+ {
+ var target = (conditionState.Tokens.Count - 1).Clamp(0, sc.Length);
+ var st = stackedTokens[condition];
+ for (var i = st.Count; i < target; i++)
+ st.Push(GrantCondition(self, sc[i]));
+
+ for (var i = st.Count; i > target; i--)
+ RevokeCondition(self, st.Pop());
+ }
}
/// Grants a specified condition.
@@ -193,7 +219,14 @@ namespace OpenRA.Mods.Common.Traits
if (state == null)
throw new InvalidOperationException("AcceptsExternalCondition cannot be queried before the actor has been fully created.");
- return externalConditions.Contains(condition) && !conditionCache[condition];
+ if (!externalConditions.Contains(condition))
+ return false;
+
+ string[] sc;
+ if (stackedConditions.TryGetValue(condition, out sc))
+ return stackedTokens[condition].Count < sc.Length;
+
+ return !conditionCache[condition];
}
/// Returns whether the specified token is valid for RevokeCondition