Rewrite UpgradeManager implementation to suit conditions.

This commit is contained in:
Paul Chote
2016-11-19 16:18:06 +00:00
parent 05187f5828
commit 3f0b33992c
17 changed files with 219 additions and 160 deletions

View File

@@ -222,7 +222,7 @@ namespace OpenRA.Support
return new VariableToken(start, expression.Substring(start)); return new VariableToken(start, expression.Substring(start));
} }
static bool ParseSymbol(VariableToken t, Dictionary<string, bool> symbols) static bool ParseSymbol(VariableToken t, IReadOnlyDictionary<string, bool> symbols)
{ {
bool value; bool value;
symbols.TryGetValue(t.Symbol, out value); symbols.TryGetValue(t.Symbol, out value);
@@ -271,7 +271,7 @@ namespace OpenRA.Support
yield return s.Pop(); yield return s.Pop();
} }
public bool Evaluate(Dictionary<string, bool> symbols) public bool Evaluate(IReadOnlyDictionary<string, bool> symbols)
{ {
var s = new Stack<bool>(); var s = new Stack<bool>();
foreach (var t in postfix) foreach (var t in postfix)

View File

@@ -96,7 +96,7 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
public class Armament : UpgradableTrait<ArmamentInfo>, INotifyCreated, ITick, IExplodeModifier public class Armament : UpgradableTrait<ArmamentInfo>, ITick, IExplodeModifier
{ {
public readonly WeaponInfo Weapon; public readonly WeaponInfo Weapon;
public readonly Barrel[] Barrels; public readonly Barrel[] Barrels;
@@ -141,12 +141,14 @@ namespace OpenRA.Mods.Common.Traits
return new WDist(Util.ApplyPercentageModifiers(Weapon.Range.Length, rangeModifiers)); return new WDist(Util.ApplyPercentageModifiers(Weapon.Range.Length, rangeModifiers));
} }
protected virtual void Created(Actor self) protected override void Created(Actor self)
{ {
turret = self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.Name == Info.Turret); turret = self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.Name == Info.Turret);
ammoPool = self.TraitsImplementing<AmmoPool>().FirstOrDefault(la => la.Info.Name == Info.AmmoPoolName); ammoPool = self.TraitsImplementing<AmmoPool>().FirstOrDefault(la => la.Info.Name == Info.AmmoPoolName);
coords = self.Trait<BodyOrientation>(); coords = self.Trait<BodyOrientation>();
rangeModifiers = self.TraitsImplementing<IRangeModifier>().ToArray().Select(m => m.GetRangeModifier()); rangeModifiers = self.TraitsImplementing<IRangeModifier>().ToArray().Select(m => m.GetRangeModifier());
base.Created(self);
} }
protected virtual void Tick(Actor self) protected virtual void Tick(Actor self)
@@ -170,12 +172,6 @@ namespace OpenRA.Mods.Common.Traits
delayedActions.RemoveAll(a => a.First <= 0); 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) void ITick.Tick(Actor self)
{ {
// Split into a protected method to allow subclassing // Split into a protected method to allow subclassing

View File

@@ -20,7 +20,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
[Desc("This actor can transport Passenger actors.")] [Desc("This actor can transport Passenger actors.")]
public class CargoInfo : ITraitInfo, Requires<IOccupySpaceInfo>, Requires<UpgradeManagerInfo> public class CargoInfo : ITraitInfo, Requires<IOccupySpaceInfo>
{ {
[Desc("The maximum sum of Passenger.Weight that this actor can support.")] [Desc("The maximum sum of Passenger.Weight that this actor can support.")]
public readonly int MaxWeight = 0; public readonly int MaxWeight = 0;
@@ -67,7 +67,6 @@ namespace OpenRA.Mods.Common.Traits
{ {
public readonly CargoInfo Info; public readonly CargoInfo Info;
readonly Actor self; readonly Actor self;
readonly UpgradeManager upgradeManager;
readonly Stack<Actor> cargo = new Stack<Actor>(); readonly Stack<Actor> cargo = new Stack<Actor>();
readonly HashSet<Actor> reserves = new HashSet<Actor>(); readonly HashSet<Actor> reserves = new HashSet<Actor>();
readonly Lazy<IFacing> facing; readonly Lazy<IFacing> facing;
@@ -76,6 +75,7 @@ namespace OpenRA.Mods.Common.Traits
int totalWeight = 0; int totalWeight = 0;
int reservedWeight = 0; int reservedWeight = 0;
Aircraft aircraft; Aircraft aircraft;
UpgradeManager upgradeManager;
CPos currentCell; CPos currentCell;
public IEnumerable<CPos> CurrentAdjacentCells { get; private set; } public IEnumerable<CPos> CurrentAdjacentCells { get; private set; }
@@ -89,7 +89,6 @@ namespace OpenRA.Mods.Common.Traits
Info = info; Info = info;
Unloading = false; Unloading = false;
checkTerrainType = info.UnloadTerrainTypes.Count > 0; checkTerrainType = info.UnloadTerrainTypes.Count > 0;
upgradeManager = self.Trait<UpgradeManager>();
if (init.Contains<RuntimeCargoInit>()) if (init.Contains<RuntimeCargoInit>())
{ {
@@ -127,6 +126,7 @@ namespace OpenRA.Mods.Common.Traits
void INotifyCreated.Created(Actor self) void INotifyCreated.Created(Actor self)
{ {
aircraft = self.TraitOrDefault<Aircraft>(); aircraft = self.TraitOrDefault<Aircraft>();
upgradeManager = self.Trait<UpgradeManager>();
} }
static int GetWeight(Actor a) { return a.Info.TraitInfo<PassengerInfo>().Weight; } static int GetWeight(Actor a) { return a.Info.TraitInfo<PassengerInfo>().Weight; }

View File

@@ -15,7 +15,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
[Desc("Can be carried by actors with the `Carryall` trait.")] [Desc("Can be carried by actors with the `Carryall` trait.")]
public class CarryableInfo : ITraitInfo, Requires<UpgradeManagerInfo> public class CarryableInfo : ITraitInfo
{ {
[UpgradeGrantedReference] [UpgradeGrantedReference]
[Desc("The upgrades to grant to self while waiting or being carried.")] [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 virtual object Create(ActorInitializer init) { return new Carryable(init.Self, this); }
} }
public class Carryable public class Carryable : INotifyCreated
{ {
readonly CarryableInfo info; readonly CarryableInfo info;
readonly UpgradeManager upgradeManager; UpgradeManager upgradeManager;
public Actor Carrier { get; private set; } public Actor Carrier { get; private set; }
public bool Reserved { get { return state != State.Free; } } public bool Reserved { get { return state != State.Free; } }
@@ -44,6 +44,10 @@ namespace OpenRA.Mods.Common.Traits
public Carryable(Actor self, CarryableInfo info) public Carryable(Actor self, CarryableInfo info)
{ {
this.info = info; this.info = info;
}
void INotifyCreated.Created(Actor self)
{
upgradeManager = self.Trait<UpgradeManager>(); upgradeManager = self.Trait<UpgradeManager>();
} }

View File

@@ -29,10 +29,10 @@ namespace OpenRA.Mods.Common.Traits
public void AddedToWorld(Actor self) public void AddedToWorld(Actor self)
{ {
if (!IsTraitDisabled) if (!IsTraitDisabled)
UpgradeEnabled(self); TraitEnabled(self);
} }
protected override void UpgradeEnabled(Actor self) protected override void TraitEnabled(Actor self)
{ {
if (self.IsDead) if (self.IsDead)
return; return;

View File

@@ -33,8 +33,8 @@ namespace OpenRA.Mods.Common.Traits
power = self.Owner.PlayerActor.Trait<PowerManager>(); power = self.Owner.PlayerActor.Trait<PowerManager>();
} }
protected override void UpgradeEnabled(Actor self) { power.UpdateActor(self); } protected override void TraitEnabled(Actor self) { power.UpdateActor(self); }
protected override void UpgradeDisabled(Actor self) { power.UpdateActor(self); } protected override void TraitDisabled(Actor self) { power.UpdateActor(self); }
int IPowerModifier.GetPowerModifier() { return IsTraitDisabled ? 100 : Info.Modifier; } int IPowerModifier.GetPowerModifier() { return IsTraitDisabled ? 100 : Info.Modifier; }

View File

@@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Traits
power = newOwner.PlayerActor.Trait<PowerManager>(); power = newOwner.PlayerActor.Trait<PowerManager>();
} }
protected override void UpgradeDisabled(Actor self) protected override void TraitDisabled(Actor self)
{ {
if (!disabled || !Info.CancelWhenDisabled) if (!disabled || !Info.CancelWhenDisabled)
return; return;

View File

@@ -41,8 +41,8 @@ namespace OpenRA.Mods.Common.Traits
powerModifiers = Exts.Lazy(() => self.TraitsImplementing<IPowerModifier>().ToArray()); powerModifiers = Exts.Lazy(() => self.TraitsImplementing<IPowerModifier>().ToArray());
} }
protected override void UpgradeEnabled(Actor self) { PlayerPower.UpdateActor(self); } protected override void TraitEnabled(Actor self) { PlayerPower.UpdateActor(self); }
protected override void UpgradeDisabled(Actor self) { PlayerPower.UpdateActor(self); } protected override void TraitDisabled(Actor self) { PlayerPower.UpdateActor(self); }
public void AddedToWorld(Actor self) { PlayerPower.UpdateActor(self); } public void AddedToWorld(Actor self) { PlayerPower.UpdateActor(self); }
public void RemovedFromWorld(Actor self) { PlayerPower.RemoveActor(self); } public void RemovedFromWorld(Actor self) { PlayerPower.RemoveActor(self); }
public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)

View File

@@ -61,7 +61,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public override object Create(ActorInitializer init) { return new LeavesTrails(init.Self, this); } public override object Create(ActorInitializer init) { return new LeavesTrails(init.Self, this); }
} }
public class LeavesTrails : UpgradableTrait<LeavesTrailsInfo>, ITick, INotifyCreated public class LeavesTrails : UpgradableTrait<LeavesTrailsInfo>, ITick
{ {
BodyOrientation body; BodyOrientation body;
IFacing facing; IFacing facing;
@@ -75,12 +75,14 @@ namespace OpenRA.Mods.Common.Traits.Render
} }
WPos cachedPosition; WPos cachedPosition;
public void Created(Actor self) protected override void Created(Actor self)
{ {
body = self.Trait<BodyOrientation>(); body = self.Trait<BodyOrientation>();
facing = self.TraitOrDefault<IFacing>(); facing = self.TraitOrDefault<IFacing>();
cachedFacing = facing != null ? facing.Facing : 0; cachedFacing = facing != null ? facing.Facing : 0;
cachedPosition = self.CenterPosition; cachedPosition = self.CenterPosition;
base.Created(self);
} }
int ticks; 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; cachedPosition = self.CenterPosition;
} }

View File

@@ -52,7 +52,7 @@ namespace OpenRA.Mods.Common.Traits.Render
} }
} }
public class WithInfantryBody : UpgradableTrait<WithInfantryBodyInfo>, ITick, INotifyAttack, INotifyIdle, INotifyCreated public class WithInfantryBody : UpgradableTrait<WithInfantryBodyInfo>, ITick, INotifyAttack, INotifyIdle
{ {
readonly IMove move; readonly IMove move;
protected readonly Animation DefaultAnimation; 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<IRenderInfantrySequenceModifier>(); rsm = self.TraitOrDefault<IRenderInfantrySequenceModifier>();
base.Created(self);
} }
protected virtual string NormalizeInfantrySequence(Actor self, string baseSequence) protected virtual string NormalizeInfantrySequence(Actor self, string baseSequence)

