diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index c0938fdadc..12f913b9e7 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -307,7 +307,7 @@
-
+
@@ -417,7 +417,7 @@
-
+
@@ -480,7 +480,7 @@
-
+
@@ -564,7 +564,7 @@
-
+
@@ -775,6 +775,8 @@
+
+
diff --git a/OpenRA.Mods.Common/Traits/Carryable.cs b/OpenRA.Mods.Common/Traits/Carryable.cs
index 8f45f29a2b..c2a1c3e8b9 100644
--- a/OpenRA.Mods.Common/Traits/Carryable.cs
+++ b/OpenRA.Mods.Common/Traits/Carryable.cs
@@ -52,6 +52,8 @@ namespace OpenRA.Mods.Common.Traits
protected override void Created(Actor self)
{
upgradeManager = self.Trait();
+
+ base.Created(self);
}
public virtual void Attached(Actor self)
diff --git a/OpenRA.Mods.Common/Traits/Crates/GrantUpgradeCrateAction.cs b/OpenRA.Mods.Common/Traits/Crates/GrantExternalConditionCrateAction.cs
similarity index 65%
rename from OpenRA.Mods.Common/Traits/Crates/GrantUpgradeCrateAction.cs
rename to OpenRA.Mods.Common/Traits/Crates/GrantExternalConditionCrateAction.cs
index 3482ad574e..daa82819b0 100644
--- a/OpenRA.Mods.Common/Traits/Crates/GrantUpgradeCrateAction.cs
+++ b/OpenRA.Mods.Common/Traits/Crates/GrantExternalConditionCrateAction.cs
@@ -15,11 +15,11 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Grants an upgrade to the collector.")]
- public class GrantUpgradeCrateActionInfo : CrateActionInfo
+ public class GrantExternalConditionCrateActionInfo : CrateActionInfo
{
- [UpgradeGrantedReference, FieldLoader.Require]
- [Desc("The upgrades to apply.")]
- public readonly string[] Upgrades = { };
+ [FieldLoader.Require]
+ [Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
+ public readonly string Condition = null;
[Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent upgrade.")]
public readonly int Duration = 0;
@@ -30,37 +30,36 @@ namespace OpenRA.Mods.Common.Traits
[Desc("The maximum number of extra collectors to grant the crate action to.", "-1 = no limit")]
public readonly int MaxExtraCollectors = 4;
- public override object Create(ActorInitializer init) { return new GrantUpgradeCrateAction(init.Self, this); }
+ public override object Create(ActorInitializer init) { return new GrantExternalConditionCrateAction(init.Self, this); }
}
- public class GrantUpgradeCrateAction : CrateAction
+ public class GrantExternalConditionCrateAction : CrateAction
{
readonly Actor self;
- readonly GrantUpgradeCrateActionInfo info;
+ readonly GrantExternalConditionCrateActionInfo info;
- public GrantUpgradeCrateAction(Actor self, GrantUpgradeCrateActionInfo info)
+ public GrantExternalConditionCrateAction(Actor self, GrantExternalConditionCrateActionInfo info)
: base(self, info)
{
this.self = self;
this.info = info;
}
- bool AcceptsUpgrade(Actor a)
+ bool AcceptsCondition(Actor a)
{
var um = a.TraitOrDefault();
- return um != null && (info.Duration > 0 ?
- info.Upgrades.Any(u => um.AcknowledgesUpgrade(a, u)) : info.Upgrades.Any(u => um.AcceptsUpgrade(a, u)));
+ return um != null && um.AcceptsExternalCondition(a, info.Condition);
}
public override int GetSelectionShares(Actor collector)
{
- return AcceptsUpgrade(collector) ? info.SelectionShares : 0;
+ return AcceptsCondition(collector) ? info.SelectionShares : 0;
}
public override void Activate(Actor collector)
{
var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, info.Range)
- .Where(a => a != self && a != collector && a.Owner == collector.Owner && AcceptsUpgrade(a));
+ .Where(a => a != self && a != collector && a.Owner == collector.Owner && AcceptsCondition(a));
if (info.MaxExtraCollectors > -1)
actorsInRange = actorsInRange.Take(info.MaxExtraCollectors);
@@ -73,19 +72,10 @@ namespace OpenRA.Mods.Common.Traits
continue;
var um = a.TraitOrDefault();
- foreach (var u in info.Upgrades)
- {
- if (info.Duration > 0)
- {
- if (um.AcknowledgesUpgrade(a, u))
- um.GrantTimedUpgrade(a, u, info.Duration);
- }
- else
- {
- if (um.AcceptsUpgrade(a, u))
- um.GrantUpgrade(a, u, this);
- }
- }
+
+ // Condition token is ignored because we never revoke this condition.
+ if (um != null)
+ um.GrantCondition(a, info.Condition, true, info.Duration);
}
});
diff --git a/OpenRA.Mods.Common/Traits/Render/TimedUpgradeBar.cs b/OpenRA.Mods.Common/Traits/Render/TimedConditionBar.cs
similarity index 65%
rename from OpenRA.Mods.Common/Traits/Render/TimedUpgradeBar.cs
rename to OpenRA.Mods.Common/Traits/Render/TimedConditionBar.cs
index 7298e40f8f..c97d5f77cd 100644
--- a/OpenRA.Mods.Common/Traits/Render/TimedUpgradeBar.cs
+++ b/OpenRA.Mods.Common/Traits/Render/TimedConditionBar.cs
@@ -15,38 +15,35 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits.Render
{
[Desc("Visualizes the remaining time for an upgrade.")]
- class TimedUpgradeBarInfo : ITraitInfo, Requires
+ class TimedConditionBarInfo : ITraitInfo, Requires
{
[FieldLoader.Require]
- [Desc("Upgrade that this bar corresponds to")]
- public readonly string Upgrade = null;
+ [Desc("Condition that this bar corresponds to")]
+ public readonly string Condition = null;
public readonly Color Color = Color.Red;
- public object Create(ActorInitializer init) { return new TimedUpgradeBar(init.Self, this); }
+ public object Create(ActorInitializer init) { return new TimedConditionBar(init.Self, this); }
}
- class TimedUpgradeBar : ISelectionBar, INotifyCreated
+ class TimedConditionBar : ISelectionBar, IConditionTimerWatcher
{
- readonly TimedUpgradeBarInfo info;
+ readonly TimedConditionBarInfo info;
readonly Actor self;
float value;
- public TimedUpgradeBar(Actor self, TimedUpgradeBarInfo info)
+ public TimedConditionBar(Actor self, TimedConditionBarInfo info)
{
this.self = self;
this.info = info;
}
- public void Created(Actor self)
+ void IConditionTimerWatcher.Update(int duration, int remaining)
{
- self.Trait().RegisterWatcher(info.Upgrade, Update);
+ value = duration > 0 ? remaining * 1f / duration : 0;
}
- public void Update(int duration, int remaining)
- {
- value = remaining * 1f / duration;
- }
+ string IConditionTimerWatcher.Condition { get { return info.Condition; } }
float ISelectionBar.GetValue()
{
diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs
similarity index 75%
rename from OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs
rename to OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs
index 1a5aba77fe..74575a64dd 100644
--- a/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs
+++ b/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs
@@ -19,31 +19,33 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
- class GrantUpgradePowerInfo : SupportPowerInfo
+ class GrantExternalConditionPowerInfo : SupportPowerInfo
{
- [UpgradeGrantedReference, FieldLoader.Require]
- [Desc("The upgrades to apply.")]
- public readonly string[] Upgrades = { };
+ [FieldLoader.Require]
+ [Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
+ public readonly string Condition = null;
- [Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent upgrade.")]
+ [Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent condition.")]
public readonly int Duration = 0;
[Desc("Cells - affects whole cells only")]
public readonly int Range = 1;
- public readonly string GrantUpgradeSound = "ironcur9.aud";
+
+ [Desc("Sound to instantly play at the targeted area.")]
+ public readonly string OnFireSound = null;
[SequenceReference, Desc("Sequence to play for granting actor when activated.",
"This requires the actor to have the WithSpriteBody trait or one of its derivatives.")]
- public readonly string GrantUpgradeSequence = "active";
+ public readonly string Sequence = "active";
- public override object Create(ActorInitializer init) { return new GrantUpgradePower(init.Self, this); }
+ public override object Create(ActorInitializer init) { return new GrantExternalConditionPower(init.Self, this); }
}
- class GrantUpgradePower : SupportPower
+ class GrantExternalConditionPower : SupportPower
{
- GrantUpgradePowerInfo info;
+ readonly GrantExternalConditionPowerInfo info;
- public GrantUpgradePower(Actor self, GrantUpgradePowerInfo info)
+ public GrantExternalConditionPower(Actor self, GrantExternalConditionPowerInfo info)
: base(self, info)
{
this.info = info;
@@ -60,30 +62,18 @@ namespace OpenRA.Mods.Common.Traits
base.Activate(self, order, manager);
var wsb = self.TraitOrDefault();
- if (wsb != null && wsb.DefaultAnimation.HasSequence(info.GrantUpgradeSequence))
- wsb.PlayCustomAnimation(self, info.GrantUpgradeSequence, () => wsb.CancelCustomAnimation(self));
+ if (wsb != null && wsb.DefaultAnimation.HasSequence(info.Sequence))
+ wsb.PlayCustomAnimation(self, info.Sequence, () => wsb.CancelCustomAnimation(self));
- Game.Sound.Play(info.GrantUpgradeSound, self.World.Map.CenterOfCell(order.TargetLocation));
+ Game.Sound.Play(info.OnFireSound, self.World.Map.CenterOfCell(order.TargetLocation));
foreach (var a in UnitsInRange(order.TargetLocation))
{
var um = a.TraitOrDefault();
- if (um == null)
- continue;
- foreach (var u in info.Upgrades)
- {
- if (info.Duration > 0)
- {
- if (um.AcknowledgesUpgrade(a, u))
- um.GrantTimedUpgrade(a, u, info.Duration);
- }
- else
- {
- if (um.AcceptsUpgrade(a, u))
- um.GrantUpgrade(a, u, this);
- }
- }
+ // Condition token is ignored because we never revoke this condition.
+ if (um != null)
+ um.GrantCondition(a, info.Condition, true, info.Duration);
}
}
@@ -101,20 +91,19 @@ namespace OpenRA.Mods.Common.Traits
return false;
var um = a.TraitOrDefault();
- return um != null && (info.Duration > 0 ?
- info.Upgrades.Any(u => um.AcknowledgesUpgrade(a, u)) : info.Upgrades.Any(u => um.AcceptsUpgrade(a, u)));
+ return um != null && um.AcceptsExternalCondition(a, info.Condition);
});
}
class SelectUpgradeTarget : IOrderGenerator
{
- readonly GrantUpgradePower power;
+ readonly GrantExternalConditionPower power;
readonly int range;
readonly Sprite tile;
readonly SupportPowerManager manager;
readonly string order;
- public SelectUpgradeTarget(World world, string order, SupportPowerManager manager, GrantUpgradePower power)
+ public SelectUpgradeTarget(World world, string order, SupportPowerManager manager, GrantExternalConditionPower power)
{
// Clear selection if using Left-Click Orders
if (Game.Settings.Game.UseClassicMouseStyle)
diff --git a/OpenRA.Mods.Common/Traits/Upgrades/ExternalConditions.cs b/OpenRA.Mods.Common/Traits/Upgrades/ExternalConditions.cs
new file mode 100644
index 0000000000..c544a6c188
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Upgrades/ExternalConditions.cs
@@ -0,0 +1,25 @@
+#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("Lists conditions that are accepted from external sources (Lua, warheads, etc).",
+ "Externally granted conditions that aren't explicitly whitelisted will be silently ignored.")]
+ public class ExternalConditionsInfo : TraitInfo
+ {
+ [UpgradeGrantedReference]
+ public readonly string[] Conditions = { };
+ }
+
+ public class ExternalConditions { }
+}
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 d7a07ef96e..4c49bbbfc8 100644
--- a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs
+++ b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs
@@ -17,6 +17,13 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
+ [RequireExplicitImplementation]
+ public interface IConditionTimerWatcher
+ {
+ string Condition { get; }
+ void Update(int duration, int remaining);
+ }
+
[Desc("Attach this to a unit to enable dynamic upgrades by warheads, experience, crates, support powers, etc.")]
public class UpgradeManagerInfo : TraitInfo, Requires { }
@@ -25,38 +32,16 @@ namespace OpenRA.Mods.Common.Traits
/// Value used to represent an invalid token.
public static readonly int InvalidConditionToken = -1;
- class TimedCondition
+ class ConditionTimer
{
- public class ConditionSource
- {
- public readonly object Source;
- public int Remaining;
-
- public ConditionSource(int duration, object source)
- {
- Remaining = duration;
- Source = source;
- }
- }
-
- public readonly string Condition;
+ public readonly int Token;
public readonly int Duration;
- public readonly HashSet Sources;
- public int Remaining; // Equal to maximum of all Sources.Remaining
+ public int Remaining;
- public TimedCondition(string condition, int duration, object source)
+ public ConditionTimer(int token, int duration)
{
- Condition = condition;
- Duration = duration;
- Remaining = duration;
- Sources = new HashSet { new ConditionSource(duration, source) };
- }
-
- public void Tick()
- {
- Remaining--;
- foreach (var source in Sources)
- source.Remaining--;
+ Token = token;
+ Duration = Remaining = duration;
}
}
@@ -69,23 +54,31 @@ namespace OpenRA.Mods.Common.Traits
public readonly HashSet Tokens = new HashSet();
/// External callbacks that are to be executed when a timed condition changes.
- public readonly List> Watchers = new List>();
+ public readonly List Watchers = new List();
}
- readonly List timedConditions = new List();
-
Dictionary state;
+ readonly Dictionary> timers = new Dictionary>();
/// 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.
- Dictionary, int> objectTokenShim = new Dictionary, int>();
+ readonly Dictionary, int> objectTokenShim = new Dictionary, int>();
/// Cache of condition -> enabled state for quick evaluation of boolean conditions.
- Dictionary conditionCache = new Dictionary();
+ readonly Dictionary conditionCache = new Dictionary();
/// Read-only version of conditionCache that is passed to IConditionConsumers.
IReadOnlyDictionary readOnlyConditionCache;
@@ -96,12 +89,19 @@ namespace OpenRA.Mods.Common.Traits
readOnlyConditionCache = new ReadOnlyDictionary(conditionCache);
var allConsumers = new HashSet();
+ var allWatchers = self.TraitsImplementing().ToList();
+
foreach (var consumer in self.TraitsImplementing())
{
allConsumers.Add(consumer);
foreach (var condition in consumer.Conditions)
{
- state.GetOrAdd(condition).Consumers.Add(consumer);
+ var cs = state.GetOrAdd(condition);
+ cs.Consumers.Add(consumer);
+ foreach (var w in allWatchers)
+ if (w.Condition == condition)
+ cs.Watchers.Add(w);
+
conditionCache[condition] = false;
}
}
@@ -117,6 +117,18 @@ namespace OpenRA.Mods.Common.Traits
conditionCache[kv.Value] = conditionState.Tokens.Count > 0;
}
+ // 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);
@@ -137,15 +149,35 @@ 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.
/// The token that is used to revoke this condition.
- public int GrantCondition(Actor self, string condition)
+ /// Validate against the external condition whitelist.
+ /// Automatically revoke condition after this delay if non-zero.
+ public int GrantCondition(Actor self, string condition, bool external = false, int duration = 0)
{
+ if (external && !externalConditions.Contains(condition))
+ return InvalidConditionToken;
+
var token = nextToken++;
tokens.Add(token, condition);
+ if (duration > 0)
+ timers.GetOrAdd(condition).Add(new ConditionTimer(token, duration));
+
// Conditions may be granted before the state is initialized.
// These conditions will be processed in INotifyCreated.Created.
if (state != null)
@@ -155,7 +187,7 @@ namespace OpenRA.Mods.Common.Traits
}
/// Revokes a previously granted condition.
- /// The invalid token ID
+ /// The invalid token ID.
/// The token ID returned by GrantCondition.
public int RevokeCondition(Actor self, int token)
{
@@ -165,6 +197,15 @@ namespace OpenRA.Mods.Common.Traits
tokens.Remove(token);
+ // Clean up timers
+ List ct;
+ if (timers.TryGetValue(condition, out ct))
+ {
+ ct.RemoveAll(t => t.Token == token);
+ if (!ct.Any())
+ timers.Remove(condition);
+ }
+
// Conditions may be granted and revoked before the state is initialized.
if (state != null)
UpdateConditionState(self, condition, token, true);
@@ -172,6 +213,61 @@ namespace OpenRA.Mods.Common.Traits
return InvalidConditionToken;
}
+ /// Returns true if the given external condition will have an effect on this actor.
+ public bool AcceptsExternalCondition(Actor self, string condition)
+ {
+ if (state == null)
+ throw new InvalidOperationException("AcceptsExternalCondition cannot be queried before the actor has been fully created.");
+
+ 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
+ public bool TokenValid(Actor self, int token)
+ {
+ return tokens.ContainsKey(token);
+ }
+
+ readonly HashSet timersToRemove = new HashSet();
+ void ITick.Tick(Actor self)
+ {
+ // Watchers will be receiving notifications while the condition is enabled.
+ // They will also be provided with the number of ticks before the condition is disabled,
+ // as well as the duration of the longest active instance.
+ foreach (var kv in timers)
+ {
+ var duration = 0;
+ var remaining = 0;
+ foreach (var t in kv.Value)
+ {
+ if (--t.Remaining <= 0)
+ timersToRemove.Add(t.Token);
+
+ // Track the duration and remaining time for the longest remaining timer
+ if (t.Remaining > remaining)
+ {
+ duration = t.Duration;
+ remaining = t.Remaining;
+ }
+ }
+
+ foreach (var w in state[kv.Key].Watchers)
+ w.Update(duration, remaining);
+ }
+
+ foreach (var t in timersToRemove)
+ RevokeCondition(self, t);
+
+ timersToRemove.Clear();
+ }
+
#region Shim methods for legacy upgrade granting code
void CheckCanManageConditions()
@@ -180,36 +276,12 @@ namespace OpenRA.Mods.Common.Traits
throw new InvalidOperationException("Conditions cannot be managed until the actor has been fully created.");
}
- /// Upgrade level increments are limited to dupesAllowed per source, i.e., if a single
- /// source attempts granting more upgrades than dupesAllowed, they will not accumulate. They will
- /// replace each other instead, leaving only the most recently granted upgrade active. Each new
- /// upgrade granting request will increment the upgrade's level until AcceptsUpgrade starts
- /// returning false. Then, when no new levels are accepted, the upgrade source with the shortest
- /// remaining upgrade duration will be replaced by the new source.
public void GrantTimedUpgrade(Actor self, string upgrade, int duration, object source = null, int dupesAllowed = 1)
{
- var timed = timedConditions.FirstOrDefault(u => u.Condition == upgrade);
- if (timed == null)
- {
- timed = new TimedCondition(upgrade, duration, source);
- timedConditions.Add(timed);
- GrantUpgrade(self, upgrade, timed);
- return;
- }
-
- var srcs = timed.Sources.Where(s => s.Source == source);
- if (srcs.Count() < dupesAllowed)
- {
- timed.Sources.Add(new TimedCondition.ConditionSource(duration, source));
- if (AcceptsUpgrade(self, upgrade))
- GrantUpgrade(self, upgrade, timed);
- else
- timed.Sources.Remove(timed.Sources.MinBy(s => s.Remaining));
- }
- else
- srcs.MinBy(s => s.Remaining).Remaining = duration;
-
- timed.Remaining = Math.Max(duration, timed.Remaining);
+ CheckCanManageConditions();
+ var token = GrantCondition(self, upgrade, false, duration);
+ if (source != null)
+ objectTokenShim[Pair.New(source, upgrade)] = token;
}
public void GrantUpgrade(Actor self, string upgrade, object source)
@@ -242,39 +314,6 @@ namespace OpenRA.Mods.Common.Traits
return !enabled;
}
- public void RegisterWatcher(string upgrade, Action action)
- {
- CheckCanManageConditions();
-
- ConditionState s;
- if (!state.TryGetValue(upgrade, out s))
- return;
-
- s.Watchers.Add(action);
- }
-
- /// Watchers will be receiving notifications while the condition is enabled.
- /// They will also be provided with the number of ticks before the condition is disabled,
- /// as well as the duration in ticks of the timed upgrade (provided in the first call to
- /// GrantTimedUpgrade).
- void ITick.Tick(Actor self)
- {
- foreach (var u in timedConditions)
- {
- u.Tick();
- foreach (var source in u.Sources)
- if (source.Remaining <= 0)
- RevokeUpgrade(self, u.Condition, u);
-
- u.Sources.RemoveWhere(source => source.Remaining <= 0);
-
- foreach (var a in state[u.Condition].Watchers)
- a(u.Duration, u.Remaining);
- }
-
- timedConditions.RemoveAll(u => u.Remaining <= 0);
- }
-
#endregion
}
}
diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
index 7718eca762..d0bac4db7c 100644
--- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
+++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
@@ -92,6 +92,28 @@ namespace OpenRA.Mods.Common.UtilityCommands
catch { }
}
+ static void RenameNodeKey(MiniYamlNode node, string key)
+ {
+ var parts = node.Key.Split('@');
+ node.Key = key;
+ if (parts.Length > 1)
+ node.Key += "@" + parts[1];
+ }
+
+ static void ConvertUpgradesToCondition(MiniYamlNode parent, MiniYamlNode node, string upgradesKey, string conditionKey)
+ {
+ var upgradesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == upgradesKey);
+ if (upgradesNode != null)
+ {
+ var conditions = FieldLoader.GetValue("", upgradesNode.Value.Value);
+ if (conditions.Length > 1)
+ Console.WriteLine("Unable to automatically migrate {0}:{1} {2} to {3}. This must be corrected manually",
+ parent.Key, node.Key, upgradesKey, conditionKey);
+ else
+ upgradesNode.Key = conditionKey;
+ }
+ }
+
internal static void UpgradeActorRules(ModData modData, int engineVersion, ref List nodes, MiniYamlNode parent, int depth)
{
var addNodes = new List();
@@ -118,10 +140,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (s != null)
s.Key = "Image";
- var parts = node.Key.Split('@');
- node.Key = "WithDamageOverlay";
- if (parts.Length > 1)
- node.Key += "@" + parts[1];
+ RenameNodeKey(node, "WithDamageOverlay");
}
}
@@ -135,13 +154,9 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (engineVersion < 20160611)
{
// Deprecated WithSpriteRotorOverlay
- if (depth == 1 && node.Key.StartsWith("WithSpriteRotorOverlay"))
+ if (depth == 1 && node.Key.StartsWith("WithSpriteRotorOverlay", StringComparison.Ordinal))
{
- var parts = node.Key.Split('@');
- node.Key = "WithIdleOverlay";
- if (parts.Length > 1)
- node.Key += "@" + parts[1];
-
+ RenameNodeKey(node, "WithIdleOverlay");
Console.WriteLine("The 'WithSpriteRotorOverlay' trait has been removed.");
Console.WriteLine("Its functionality can be fully replicated with 'WithIdleOverlay' + upgrades.");
Console.WriteLine("Look at the helicopters in our RA / C&C1 mods for implementation details.");
@@ -282,13 +297,8 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (engineVersion < 20160818)
{
- if (depth == 1 && node.Key.StartsWith("UpgradeOnDamage"))
- {
- var parts = node.Key.Split('@');
- node.Key = "UpgradeOnDamageState";
- if (parts.Length > 1)
- node.Key += "@" + parts[1];
- }
+ if (depth == 1 && node.Key.StartsWith("UpgradeOnDamage", StringComparison.Ordinal))
+ RenameNodeKey(node, "UpgradeOnDamageState");
}
// DisplayTimer was replaced by DisplayTimerStances
@@ -435,7 +445,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
- // Rename Replaced upgrade consumers with conditions
+ // Replaced upgrade consumers with conditions
if (engineVersion < 20161117)
{
var upgradeTypesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "UpgradeTypes");
@@ -480,16 +490,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (engineVersion < 20161119)
{
// Migrated carryalls over to new conditions system
- var carryableUpgradesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "CarryableUpgrades");
- if (carryableUpgradesNode != null)
- {
- var conditions = FieldLoader.GetValue("", carryableUpgradesNode.Value.Value);
- if (conditions.Length > 1)
- Console.WriteLine("Unable to automatically migrate {0}:{1} CarryableUpgrades to CarriedCondition. This must be corrected manually",
- parent.Key, node.Key);
- else
- carryableUpgradesNode.Key = "CarriedCondition";
- }
+ ConvertUpgradesToCondition(parent, node, "CarryableUpgrades", "CarriedCondition");
if (node.Key == "WithDecorationCarryable")
{
@@ -498,6 +499,39 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
+ if (engineVersion < 20161120)
+ {
+ if (node.Key.StartsWith("TimedUpgradeBar", StringComparison.Ordinal))
+ {
+ RenameNodeKey(node, "TimedConditionBar");
+ ConvertUpgradesToCondition(parent, node, "Upgrade", "Condition");
+ }
+
+ if (node.Key.StartsWith("GrantUpgradePower", StringComparison.Ordinal))
+ {
+ Console.WriteLine("GrantUpgradePower Condition must be manually added to all target actor's ExternalConditions list.");
+ RenameNodeKey(node, "GrantExternalConditionPower");
+ ConvertUpgradesToCondition(parent, node, "Upgrades", "Condition");
+
+ var soundNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "GrantUpgradeSound");
+ if (soundNode != null)
+ soundNode.Key = "OnFireSound";
+ else
+ node.Value.Nodes.Add(new MiniYamlNode("OnFireSound", "ironcur9.aud"));
+
+ var sequenceNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "GrantUpgradeSequence");
+ if (sequenceNode != null)
+ sequenceNode.Key = "Sequence";
+ }
+
+ if (node.Key.StartsWith("GrantUpgradeCrateAction", StringComparison.Ordinal))
+ {
+ Console.WriteLine("GrantUpgradeCrateAction Condition must be manually added to all target actor's ExternalConditions list.");
+ RenameNodeKey(node, "GrantExternalConditionCrateAction");
+ ConvertUpgradesToCondition(parent, node, "Upgrades", "Condition");
+ }
+ }
+
UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}
@@ -567,6 +601,16 @@ namespace OpenRA.Mods.Common.UtilityCommands
node.Key = "LaunchAngle";
}
+ if (engineVersion < 20161120)
+ {
+ if (node.Key.StartsWith("Warhead", StringComparison.Ordinal) && node.Value.Value == "GrantUpgrade")
+ {
+ node.Value.Value = "GrantExternalCondition";
+ Console.WriteLine("GrantExternalCondition Condition must be manually added to all target actor's ExternalConditions list.");
+ ConvertUpgradesToCondition(parent, node, "Upgrades", "Condition");
+ }
+ }
+
UpgradeWeaponRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}
}
diff --git a/OpenRA.Mods.Common/Warheads/GrantUpgradeWarhead.cs b/OpenRA.Mods.Common/Warheads/GrantExternalConditionWarhead.cs
similarity index 63%
rename from OpenRA.Mods.Common/Warheads/GrantUpgradeWarhead.cs
rename to OpenRA.Mods.Common/Warheads/GrantExternalConditionWarhead.cs
index add510f084..73173a4f6e 100644
--- a/OpenRA.Mods.Common/Warheads/GrantUpgradeWarhead.cs
+++ b/OpenRA.Mods.Common/Warheads/GrantExternalConditionWarhead.cs
@@ -10,19 +10,18 @@
#endregion
using System.Collections.Generic;
-using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Warheads
{
- public class GrantUpgradeWarhead : Warhead
+ public class GrantExternalConditionWarhead : Warhead
{
- [UpgradeGrantedReference]
- [Desc("The upgrades to apply.")]
- public readonly string[] Upgrades = { };
+ [FieldLoader.Require]
+ [Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
+ public readonly string Condition = null;
- [Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent upgrade.")]
+ [Desc("Duration of the condition (in ticks). Set to 0 for a permanent condition.")]
public readonly int Duration = 0;
public readonly WDist Range = WDist.FromCells(1);
@@ -38,22 +37,10 @@ namespace OpenRA.Mods.Common.Warheads
continue;
var um = a.TraitOrDefault();
- if (um == null)
- continue;
- foreach (var u in Upgrades)
- {
- if (Duration > 0)
- {
- if (um.AcknowledgesUpgrade(a, u))
- um.GrantTimedUpgrade(a, u, Duration, firedBy, Upgrades.Count(upg => upg == u));
- }
- else
- {
- if (um.AcceptsUpgrade(a, u))
- um.GrantUpgrade(a, u, this);
- }
- }
+ // Condition token is ignored because we never revoke this condition.
+ if (um != null && um.AcceptsExternalCondition(a, Condition))
+ um.GrantCondition(a, Condition, true, Duration);
}
}
}
diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml
index 6786586e8c..79efc71355 100644
--- a/mods/cnc/rules/defaults.yaml
+++ b/mods/cnc/rules/defaults.yaml
@@ -159,6 +159,8 @@
CloakSound: trans1.aud
UncloakSound: trans1.aud
RequiresCondition: cloak
+ ExternalConditions@CLOAK:
+ Conditions: cloak
MustBeDestroyed:
Voiced:
VoiceSet: VehicleVoice
diff --git a/mods/cnc/rules/misc.yaml b/mods/cnc/rules/misc.yaml
index 52a1e267bd..efeab016f0 100644
--- a/mods/cnc/rules/misc.yaml
+++ b/mods/cnc/rules/misc.yaml
@@ -16,10 +16,10 @@ CRATE:
ExplodeCrateAction@fire:
Weapon: Napalm.Crate
SelectionShares: 5
- GrantUpgradeCrateAction@cloak:
+ GrantExternalConditionCrateAction@cloak:
SelectionShares: 5
Effect: cloak
- Upgrades: cloak
+ Condition: cloak
GiveMcvCrateAction:
SelectionShares: 0
NoBaseSelectionShares: 120
diff --git a/mods/ra/maps/bomber-john/rules.yaml b/mods/ra/maps/bomber-john/rules.yaml
index dd1f3b6566..2d92b36338 100644
--- a/mods/ra/maps/bomber-john/rules.yaml
+++ b/mods/ra/maps/bomber-john/rules.yaml
@@ -119,7 +119,7 @@ FTUR:
BeginChargeSound: chrochr1.aud
EndChargeSound: chrordy1.aud
Range: 3
- GrantUpgradePower@IRONCURTAIN:
+ GrantExternalConditionPower@IRONCURTAIN:
Icon: invuln
ChargeTime: 30
Description: Invulnerability
@@ -129,8 +129,9 @@ FTUR:
BeginChargeSound: ironchg1.aud
EndChargeSound: ironrdy1.aud
Range: 1
- Upgrades: invulnerability
- GrantUpgradeSequence: idle
+ Condition: invulnerability
+ Sequence: idle
+ OnFireSound: ironcur9.aud
Power:
Amount: 0
@@ -186,7 +187,7 @@ T17:
Duration: 999999
KillCargo: yes
Range: 3
- GrantUpgradePower@IRONCURTAIN:
+ GrantExternalConditionPower@IRONCURTAIN:
Icon: invuln
ChargeTime: 30
Description: Invulnerability
@@ -196,5 +197,6 @@ T17:
BeginChargeSound: ironchg1.aud
EndChargeSound: ironrdy1.aud
Range: 1
- Upgrades: invulnerability
- GrantUpgradeSequence: idle
+ Condition: invulnerability
+ Sequence: idle
+ OnFireSound: ironcur9.aud
diff --git a/mods/ra/maps/fort-lonestar/rules.yaml b/mods/ra/maps/fort-lonestar/rules.yaml
index 22def065b8..3ac6f9f0f0 100644
--- a/mods/ra/maps/fort-lonestar/rules.yaml
+++ b/mods/ra/maps/fort-lonestar/rules.yaml
@@ -69,11 +69,11 @@ FORTCRATE:
GiveUnitCrateAction@e7:
Units: e7
SelectionShares: 10
- GrantUpgradeCrateAction@ironcurtain:
+ GrantExternalConditionCrateAction@ironcurtain:
SelectionShares: 10
Effect: invuln
Notification: ironcur9.aud
- Upgrades: invulnerability
+ Condition: invulnerability
Duration: 1200
ExplodeCrateAction@bigboom:
Weapon: SCUD
diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml
index d7ad8b18c0..f6347c96af 100644
--- a/mods/ra/rules/defaults.yaml
+++ b/mods/ra/rules/defaults.yaml
@@ -15,7 +15,7 @@
^GainsExperience:
GainsExperience:
- Upgrades:
+ Upgrades:
200: rank-veteran-1
400: rank-veteran-2
800: rank-veteran-3
@@ -121,8 +121,10 @@
DamageMultiplier@IRONCURTAIN:
RequiresCondition: invulnerability
Modifier: 0
- TimedUpgradeBar:
- Upgrade: invulnerability
+ TimedConditionBar:
+ Condition: invulnerability
+ ExternalConditions@INVULNERABILITY:
+ Conditions: invulnerability
^Vehicle:
Inherits@1: ^ExistsInWorld
diff --git a/mods/ra/rules/misc.yaml b/mods/ra/rules/misc.yaml
index d0e626927c..6054918c4c 100644
--- a/mods/ra/rules/misc.yaml
+++ b/mods/ra/rules/misc.yaml
@@ -90,11 +90,11 @@ CRATE:
Units: e1,e1,e4,e4,e3,e3,e3
ValidFactions: soviet, russia, ukraine
TimeDelay: 4500
- GrantUpgradeCrateAction@invuln:
+ GrantExternalConditionCrateAction@invuln:
SelectionShares: 5
Effect: invuln
Notification: ironcur9.aud
- Upgrades: invulnerability
+ Condition: invulnerability
Duration: 600
MONEYCRATE:
diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml
index 58730edadc..d485a486c2 100644
--- a/mods/ra/rules/structures.yaml
+++ b/mods/ra/rules/structures.yaml
@@ -303,7 +303,7 @@ IRON:
Range: 10c0
Bib:
HasMinibib: Yes
- GrantUpgradePower@IRONCURTAIN:
+ GrantExternalConditionPower@IRONCURTAIN:
Icon: invuln
ChargeTime: 120
Description: Invulnerability
@@ -314,7 +314,8 @@ IRON:
BeginChargeSpeechNotification: IronCurtainCharging
EndChargeSpeechNotification: IronCurtainReady
DisplayRadarPing: True
- Upgrades: invulnerability
+ Condition: invulnerability
+ OnFireSound: ironcur9.aud
SupportPowerChargeBar:
Power:
Amount: -200
diff --git a/mods/ts/rules/civilian-vehicles.yaml b/mods/ts/rules/civilian-vehicles.yaml
index 75c53f476c..f43065b380 100644
--- a/mods/ts/rules/civilian-vehicles.yaml
+++ b/mods/ts/rules/civilian-vehicles.yaml
@@ -102,7 +102,7 @@ BUS:
MaxWeight: 20
PipCount: 5
UnloadVoice: Unload
- LoadingUpgrades: notmobile
+ LoadingUpgrades: loading
EjectOnDeath: true
PICK:
@@ -126,7 +126,7 @@ PICK:
MaxWeight: 2
PipCount: 5
UnloadVoice: Unload
- LoadingUpgrades: notmobile
+ LoadingUpgrades: loading
EjectOnDeath: true
CAR:
@@ -150,7 +150,7 @@ CAR:
MaxWeight: 4
PipCount: 5
UnloadVoice: Unload
- LoadingUpgrades: notmobile
+ LoadingUpgrades: loading
EjectOnDeath: true
WINI:
@@ -174,7 +174,7 @@ WINI:
MaxWeight: 5
PipCount: 5
UnloadVoice: Unload
- LoadingUpgrades: notmobile
+ LoadingUpgrades: loading
EjectOnDeath: true
LOCOMOTIVE:
diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml
index 679010a8d5..c3d01b1bc5 100644
--- a/mods/ts/rules/defaults.yaml
+++ b/mods/ts/rules/defaults.yaml
@@ -64,6 +64,8 @@
ReferencePoint: Bottom, Right
RequiresCondition: rank-elite
ZOffset: 256
+ ExternalConditions@CRATES:
+ Conditions: crate-firepower, crate-damage, crate-speed, crate-cloak
^EmpDisable:
UpgradeOverlay@EMPDISABLE:
@@ -71,8 +73,8 @@
Palette: disabled
DisableOnUpgrade@EMPDISABLE:
RequiresCondition: empdisable
- TimedUpgradeBar@EMPDISABLE:
- Upgrade: empdisable
+ TimedConditionBar@EMPDISABLE:
+ Condition: empdisable
Color: FFFFFF
WithIdleOverlay@EMPDISABLE:
Sequence: emp-overlay
@@ -83,11 +85,13 @@
PowerMultiplier@EMPDISABLE:
RequiresCondition: empdisable
Modifier: 0
+ ExternalConditions@EMPDISABLE:
+ Conditions: empdisable
^EmpDisableMobile:
Inherits: ^EmpDisable
Mobile:
- RequiresCondition: !notmobile
+ RequiresCondition: !empdisable && !deployed && !loading
^Cloakable:
Cloak@CLOAKGENERATOR:
@@ -639,7 +643,6 @@
Mobile:
Speed: 113
TurnSpeed: 16
- Crushes: crate
SharesCell: no
TerrainSpeeds:
Clear: 90
@@ -795,7 +798,7 @@
Cargo:
Types: Infantry
UnloadVoice: Unload
- LoadingUpgrades: notmobile
+ LoadingUpgrades: loading
Health:
HP: 100
Armor:
diff --git a/mods/ts/rules/gdi-vehicles.yaml b/mods/ts/rules/gdi-vehicles.yaml
index 86c58f6dee..e57ea36dbb 100644
--- a/mods/ts/rules/gdi-vehicles.yaml
+++ b/mods/ts/rules/gdi-vehicles.yaml
@@ -28,7 +28,7 @@ APC:
MaxWeight: 5
PipCount: 5
UnloadVoice: Unload
- LoadingUpgrades: notmobile
+ LoadingUpgrades: loading
EjectOnDeath: true
UpgradeOnTerrain:
Upgrades: inwater
diff --git a/mods/ts/rules/misc.yaml b/mods/ts/rules/misc.yaml
index aa176c15f2..45d14be609 100644
--- a/mods/ts/rules/misc.yaml
+++ b/mods/ts/rules/misc.yaml
@@ -74,24 +74,24 @@ CRATE:
SelectionShares: 0
NoBaseSelectionShares: 100
Units: mcv
- GrantUpgradeCrateAction@cloak:
+ GrantExternalConditionCrateAction@cloak:
SelectionShares: 5
Effect: stealth
- Upgrades: crate-cloak
+ Condition: crate-cloak
Notification: cloak5.aud
- GrantUpgradeCrateAction@firepower:
+ GrantExternalConditionCrateAction@firepower:
SelectionShares: 5
Effect: firepower
- Upgrades: crate-firepower
+ Condition: crate-firepower
Notification: 00-i070.aud
- GrantUpgradeCrateAction@armor:
+ GrantExternalConditionCrateAction@armor:
SelectionShares: 5
Effect: armor
- Upgrades: crate-damage
+ Condition: crate-damage
Notification: 00-i068.aud
- GrantUpgradeCrateAction@speed:
+ GrantExternalConditionCrateAction@speed:
SelectionShares: 5
- Upgrades: crate-speed
+ Condition: crate-speed
Notification: 00-i080.aud
SROCK01:
diff --git a/mods/ts/rules/nod-vehicles.yaml b/mods/ts/rules/nod-vehicles.yaml
index 8837a3402e..965ede8f7f 100644
--- a/mods/ts/rules/nod-vehicles.yaml
+++ b/mods/ts/rules/nod-vehicles.yaml
@@ -106,7 +106,7 @@ TTNK:
RenderSprites:
Image: ttnk
DeployToUpgrade:
- DeployedUpgrades: deployed, notmobile
+ DeployedUpgrades: deployed
UndeployedUpgrades: undeployed
DeployAnimation: make
Facing: 160
@@ -285,7 +285,7 @@ SAPC:
MaxWeight: 5
PipCount: 5
UnloadVoice: Unload
- LoadingUpgrades: notmobile
+ LoadingUpgrades: loading
EjectOnDeath: true
SUBTANK:
diff --git a/mods/ts/rules/shared-vehicles.yaml b/mods/ts/rules/shared-vehicles.yaml
index b90046afa5..d66be951c3 100644
--- a/mods/ts/rules/shared-vehicles.yaml
+++ b/mods/ts/rules/shared-vehicles.yaml
@@ -138,7 +138,7 @@ LPST:
gdi: lpst.gdi
nod: lpst.nod
DeployToUpgrade:
- DeployedUpgrades: deployed, notmobile
+ DeployedUpgrades: deployed
UndeployedUpgrades: undeployed
DeployAnimation: make
Facing: 160
diff --git a/mods/ts/weapons/superweapons.yaml b/mods/ts/weapons/superweapons.yaml
index 7c9f6fdf9d..f85b9a7802 100644
--- a/mods/ts/weapons/superweapons.yaml
+++ b/mods/ts/weapons/superweapons.yaml
@@ -107,10 +107,10 @@ EMPulseCannon:
Image: pulsball
Warhead@1Eff: CreateEffect
Explosions: pulse_explosion
- Warhead@emp: GrantUpgrade
+ Warhead@emp: GrantExternalCondition
Range: 4c0
Duration: 250
- Upgrades: empdisable, notmobile
+ Condition: empdisable
ClusterMissile:
ValidTargets: Ground, Water, Air