diff --git a/OpenRA.Game/Support/BooleanExpression.cs b/OpenRA.Game/Support/BooleanExpression.cs index c11d35ab02..a31b9aa402 100644 --- a/OpenRA.Game/Support/BooleanExpression.cs +++ b/OpenRA.Game/Support/BooleanExpression.cs @@ -222,7 +222,7 @@ namespace OpenRA.Support return new VariableToken(start, expression.Substring(start)); } - static bool ParseSymbol(VariableToken t, Dictionary symbols) + static bool ParseSymbol(VariableToken t, IReadOnlyDictionary symbols) { bool value; symbols.TryGetValue(t.Symbol, out value); @@ -271,7 +271,7 @@ namespace OpenRA.Support yield return s.Pop(); } - public bool Evaluate(Dictionary symbols) + public bool Evaluate(IReadOnlyDictionary symbols) { var s = new Stack(); foreach (var t in postfix) diff --git a/OpenRA.Mods.Common/Traits/Armament.cs b/OpenRA.Mods.Common/Traits/Armament.cs index 53423f4e25..06f493fc13 100644 --- a/OpenRA.Mods.Common/Traits/Armament.cs +++ b/OpenRA.Mods.Common/Traits/Armament.cs @@ -96,7 +96,7 @@ namespace OpenRA.Mods.Common.Traits } } - public class Armament : UpgradableTrait, INotifyCreated, ITick, IExplodeModifier + public class Armament : UpgradableTrait, ITick, IExplodeModifier { public readonly WeaponInfo Weapon; public readonly Barrel[] Barrels; @@ -141,12 +141,14 @@ namespace OpenRA.Mods.Common.Traits return new WDist(Util.ApplyPercentageModifiers(Weapon.Range.Length, rangeModifiers)); } - protected virtual void Created(Actor self) + protected override void Created(Actor self) { turret = self.TraitsImplementing().FirstOrDefault(t => t.Name == Info.Turret); ammoPool = self.TraitsImplementing().FirstOrDefault(la => la.Info.Name == Info.AmmoPoolName); coords = self.Trait(); rangeModifiers = self.TraitsImplementing().ToArray().Select(m => m.GetRangeModifier()); + + base.Created(self); } protected virtual void Tick(Actor self) @@ -170,12 +172,6 @@ namespace OpenRA.Mods.Common.Traits delayedActions.RemoveAll(a => a.First <= 0); } - void INotifyCreated.Created(Actor self) - { - // Split into a protected method to allow subclassing - Created(self); - } - void ITick.Tick(Actor self) { // Split into a protected method to allow subclassing diff --git a/OpenRA.Mods.Common/Traits/Cargo.cs b/OpenRA.Mods.Common/Traits/Cargo.cs index 5e66f98e38..0ce4b9b853 100644 --- a/OpenRA.Mods.Common/Traits/Cargo.cs +++ b/OpenRA.Mods.Common/Traits/Cargo.cs @@ -20,7 +20,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("This actor can transport Passenger actors.")] - public class CargoInfo : ITraitInfo, Requires, Requires + public class CargoInfo : ITraitInfo, Requires { [Desc("The maximum sum of Passenger.Weight that this actor can support.")] public readonly int MaxWeight = 0; @@ -67,7 +67,6 @@ namespace OpenRA.Mods.Common.Traits { public readonly CargoInfo Info; readonly Actor self; - readonly UpgradeManager upgradeManager; readonly Stack cargo = new Stack(); readonly HashSet reserves = new HashSet(); readonly Lazy facing; @@ -76,6 +75,7 @@ namespace OpenRA.Mods.Common.Traits int totalWeight = 0; int reservedWeight = 0; Aircraft aircraft; + UpgradeManager upgradeManager; CPos currentCell; public IEnumerable CurrentAdjacentCells { get; private set; } @@ -89,7 +89,6 @@ namespace OpenRA.Mods.Common.Traits Info = info; Unloading = false; checkTerrainType = info.UnloadTerrainTypes.Count > 0; - upgradeManager = self.Trait(); if (init.Contains()) { @@ -127,6 +126,7 @@ namespace OpenRA.Mods.Common.Traits void INotifyCreated.Created(Actor self) { aircraft = self.TraitOrDefault(); + upgradeManager = self.Trait(); } static int GetWeight(Actor a) { return a.Info.TraitInfo().Weight; } diff --git a/OpenRA.Mods.Common/Traits/Carryable.cs b/OpenRA.Mods.Common/Traits/Carryable.cs index ba26914040..7674266188 100644 --- a/OpenRA.Mods.Common/Traits/Carryable.cs +++ b/OpenRA.Mods.Common/Traits/Carryable.cs @@ -15,7 +15,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Can be carried by actors with the `Carryall` trait.")] - public class CarryableInfo : ITraitInfo, Requires + public class CarryableInfo : ITraitInfo { [UpgradeGrantedReference] [Desc("The upgrades to grant to self while waiting or being carried.")] @@ -27,10 +27,10 @@ namespace OpenRA.Mods.Common.Traits public virtual object Create(ActorInitializer init) { return new Carryable(init.Self, this); } } - public class Carryable + public class Carryable : INotifyCreated { readonly CarryableInfo info; - readonly UpgradeManager upgradeManager; + UpgradeManager upgradeManager; public Actor Carrier { get; private set; } public bool Reserved { get { return state != State.Free; } } @@ -44,6 +44,10 @@ namespace OpenRA.Mods.Common.Traits public Carryable(Actor self, CarryableInfo info) { this.info = info; + } + + void INotifyCreated.Created(Actor self) + { upgradeManager = self.Trait(); } diff --git a/OpenRA.Mods.Common/Traits/KillsSelf.cs b/OpenRA.Mods.Common/Traits/KillsSelf.cs index 1411d8364f..65365028a5 100644 --- a/OpenRA.Mods.Common/Traits/KillsSelf.cs +++ b/OpenRA.Mods.Common/Traits/KillsSelf.cs @@ -29,10 +29,10 @@ namespace OpenRA.Mods.Common.Traits public void AddedToWorld(Actor self) { if (!IsTraitDisabled) - UpgradeEnabled(self); + TraitEnabled(self); } - protected override void UpgradeEnabled(Actor self) + protected override void TraitEnabled(Actor self) { if (self.IsDead) return; diff --git a/OpenRA.Mods.Common/Traits/Multipliers/PowerMultiplier.cs b/OpenRA.Mods.Common/Traits/Multipliers/PowerMultiplier.cs index dee64dba15..68d493b6bb 100644 --- a/OpenRA.Mods.Common/Traits/Multipliers/PowerMultiplier.cs +++ b/OpenRA.Mods.Common/Traits/Multipliers/PowerMultiplier.cs @@ -33,8 +33,8 @@ namespace OpenRA.Mods.Common.Traits power = self.Owner.PlayerActor.Trait(); } - protected override void UpgradeEnabled(Actor self) { power.UpdateActor(self); } - protected override void UpgradeDisabled(Actor self) { power.UpdateActor(self); } + protected override void TraitEnabled(Actor self) { power.UpdateActor(self); } + protected override void TraitDisabled(Actor self) { power.UpdateActor(self); } int IPowerModifier.GetPowerModifier() { return IsTraitDisabled ? 100 : Info.Modifier; } diff --git a/OpenRA.Mods.Common/Traits/Power/CanPowerDown.cs b/OpenRA.Mods.Common/Traits/Power/CanPowerDown.cs index 0cbb9d9ef9..6b5953a3b8 100644 --- a/OpenRA.Mods.Common/Traits/Power/CanPowerDown.cs +++ b/OpenRA.Mods.Common/Traits/Power/CanPowerDown.cs @@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Traits power = newOwner.PlayerActor.Trait(); } - protected override void UpgradeDisabled(Actor self) + protected override void TraitDisabled(Actor self) { if (!disabled || !Info.CancelWhenDisabled) return; diff --git a/OpenRA.Mods.Common/Traits/Power/Power.cs b/OpenRA.Mods.Common/Traits/Power/Power.cs index 839165a668..148d614bc1 100644 --- a/OpenRA.Mods.Common/Traits/Power/Power.cs +++ b/OpenRA.Mods.Common/Traits/Power/Power.cs @@ -41,8 +41,8 @@ namespace OpenRA.Mods.Common.Traits powerModifiers = Exts.Lazy(() => self.TraitsImplementing().ToArray()); } - protected override void UpgradeEnabled(Actor self) { PlayerPower.UpdateActor(self); } - protected override void UpgradeDisabled(Actor self) { PlayerPower.UpdateActor(self); } + protected override void TraitEnabled(Actor self) { PlayerPower.UpdateActor(self); } + protected override void TraitDisabled(Actor self) { PlayerPower.UpdateActor(self); } public void AddedToWorld(Actor self) { PlayerPower.UpdateActor(self); } public void RemovedFromWorld(Actor self) { PlayerPower.RemoveActor(self); } public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) diff --git a/OpenRA.Mods.Common/Traits/Render/LeavesTrails.cs b/OpenRA.Mods.Common/Traits/Render/LeavesTrails.cs index 824ecfb9ba..8fc813c2de 100644 --- a/OpenRA.Mods.Common/Traits/Render/LeavesTrails.cs +++ b/OpenRA.Mods.Common/Traits/Render/LeavesTrails.cs @@ -61,7 +61,7 @@ namespace OpenRA.Mods.Common.Traits.Render public override object Create(ActorInitializer init) { return new LeavesTrails(init.Self, this); } } - public class LeavesTrails : UpgradableTrait, ITick, INotifyCreated + public class LeavesTrails : UpgradableTrait, ITick { BodyOrientation body; IFacing facing; @@ -75,12 +75,14 @@ namespace OpenRA.Mods.Common.Traits.Render } WPos cachedPosition; - public void Created(Actor self) + protected override void Created(Actor self) { body = self.Trait(); facing = self.TraitOrDefault(); cachedFacing = facing != null ? facing.Facing : 0; cachedPosition = self.CenterPosition; + + base.Created(self); } int ticks; @@ -132,7 +134,7 @@ namespace OpenRA.Mods.Common.Traits.Render } } - protected override void UpgradeEnabled(Actor self) + protected override void TraitEnabled(Actor self) { cachedPosition = self.CenterPosition; } diff --git a/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs b/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs index f862814e4d..ad1eaa0195 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs @@ -52,7 +52,7 @@ namespace OpenRA.Mods.Common.Traits.Render } } - public class WithInfantryBody : UpgradableTrait, ITick, INotifyAttack, INotifyIdle, INotifyCreated + public class WithInfantryBody : UpgradableTrait, ITick, INotifyAttack, INotifyIdle { readonly IMove move; protected readonly Animation DefaultAnimation; @@ -90,9 +90,11 @@ namespace OpenRA.Mods.Common.Traits.Render } } - public void Created(Actor self) + protected override void Created(Actor self) { rsm = self.TraitOrDefault(); + + base.Created(self); } protected virtual string NormalizeInfantrySequence(Actor self, string baseSequence) diff --git a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs index 08e8095ddd..aa3692950b 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs @@ -128,7 +128,7 @@ namespace OpenRA.Mods.Common.Traits.Render rs.Add(anim, info.Palette, info.IsPlayerPalette); } - protected override void UpgradeEnabled(Actor self) + protected override void TraitEnabled(Actor self) { if (info.Image == null) return; @@ -136,7 +136,7 @@ namespace OpenRA.Mods.Common.Traits.Render anim.Animation.PlayThen(info.OpeningSequence, () => anim.Animation.PlayRepeating(info.Sequence)); } - protected override void UpgradeDisabled(Actor self) + protected override void TraitDisabled(Actor self) { if (info.Image == null) return; diff --git a/OpenRA.Mods.Common/Traits/Targetable.cs b/OpenRA.Mods.Common/Traits/Targetable.cs index a955e23ba0..11a32908ee 100644 --- a/OpenRA.Mods.Common/Traits/Targetable.cs +++ b/OpenRA.Mods.Common/Traits/Targetable.cs @@ -27,7 +27,7 @@ namespace OpenRA.Mods.Common.Traits public override object Create(ActorInitializer init) { return new Targetable(init.Self, this); } } - public class Targetable : UpgradableTrait, ITargetable, INotifyCreated + public class Targetable : UpgradableTrait, ITargetable { protected static readonly string[] None = new string[] { }; protected Cloak[] cloaks; @@ -35,9 +35,11 @@ namespace OpenRA.Mods.Common.Traits public Targetable(Actor self, TargetableInfo info) : base(info) { } - void INotifyCreated.Created(Actor self) + protected override void Created(Actor self) { cloaks = self.TraitsImplementing().ToArray(); + + base.Created(self); } public virtual bool TargetableBy(Actor self, Actor viewer) diff --git a/OpenRA.Mods.Common/Traits/Upgrades/UpgradableTrait.cs b/OpenRA.Mods.Common/Traits/Upgrades/UpgradableTrait.cs index 843d3d98ef..527679875f 100644 --- a/OpenRA.Mods.Common/Traits/Upgrades/UpgradableTrait.cs +++ b/OpenRA.Mods.Common/Traits/Upgrades/UpgradableTrait.cs @@ -17,9 +17,9 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { /// Use as base class for *Info to subclass of UpgradableTrait. (See UpgradableTrait.) - public abstract class UpgradableTraitInfo : IUpgradableInfo, IRulesetLoaded + public abstract class UpgradableTraitInfo : IConditionConsumerInfo, IRulesetLoaded { - static readonly Dictionary NoConditions = new Dictionary(); + static readonly IReadOnlyDictionary NoConditions = new ReadOnlyDictionary(new Dictionary()); [UpgradeUsedReference] [Desc("Boolean expression defining the condition to enable this trait.")] @@ -43,12 +43,11 @@ namespace OpenRA.Mods.Common.Traits /// Requires basing *Info on UpgradableTraitInfo and using base(info) constructor. /// Note that EnabledByUpgrade is not called at creation even if this starts as enabled. /// - public abstract class UpgradableTrait : IUpgradable, IDisabledTrait, ISync where InfoType : UpgradableTraitInfo + public abstract class UpgradableTrait : IConditionConsumer, IDisabledTrait, INotifyCreated, ISync where InfoType : UpgradableTraitInfo { public readonly InfoType Info; - readonly Dictionary conditions = new Dictionary(); - IEnumerable IUpgradable.UpgradeTypes + IEnumerable IConditionConsumer.Conditions { get { @@ -65,36 +64,38 @@ namespace OpenRA.Mods.Common.Traits { Info = info; - // TODO: Set initial state from a ConditionsInit once that exists - IsTraitDisabled = info.RequiresCondition != null ? - !info.RequiresCondition.Evaluate(conditions) : false; + // Conditional traits will be enabled (if appropriate) by the UpgradeManager + // calling IConditionConsumer.ConditionsChanged at the end of INotifyCreated. + IsTraitDisabled = Info.RequiresCondition != null; } - bool IUpgradable.AcceptsUpgradeLevel(Actor self, string type, int level) + protected virtual void Created(Actor self) { - return level == 1; + if (Info.RequiresCondition == null) + TraitEnabled(self); } - void IUpgradable.UpgradeLevelChanged(Actor self, string type, int oldLevel, int newLevel) + void INotifyCreated.Created(Actor self) { Created(self); } + + void IConditionConsumer.ConditionsChanged(Actor self, IReadOnlyDictionary conditions) { + if (Info.RequiresCondition == null) + return; + var wasDisabled = IsTraitDisabled; - conditions[type] = newLevel > 0; IsTraitDisabled = !Info.RequiresCondition.Evaluate(conditions); - UpgradeLevelChanged(self, oldLevel, newLevel); - if (IsTraitDisabled != wasDisabled) { if (wasDisabled) - UpgradeEnabled(self); + TraitEnabled(self); else - UpgradeDisabled(self); + TraitDisabled(self); } } - // Subclasses can add upgrade support by querying IsTraitDisabled and/or overriding these methods. - protected virtual void UpgradeLevelChanged(Actor self, int oldLevel, int newLevel) { } - protected virtual void UpgradeEnabled(Actor self) { } - protected virtual void UpgradeDisabled(Actor self) { } + // Subclasses can add condition support by querying IsTraitDisabled and/or overriding these methods. + protected virtual void TraitEnabled(Actor self) { } + protected virtual void TraitDisabled(Actor self) { } } } diff --git a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs index 81d2e0e4ad..d7a07ef96e 100644 --- a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs +++ b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs @@ -12,49 +12,44 @@ using System; using System.Collections.Generic; using System.Linq; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Attach this to a unit to enable dynamic upgrades by warheads, experience, crates, support powers, etc.")] - public class UpgradeManagerInfo : TraitInfo, IRulesetLoaded - { - public void RulesetLoaded(Ruleset rules, ActorInfo info) - { - if (!info.Name.StartsWith("^") && !info.TraitInfos().Any()) - throw new YamlException( - "There are no upgrades to be managed for actor '{0}'. You are either missing some upgradeable traits, or this UpgradeManager trait is not required.".F( - info.Name)); - } - } + public class UpgradeManagerInfo : TraitInfo, Requires { } public class UpgradeManager : INotifyCreated, ITick { - class TimedUpgrade + /// Value used to represent an invalid token. + public static readonly int InvalidConditionToken = -1; + + class TimedCondition { - public class UpgradeSource + public class ConditionSource { public readonly object Source; public int Remaining; - public UpgradeSource(int duration, object source) + public ConditionSource(int duration, object source) { Remaining = duration; Source = source; } } - public readonly string Upgrade; + public readonly string Condition; public readonly int Duration; - public readonly HashSet Sources; + public readonly HashSet Sources; public int Remaining; // Equal to maximum of all Sources.Remaining - public TimedUpgrade(string upgrade, int duration, object source) + public TimedCondition(string condition, int duration, object source) { - Upgrade = upgrade; + Condition = condition; Duration = duration; Remaining = duration; - Sources = new HashSet { new UpgradeSource(duration, source) }; + Sources = new HashSet { new ConditionSource(duration, source) }; } public void Tick() @@ -65,29 +60,124 @@ namespace OpenRA.Mods.Common.Traits } } - class UpgradeState + class ConditionState { - public readonly List Traits = new List(); - public readonly List Sources = new List(); + /// Traits that have registered to be notified when this condition changes. + public readonly List Consumers = new List(); + + /// Unique integers identifying granted instances of the condition. + public readonly HashSet Tokens = new HashSet(); + + /// External callbacks that are to be executed when a timed condition changes. public readonly List> Watchers = new List>(); } - readonly List timedUpgrades = new List(); - readonly Dictionary levels = new Dictionary(); - Dictionary upgrades; + readonly List timedConditions = new List(); + + Dictionary state; + + /// Each granted condition receives a unique token that is used when revoking. + Dictionary tokens = new Dictionary(); + + int nextToken = 1; + + /// Temporary shim between the old and new upgrade/condition grant and revoke methods. + Dictionary, int> objectTokenShim = new Dictionary, int>(); + + /// Cache of condition -> enabled state for quick evaluation of boolean conditions. + Dictionary conditionCache = new Dictionary(); + + /// Read-only version of conditionCache that is passed to IConditionConsumers. + IReadOnlyDictionary readOnlyConditionCache; void INotifyCreated.Created(Actor self) { - upgrades = new Dictionary(); - foreach (var up in self.TraitsImplementing()) - foreach (var t in up.UpgradeTypes) - upgrades.GetOrAdd(t).Traits.Add(up); + state = new Dictionary(); + readOnlyConditionCache = new ReadOnlyDictionary(conditionCache); + + var allConsumers = new HashSet(); + foreach (var consumer in self.TraitsImplementing()) + { + allConsumers.Add(consumer); + foreach (var condition in consumer.Conditions) + { + state.GetOrAdd(condition).Consumers.Add(consumer); + conditionCache[condition] = false; + } + } + + // Enable any conditions granted during trait setup + foreach (var kv in tokens) + { + ConditionState conditionState; + if (!state.TryGetValue(kv.Value, out conditionState)) + continue; + + conditionState.Tokens.Add(kv.Key); + conditionCache[kv.Value] = conditionState.Tokens.Count > 0; + } + + // Update all traits with their initial condition state + foreach (var consumer in allConsumers) + consumer.ConditionsChanged(self, readOnlyConditionCache); } - void CheckCanManageUpgrades() + void UpdateConditionState(Actor self, string condition, int token, bool isRevoke) { - if (upgrades == null) - throw new InvalidOperationException("Upgrades cannot be managed until the actor has been fully created."); + ConditionState conditionState; + if (!state.TryGetValue(condition, out conditionState)) + return; + + if (isRevoke) + conditionState.Tokens.Remove(token); + else + conditionState.Tokens.Add(token); + + conditionCache[condition] = conditionState.Tokens.Count > 0; + + foreach (var t in conditionState.Consumers) + t.ConditionsChanged(self, readOnlyConditionCache); + } + + /// Grants a specified condition. + /// The token that is used to revoke this condition. + public int GrantCondition(Actor self, string condition) + { + var token = nextToken++; + tokens.Add(token, condition); + + // Conditions may be granted before the state is initialized. + // These conditions will be processed in INotifyCreated.Created. + if (state != null) + UpdateConditionState(self, condition, token, false); + + return token; + } + + /// Revokes a previously granted condition. + /// The invalid token ID + /// The token ID returned by GrantCondition. + public int RevokeCondition(Actor self, int token) + { + string condition; + if (!tokens.TryGetValue(token, out condition)) + throw new InvalidOperationException("Attempting to revoke condition with invalid token {0} for {1}.".F(token, self)); + + tokens.Remove(token); + + // Conditions may be granted and revoked before the state is initialized. + if (state != null) + UpdateConditionState(self, condition, token, true); + + return InvalidConditionToken; + } + + #region Shim methods for legacy upgrade granting code + + void CheckCanManageConditions() + { + if (state == null) + 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 @@ -98,11 +188,11 @@ namespace OpenRA.Mods.Common.Traits /// 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 = timedUpgrades.FirstOrDefault(u => u.Upgrade == upgrade); + var timed = timedConditions.FirstOrDefault(u => u.Condition == upgrade); if (timed == null) { - timed = new TimedUpgrade(upgrade, duration, source); - timedUpgrades.Add(timed); + timed = new TimedCondition(upgrade, duration, source); + timedConditions.Add(timed); GrantUpgrade(self, upgrade, timed); return; } @@ -110,7 +200,7 @@ namespace OpenRA.Mods.Common.Traits var srcs = timed.Sources.Where(s => s.Source == source); if (srcs.Count() < dupesAllowed) { - timed.Sources.Add(new TimedUpgrade.UpgradeSource(duration, source)); + timed.Sources.Add(new TimedCondition.ConditionSource(duration, source)); if (AcceptsUpgrade(self, upgrade)) GrantUpgrade(self, upgrade, timed); else @@ -122,108 +212,69 @@ namespace OpenRA.Mods.Common.Traits timed.Remaining = Math.Max(duration, timed.Remaining); } - // Different upgradeable traits may define (a) different level ranges for the same upgrade type, - // and (b) multiple upgrade types for the same trait. The unrestricted level for each trait is - // tracked independently so that we can correctly revoke levels without adding the burden of - // tracking both the overall (unclamped) and effective (clamped) levels on each individual trait. - void NotifyUpgradeLevelChanged(IEnumerable traits, Actor self, string upgrade, int levelAdjust) - { - foreach (var up in traits) - { - var oldLevel = levels.GetOrAdd(up); - var newLevel = levels[up] = oldLevel + levelAdjust; - - // This will internally clamp the levels to its own restricted range - up.UpgradeLevelChanged(self, upgrade, oldLevel, newLevel); - } - } - - int GetOverallLevel(IUpgradable upgradable) - { - int level; - return levels.TryGetValue(upgradable, out level) ? level : 0; - } - public void GrantUpgrade(Actor self, string upgrade, object source) { - CheckCanManageUpgrades(); - - UpgradeState s; - if (!upgrades.TryGetValue(upgrade, out s)) - return; - - // Track the upgrade source so that the upgrade can be removed without conflicts - s.Sources.Add(source); - - NotifyUpgradeLevelChanged(s.Traits, self, upgrade, 1); + CheckCanManageConditions(); + objectTokenShim[Pair.New(source, upgrade)] = GrantCondition(self, upgrade); } public void RevokeUpgrade(Actor self, string upgrade, object source) { - CheckCanManageUpgrades(); - - UpgradeState s; - if (!upgrades.TryGetValue(upgrade, out s)) - return; - - if (!s.Sources.Remove(source)) - throw new InvalidOperationException("Object <{0}> revoked more levels of upgrade {1} than it granted for {2}.".F(source, upgrade, self)); - - NotifyUpgradeLevelChanged(s.Traits, self, upgrade, -1); + CheckCanManageConditions(); + RevokeCondition(self, objectTokenShim[Pair.New(source, upgrade)]); } /// Returns true if the actor uses the given upgrade. Does not check the actual level of the upgrade. public bool AcknowledgesUpgrade(Actor self, string upgrade) { - CheckCanManageUpgrades(); - return upgrades.ContainsKey(upgrade); + CheckCanManageConditions(); + return state.ContainsKey(upgrade); } /// Returns true only if the actor can accept another level of the upgrade. public bool AcceptsUpgrade(Actor self, string upgrade) { - CheckCanManageUpgrades(); - - UpgradeState s; - if (!upgrades.TryGetValue(upgrade, out s)) + CheckCanManageConditions(); + bool enabled; + if (!conditionCache.TryGetValue(upgrade, out enabled)) return false; - return s.Traits.Any(up => up.AcceptsUpgradeLevel(self, upgrade, GetOverallLevel(up) + 1)); + return !enabled; } public void RegisterWatcher(string upgrade, Action action) { - CheckCanManageUpgrades(); + CheckCanManageConditions(); - UpgradeState s; - if (!upgrades.TryGetValue(upgrade, out s)) + ConditionState s; + if (!state.TryGetValue(upgrade, out s)) return; s.Watchers.Add(action); } - /// Watchers will be receiving notifications while the upgrade's level is nonzero. - /// They will also be provided with the number of ticks before the level returns to zero, + /// 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). - public void Tick(Actor self) + void ITick.Tick(Actor self) { - CheckCanManageUpgrades(); - - foreach (var u in timedUpgrades) + foreach (var u in timedConditions) { u.Tick(); foreach (var source in u.Sources) if (source.Remaining <= 0) - RevokeUpgrade(self, u.Upgrade, u); + RevokeUpgrade(self, u.Condition, u); u.Sources.RemoveWhere(source => source.Remaining <= 0); - foreach (var a in upgrades[u.Upgrade].Watchers) + foreach (var a in state[u.Condition].Watchers) a(u.Duration, u.Remaining); } - timedUpgrades.RemoveAll(u => u.Remaining <= 0); + timedConditions.RemoveAll(u => u.Remaining <= 0); } + + #endregion } } diff --git a/OpenRA.Mods.Common/Traits/Wanders.cs b/OpenRA.Mods.Common/Traits/Wanders.cs index 02a963cc91..3c0f5983d5 100644 --- a/OpenRA.Mods.Common/Traits/Wanders.cs +++ b/OpenRA.Mods.Common/Traits/Wanders.cs @@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.Traits public override object Create(ActorInitializer init) { return new Wanders(init.Self, this); } } - public class Wanders : UpgradableTrait, INotifyCreated, INotifyIdle, INotifyBecomingIdle + public class Wanders : UpgradableTrait, INotifyIdle, INotifyBecomingIdle { readonly Actor self; readonly WandersInfo info; @@ -50,9 +50,11 @@ namespace OpenRA.Mods.Common.Traits effectiveMoveRadius = info.WanderMoveRadius; } - void INotifyCreated.Created(Actor self) + protected override void Created(Actor self) { move = self.Trait() as IResolveOrder; + + base.Created(self); } public virtual void OnBecomingIdle(Actor self) diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 38044bd452..405ba38190 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -103,15 +103,14 @@ namespace OpenRA.Mods.Common.Traits [RequireExplicitImplementation] public interface INotifyPassengerExited { void OnPassengerExited(Actor self, Actor passenger); } - public interface IUpgradable - { - IEnumerable UpgradeTypes { get; } - bool AcceptsUpgradeLevel(Actor self, string type, int level); - void UpgradeLevelChanged(Actor self, string type, int oldLevel, int newLevel); - } + [RequireExplicitImplementation] + public interface IConditionConsumerInfo : ITraitInfo { } - // Implement to construct before UpgradeManager - public interface IUpgradableInfo : ITraitInfo { } + public interface IConditionConsumer + { + IEnumerable Conditions { get; } + void ConditionsChanged(Actor self, IReadOnlyDictionary conditions); + } public interface INotifyHarvesterAction { diff --git a/OpenRA.Test/OpenRA.Game/BooleanExpressionTest.cs b/OpenRA.Test/OpenRA.Game/BooleanExpressionTest.cs index 1b40536ff9..214778f623 100644 --- a/OpenRA.Test/OpenRA.Game/BooleanExpressionTest.cs +++ b/OpenRA.Test/OpenRA.Game/BooleanExpressionTest.cs @@ -21,11 +21,11 @@ namespace OpenRA.Test [TestFixture] public class BooleanExpressionTest { - Dictionary testValues = new Dictionary() + IReadOnlyDictionary testValues = new ReadOnlyDictionary(new Dictionary() { { "true", true }, { "false", false } - }; + }); void AssertFalse(string expression) {