View File

@@ -128,7 +128,7 @@ namespace OpenRA.Mods.Common.Traits.Render
rs.Add(anim, info.Palette, info.IsPlayerPalette); rs.Add(anim, info.Palette, info.IsPlayerPalette);
} }
protected override void UpgradeEnabled(Actor self) protected override void TraitEnabled(Actor self)
{ {
if (info.Image == null) if (info.Image == null)
return; return;
@@ -136,7 +136,7 @@ namespace OpenRA.Mods.Common.Traits.Render
anim.Animation.PlayThen(info.OpeningSequence, () => anim.Animation.PlayRepeating(info.Sequence)); 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) if (info.Image == null)
return; return;

View File

@@ -27,7 +27,7 @@ namespace OpenRA.Mods.Common.Traits
public override object Create(ActorInitializer init) { return new Targetable(init.Self, this); } public override object Create(ActorInitializer init) { return new Targetable(init.Self, this); }
} }
public class Targetable : UpgradableTrait<TargetableInfo>, ITargetable, INotifyCreated public class Targetable : UpgradableTrait<TargetableInfo>, ITargetable
{ {
protected static readonly string[] None = new string[] { }; protected static readonly string[] None = new string[] { };
protected Cloak[] cloaks; protected Cloak[] cloaks;
@@ -35,9 +35,11 @@ namespace OpenRA.Mods.Common.Traits
public Targetable(Actor self, TargetableInfo info) public Targetable(Actor self, TargetableInfo info)
: base(info) { } : base(info) { }
void INotifyCreated.Created(Actor self) protected override void Created(Actor self)
{ {
cloaks = self.TraitsImplementing<Cloak>().ToArray(); cloaks = self.TraitsImplementing<Cloak>().ToArray();
base.Created(self);
} }
public virtual bool TargetableBy(Actor self, Actor viewer) public virtual bool TargetableBy(Actor self, Actor viewer)

View File

@@ -17,9 +17,9 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
/// <summary>Use as base class for *Info to subclass of UpgradableTrait. (See UpgradableTrait.)</summary> /// <summary>Use as base class for *Info to subclass of UpgradableTrait. (See UpgradableTrait.)</summary>
public abstract class UpgradableTraitInfo : IUpgradableInfo, IRulesetLoaded public abstract class UpgradableTraitInfo : IConditionConsumerInfo, IRulesetLoaded
{ {
static readonly Dictionary<string, bool> NoConditions = new Dictionary<string, bool>(); static readonly IReadOnlyDictionary<string, bool> NoConditions = new ReadOnlyDictionary<string, bool>(new Dictionary<string, bool>());
[UpgradeUsedReference] [UpgradeUsedReference]
[Desc("Boolean expression defining the condition to enable this trait.")] [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. /// Requires basing *Info on UpgradableTraitInfo and using base(info) constructor.
/// Note that EnabledByUpgrade is not called at creation even if this starts as enabled. /// Note that EnabledByUpgrade is not called at creation even if this starts as enabled.
/// </summary> /// </summary>
public abstract class UpgradableTrait<InfoType> : IUpgradable, IDisabledTrait, ISync where InfoType : UpgradableTraitInfo public abstract class UpgradableTrait<InfoType> : IConditionConsumer, IDisabledTrait, INotifyCreated, ISync where InfoType : UpgradableTraitInfo
{ {
public readonly InfoType Info; public readonly InfoType Info;
readonly Dictionary<string, bool> conditions = new Dictionary<string, bool>();
IEnumerable<string> IUpgradable.UpgradeTypes IEnumerable<string> IConditionConsumer.Conditions
{ {
get get
{ {
@@ -65,36 +64,38 @@ namespace OpenRA.Mods.Common.Traits
{ {
Info = info; Info = info;
// TODO: Set initial state from a ConditionsInit once that exists // Conditional traits will be enabled (if appropriate) by the UpgradeManager
IsTraitDisabled = info.RequiresCondition != null ? // calling IConditionConsumer.ConditionsChanged at the end of INotifyCreated.
!info.RequiresCondition.Evaluate(conditions) : false; 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<string, bool> conditions)
{ {
if (Info.RequiresCondition == null)
return;
var wasDisabled = IsTraitDisabled; var wasDisabled = IsTraitDisabled;
conditions[type] = newLevel > 0;
IsTraitDisabled = !Info.RequiresCondition.Evaluate(conditions); IsTraitDisabled = !Info.RequiresCondition.Evaluate(conditions);
UpgradeLevelChanged(self, oldLevel, newLevel);
if (IsTraitDisabled != wasDisabled) if (IsTraitDisabled != wasDisabled)
{ {
if (wasDisabled) if (wasDisabled)
UpgradeEnabled(self); TraitEnabled(self);
else else
UpgradeDisabled(self); TraitDisabled(self);
} }
} }
// Subclasses can add upgrade support by querying IsTraitDisabled and/or overriding these methods. // Subclasses can add condition support by querying IsTraitDisabled and/or overriding these methods.
protected virtual void UpgradeLevelChanged(Actor self, int oldLevel, int newLevel) { } protected virtual void TraitEnabled(Actor self) { }
protected virtual void UpgradeEnabled(Actor self) { } protected virtual void TraitDisabled(Actor self) { }
protected virtual void UpgradeDisabled(Actor self) { }
} }
} }

View File

@@ -12,49 +12,44 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Primitives;
using OpenRA.Traits; using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
[Desc("Attach this to a unit to enable dynamic upgrades by warheads, experience, crates, support powers, etc.")] [Desc("Attach this to a unit to enable dynamic upgrades by warheads, experience, crates, support powers, etc.")]
public class UpgradeManagerInfo : TraitInfo<UpgradeManager>, IRulesetLoaded public class UpgradeManagerInfo : TraitInfo<UpgradeManager>, Requires<IConditionConsumerInfo> { }
{
public void RulesetLoaded(Ruleset rules, ActorInfo info)
{
if (!info.Name.StartsWith("^") && !info.TraitInfos<IUpgradableInfo>().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 UpgradeManager : INotifyCreated, ITick public class UpgradeManager : INotifyCreated, ITick
{ {
class TimedUpgrade /// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1;
class TimedCondition
{ {
public class UpgradeSource public class ConditionSource
{ {
public readonly object Source; public readonly object Source;
public int Remaining; public int Remaining;
public UpgradeSource(int duration, object source) public ConditionSource(int duration, object source)
{ {
Remaining = duration; Remaining = duration;
Source = source; Source = source;
} }
} }
public readonly string Upgrade; public readonly string Condition;
public readonly int Duration; public readonly int Duration;
public readonly HashSet<UpgradeSource> Sources; public readonly HashSet<ConditionSource> Sources;
public int Remaining; // Equal to maximum of all Sources.Remaining 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; Duration = duration;
Remaining = duration; Remaining = duration;
Sources = new HashSet<UpgradeSource> { new UpgradeSource(duration, source) }; Sources = new HashSet<ConditionSource> { new ConditionSource(duration, source) };
} }
public void Tick() public void Tick()
@@ -65,29 +60,124 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
class UpgradeState class ConditionState
{ {
public readonly List<IUpgradable> Traits = new List<IUpgradable>(); /// <summary>Traits that have registered to be notified when this condition changes.</summary>
public readonly List<object> Sources = new List<object>(); public readonly List<IConditionConsumer> Consumers = new List<IConditionConsumer>();
/// <summary>Unique integers identifying granted instances of the condition.</summary>
public readonly HashSet<int> Tokens = new HashSet<int>();
/// <summary>External callbacks that are to be executed when a timed condition changes.</summary>
public readonly List<Action<int, int>> Watchers = new List<Action<int, int>>(); public readonly List<Action<int, int>> Watchers = new List<Action<int, int>>();
} }
readonly List<TimedUpgrade> timedUpgrades = new List<TimedUpgrade>(); readonly List<TimedCondition> timedConditions = new List<TimedCondition>();
readonly Dictionary<IUpgradable, int> levels = new Dictionary<IUpgradable, int>();
Dictionary<string, UpgradeState> upgrades; Dictionary<string, ConditionState> state;
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
Dictionary<int, string> tokens = new Dictionary<int, string>();
int nextToken = 1;
/// <summary>Temporary shim between the old and new upgrade/condition grant and revoke methods.</summary>
Dictionary<Pair<object, string>, int> objectTokenShim = new Dictionary<Pair<object, string>, int>();
/// <summary>Cache of condition -> enabled state for quick evaluation of boolean conditions.</summary>
Dictionary<string, bool> conditionCache = new Dictionary<string, bool>();
/// <summary>Read-only version of conditionCache that is passed to IConditionConsumers.</summary>
IReadOnlyDictionary<string, bool> readOnlyConditionCache;
void INotifyCreated.Created(Actor self) void INotifyCreated.Created(Actor self)
{ {
upgrades = new Dictionary<string, UpgradeState>(); state = new Dictionary<string, ConditionState>();
foreach (var up in self.TraitsImplementing<IUpgradable>()) readOnlyConditionCache = new ReadOnlyDictionary<string, bool>(conditionCache);
foreach (var t in up.UpgradeTypes)
upgrades.GetOrAdd(t).Traits.Add(up); var allConsumers = new HashSet<IConditionConsumer>();
foreach (var consumer in self.TraitsImplementing<IConditionConsumer>())
{
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) ConditionState conditionState;
throw new InvalidOperationException("Upgrades cannot be managed until the actor has been fully created."); 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);
}
/// <summary>Grants a specified condition.</summary>
/// <returns>The token that is used to revoke this condition.</returns>
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;
}
/// <summary>Revokes a previously granted condition.</summary>
/// <returns>The invalid token ID</returns>
/// <param name="token">The token ID returned by GrantCondition.</param>
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.");
} }
/// <summary>Upgrade level increments are limited to dupesAllowed per source, i.e., if a single /// <summary>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.</summary> /// remaining upgrade duration will be replaced by the new source.</summary>
public void GrantTimedUpgrade(Actor self, string upgrade, int duration, object source = null, int dupesAllowed = 1) 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) if (timed == null)
{ {
timed = new TimedUpgrade(upgrade, duration, source); timed = new TimedCondition(upgrade, duration, source);
timedUpgrades.Add(timed); timedConditions.Add(timed);
GrantUpgrade(self, upgrade, timed); GrantUpgrade(self, upgrade, timed);
return; return;
} }
@@ -110,7 +200,7 @@ namespace OpenRA.Mods.Common.Traits
var srcs = timed.Sources.Where(s => s.Source == source); var srcs = timed.Sources.Where(s => s.Source == source);
if (srcs.Count() < dupesAllowed) if (srcs.Count() < dupesAllowed)
{ {
timed.Sources.Add(new TimedUpgrade.UpgradeSource(duration, source)); timed.Sources.Add(new TimedCondition.ConditionSource(duration, source));
if (AcceptsUpgrade(self, upgrade)) if (AcceptsUpgrade(self, upgrade))
GrantUpgrade(self, upgrade, timed); GrantUpgrade(self, upgrade, timed);
else else
@@ -122,108 +212,69 @@ namespace OpenRA.Mods.Common.Traits
timed.Remaining = Math.Max(duration, timed.Remaining); 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<IUpgradable> 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) public void GrantUpgrade(Actor self, string upgrade, object source)
{ {
CheckCanManageUpgrades(); CheckCanManageConditions();
objectTokenShim[Pair.New(source, upgrade)] = GrantCondition(self, upgrade);
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);
} }
public void RevokeUpgrade(Actor self, string upgrade, object source) public void RevokeUpgrade(Actor self, string upgrade, object source)
{ {
CheckCanManageUpgrades(); CheckCanManageConditions();
RevokeCondition(self, objectTokenShim[Pair.New(source, upgrade)]);
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);
} }
/// <summary>Returns true if the actor uses the given upgrade. Does not check the actual level of the upgrade.</summary> /// <summary>Returns true if the actor uses the given upgrade. Does not check the actual level of the upgrade.</summary>
public bool AcknowledgesUpgrade(Actor self, string upgrade) public bool AcknowledgesUpgrade(Actor self, string upgrade)
{ {
CheckCanManageUpgrades(); CheckCanManageConditions();
return upgrades.ContainsKey(upgrade); return state.ContainsKey(upgrade);
} }
/// <summary>Returns true only if the actor can accept another level of the upgrade.</summary> /// <summary>Returns true only if the actor can accept another level of the upgrade.</summary>
public bool AcceptsUpgrade(Actor self, string upgrade) public bool AcceptsUpgrade(Actor self, string upgrade)
{ {
CheckCanManageUpgrades(); CheckCanManageConditions();
bool enabled;
UpgradeState s; if (!conditionCache.TryGetValue(upgrade, out enabled))
if (!upgrades.TryGetValue(upgrade, out s))
return false; return false;
return s.Traits.Any(up => up.AcceptsUpgradeLevel(self, upgrade, GetOverallLevel(up) + 1)); return !enabled;
} }
public void RegisterWatcher(string upgrade, Action<int, int> action) public void RegisterWatcher(string upgrade, Action<int, int> action)
{ {
CheckCanManageUpgrades(); CheckCanManageConditions();
UpgradeState s; ConditionState s;
if (!upgrades.TryGetValue(upgrade, out s)) if (!state.TryGetValue(upgrade, out s))
return; return;
s.Watchers.Add(action); s.Watchers.Add(action);
} }
/// <summary>Watchers will be receiving notifications while the upgrade's level is nonzero. /// <summary>Watchers will be receiving notifications while the condition is enabled.
/// They will also be provided with the number of ticks before the level returns to zero, /// 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 /// as well as the duration in ticks of the timed upgrade (provided in the first call to
/// GrantTimedUpgrade).</summary> /// GrantTimedUpgrade).</summary>
public void Tick(Actor self) void ITick.Tick(Actor self)
{ {
CheckCanManageUpgrades(); foreach (var u in timedConditions)
foreach (var u in timedUpgrades)
{ {
u.Tick(); u.Tick();
foreach (var source in u.Sources) foreach (var source in u.Sources)
if (source.Remaining <= 0) if (source.Remaining <= 0)
RevokeUpgrade(self, u.Upgrade, u); RevokeUpgrade(self, u.Condition, u);
u.Sources.RemoveWhere(source => source.Remaining <= 0); 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); a(u.Duration, u.Remaining);
} }
timedUpgrades.RemoveAll(u => u.Remaining <= 0); timedConditions.RemoveAll(u => u.Remaining <= 0);
} }
#endregion
} }
} }

View File

@@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.Traits
public override object Create(ActorInitializer init) { return new Wanders(init.Self, this); } public override object Create(ActorInitializer init) { return new Wanders(init.Self, this); }
} }
public class Wanders : UpgradableTrait<WandersInfo>, INotifyCreated, INotifyIdle, INotifyBecomingIdle public class Wanders : UpgradableTrait<WandersInfo>, INotifyIdle, INotifyBecomingIdle
{ {
readonly Actor self; readonly Actor self;
readonly WandersInfo info; readonly WandersInfo info;
@@ -50,9 +50,11 @@ namespace OpenRA.Mods.Common.Traits
effectiveMoveRadius = info.WanderMoveRadius; effectiveMoveRadius = info.WanderMoveRadius;
} }
void INotifyCreated.Created(Actor self) protected override void Created(Actor self)
{ {
move = self.Trait<IMove>() as IResolveOrder; move = self.Trait<IMove>() as IResolveOrder;
base.Created(self);
} }
public virtual void OnBecomingIdle(Actor self) public virtual void OnBecomingIdle(Actor self)

View File

@@ -103,15 +103,14 @@ namespace OpenRA.Mods.Common.Traits
[RequireExplicitImplementation] [RequireExplicitImplementation]
public interface INotifyPassengerExited { void OnPassengerExited(Actor self, Actor passenger); } public interface INotifyPassengerExited { void OnPassengerExited(Actor self, Actor passenger); }
public interface IUpgradable [RequireExplicitImplementation]
{ public interface IConditionConsumerInfo : ITraitInfo { }
IEnumerable<string> UpgradeTypes { get; }
bool AcceptsUpgradeLevel(Actor self, string type, int level);
void UpgradeLevelChanged(Actor self, string type, int oldLevel, int newLevel);
}
// Implement to construct before UpgradeManager public interface IConditionConsumer
public interface IUpgradableInfo : ITraitInfo { } {
IEnumerable<string> Conditions { get; }
void ConditionsChanged(Actor self, IReadOnlyDictionary<string, bool> conditions);
}
public interface INotifyHarvesterAction public interface INotifyHarvesterAction
{ {

View File

@@ -21,11 +21,11 @@ namespace OpenRA.Test
[TestFixture] [TestFixture]
public class BooleanExpressionTest public class BooleanExpressionTest
{ {
Dictionary<string, bool> testValues = new Dictionary<string, bool>() IReadOnlyDictionary<string, bool> testValues = new ReadOnlyDictionary<string, bool>(new Dictionary<string, bool>()
{ {
{ "true", true }, { "true", true },
{ "false", false } { "false", false }
}; });
void AssertFalse(string expression) void AssertFalse(string expression)
{ {