Merge pull request #12381 from pchote/upgrade-conditions

Upgrades overhaul part 1: EnabledCondition
This commit is contained in:
reaperrr
2016-11-30 19:42:34 +01:00
committed by GitHub
79 changed files with 947 additions and 971 deletions

View File

@@ -13,6 +13,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Support;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.AI
@@ -193,7 +194,7 @@ namespace OpenRA.Mods.Common.AI
bool HasSufficientPowerForActor(ActorInfo actorInfo)
{
return (actorInfo.TraitInfos<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1)
return (actorInfo.TraitInfos<PowerInfo>().Where(i => i.EnabledByDefault)
.Sum(p => p.Amount) + playerPower.ExcessPower) >= ai.Info.MinimumExcessPower;
}
@@ -203,12 +204,12 @@ namespace OpenRA.Mods.Common.AI
// This gets used quite a bit, so let's cache it here
var power = GetProducibleBuilding(ai.Info.BuildingCommonNames.Power, buildableThings,
a => a.TraitInfos<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount));
a => a.TraitInfos<PowerInfo>().Where(i => i.EnabledByDefault).Sum(p => p.Amount));
// First priority is to get out of a low power situation
if (playerPower.ExcessPower < ai.Info.MinimumExcessPower)
{
if (power != null && power.TraitInfos<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(p => p.Amount) > 0)
if (power != null && power.TraitInfos<PowerInfo>().Where(i => i.EnabledByDefault).Sum(p => p.Amount) > 0)
{
HackyAI.BotDebug("AI: {0} decided to build {1}: Priority override (low power)", queue.Actor.Owner, power.Name);
return power;
@@ -314,7 +315,7 @@ namespace OpenRA.Mods.Common.AI
if (playerPower.ExcessPower < ai.Info.MinimumExcessPower || !HasSufficientPowerForActor(actor))
{
// Try building a power plant instead
if (power != null && power.TraitInfos<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(pi => pi.Amount) > 0)
if (power != null && power.TraitInfos<PowerInfo>().Where(i => i.EnabledByDefault).Sum(pi => pi.Amount) > 0)
{
if (playerPower.PowerOutageRemainingTicks > 0)
HackyAI.BotDebug("{0} decided to build {1}: Priority override (is low power)", queue.Actor.Owner, power.Name);

View File

@@ -1,166 +0,0 @@
#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 System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Lint
{
public class CheckUpgrades : ILintRulesPass
{
public void Run(Action<string> emitError, Action<string> emitWarning, Ruleset rules)
{
CheckUpgradesValidity(emitError, rules);
CheckUpgradesUsage(emitError, emitWarning, rules);
}
static void CheckUpgradesValidity(Action<string> emitError, Ruleset rules)
{
var upgradesGranted = GetAllGrantedUpgrades(emitError, rules).ToHashSet();
foreach (var actorInfo in rules.Actors)
{
if (actorInfo.Key.StartsWith("^"))
continue;
foreach (var trait in actorInfo.Value.TraitInfos<ITraitInfo>())
{
var fields = trait.GetType().GetFields();
foreach (var field in fields.Where(x => x.HasAttribute<UpgradeUsedReferenceAttribute>()))
{
var values = LintExts.GetFieldValues(trait, field, emitError);
foreach (var value in values)
{
if (!upgradesGranted.Contains(value))
emitError("Actor type `{0}` uses upgrade `{1}` that is not granted by anything!".F(actorInfo.Key, value));
if (actorInfo.Value.TraitInfoOrDefault<UpgradeManagerInfo>() == null)
emitError("Actor type `{0}` uses upgrade `{1}`, but doesn't have the UpgradeManager trait.".F(actorInfo.Key, value));
}
}
}
}
}
static void CheckUpgradesUsage(Action<string> emitError, Action<string> emitWarning, Ruleset rules)
{
var upgradesUsed = GetAllUsedUpgrades(emitError, rules).ToHashSet();
// Check all upgrades granted by traits.
foreach (var actorInfo in rules.Actors)
{
if (actorInfo.Key.StartsWith("^"))
continue;
foreach (var trait in actorInfo.Value.TraitInfos<ITraitInfo>())
{
var fields = trait.GetType().GetFields();
foreach (var field in fields.Where(x => x.HasAttribute<UpgradeGrantedReferenceAttribute>()))
{
var values = LintExts.GetFieldValues(trait, field, emitError);
foreach (var value in values.Where(x => !upgradesUsed.Contains(x)))
emitWarning("Actor type `{0}` grants upgrade `{1}` that is not used by anything!".F(actorInfo.Key, value));
}
}
}
// Check all upgrades granted by warheads.
foreach (var weapon in rules.Weapons)
{
foreach (var warhead in weapon.Value.Warheads)
{
var fields = warhead.GetType().GetFields();
foreach (var field in fields.Where(x => x.HasAttribute<UpgradeGrantedReferenceAttribute>()))
{
var values = LintExts.GetFieldValues(warhead, field, emitError);
foreach (var value in values.Where(x => !upgradesUsed.Contains(x)))
emitWarning("Weapon type `{0}` grants upgrade `{1}` that is not used by anything!".F(weapon.Key, value));
}
}
}
}
static IEnumerable<string> GetAllGrantedUpgrades(Action<string> emitError, Ruleset rules)
{
// Get all upgrades granted by traits.
foreach (var actorInfo in rules.Actors)
{
foreach (var trait in actorInfo.Value.TraitInfos<ITraitInfo>())
{
var fields = trait.GetType().GetFields();
foreach (var field in fields.Where(x => x.HasAttribute<UpgradeGrantedReferenceAttribute>()))
{
var values = LintExts.GetFieldValues(trait, field, emitError);
foreach (var value in values)
yield return value;
}
}
}
// Get all upgrades granted by warheads.
foreach (var weapon in rules.Weapons)
{
foreach (var warhead in weapon.Value.Warheads)
{
var fields = warhead.GetType().GetFields();
foreach (var field in fields.Where(x => x.HasAttribute<UpgradeGrantedReferenceAttribute>()))
{
var values = LintExts.GetFieldValues(warhead, field, emitError);
foreach (var value in values)
yield return value;
}
}
}
// TODO: HACK because GainsExperience grants upgrades differently to most other sources.
var gainsExperience = rules.Actors.SelectMany(x => x.Value.TraitInfos<GainsExperienceInfo>()
.SelectMany(y => y.Upgrades.SelectMany(z => z.Value)));
foreach (var upgrade in gainsExperience)
yield return upgrade;
// TODO: HACK because Pluggable grants upgrades differently to most other sources.
var pluggable = rules.Actors.SelectMany(x => x.Value.TraitInfos<PluggableInfo>()
.SelectMany(y => y.Upgrades.SelectMany(z => z.Value)));
foreach (var upgrade in pluggable)
yield return upgrade;
}
static IEnumerable<string> GetAllUsedUpgrades(Action<string> emitError, Ruleset rules)
{
foreach (var actorInfo in rules.Actors)
{
foreach (var trait in actorInfo.Value.TraitInfos<ITraitInfo>())
{
var fields = trait.GetType().GetFields();
foreach (var field in fields.Where(x => x.HasAttribute<UpgradeUsedReferenceAttribute>()))
{
var values = LintExts.GetFieldValues(trait, field, emitError);
foreach (var value in values)
yield return value;
}
}
}
// TODO: HACK because GainsExperience and GainsStatUpgrades do not play by the rules...
// We assume everything GainsExperience grants is used by GainsStatUpgrade
var gainsExperience = rules.Actors.SelectMany(x => x.Value.TraitInfos<GainsExperienceInfo>()
.SelectMany(y => y.Upgrades.SelectMany(z => z.Value)));
foreach (var upgrade in gainsExperience)
yield return upgrade;
}
}
}

View File

@@ -11,7 +11,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using OpenRA.Support;
namespace OpenRA.Mods.Common.Lint
{
@@ -26,8 +28,13 @@ namespace OpenRA.Mods.Common.Lint
return (string[])fieldInfo.GetValue(ruleInfo);
if (type == typeof(HashSet<string>))
return (HashSet<string>)fieldInfo.GetValue(ruleInfo);
if (type == typeof(BooleanExpression))
{
var expr = (BooleanExpression)fieldInfo.GetValue(ruleInfo);
return expr != null ? expr.Variables : Enumerable.Empty<string>();
}
emitError("Bad type for reference on {0}.{1}. Supported types: string, string[], HashSet<string>"
emitError("Bad type for reference on {0}.{1}. Supported types: string, string[], HashSet<string>, BooleanExpression"
.F(ruleInfo.GetType().Name, fieldInfo.Name));
return new string[] { };

View File

@@ -181,7 +181,6 @@
<Compile Include="Lint\CheckDeathTypes.cs" />
<Compile Include="Lint\CheckRangeLimit.cs" />
<Compile Include="Lint\CheckVoiceReferences.cs" />
<Compile Include="Lint\CheckUpgrades.cs" />
<Compile Include="Lint\CheckTargetHealthRadius.cs" />
<Compile Include="Lint\LintBuildablePrerequisites.cs" />
<Compile Include="Lint\LintExts.cs" />
@@ -357,7 +356,6 @@
<Compile Include="Traits\Parachutable.cs" />
<Compile Include="Traits\ParaDrop.cs" />
<Compile Include="Traits\Passenger.cs" />
<Compile Include="Traits\Multipliers\UpgradableMultiplierTrait.cs" />
<Compile Include="Traits\Multipliers\DamageMultiplier.cs" />
<Compile Include="Traits\Multipliers\FirepowerMultiplier.cs" />
<Compile Include="Traits\Multipliers\InaccuracyMultiplier.cs" />
@@ -443,7 +441,6 @@
<Compile Include="Traits\Render\WithMuzzleOverlay.cs" />
<Compile Include="Traits\Render\WithParachute.cs" />
<Compile Include="Traits\Render\WithRangeCircle.cs" />
<Compile Include="Traits\Render\WithRankDecoration.cs" />
<Compile Include="Traits\Render\WithRearmAnimation.cs" />
<Compile Include="Traits\Render\WithRepairAnimation.cs" />
<Compile Include="Traits\Render\WithRepairOverlay.cs" />

View File

@@ -25,7 +25,7 @@ namespace OpenRA.Mods.Common.Traits
}
[Desc("Allows you to attach weapons to the unit (use @IdentifierSuffix for > 1)")]
public class ArmamentInfo : UpgradableTraitInfo, IRulesetLoaded, Requires<AttackBaseInfo>
public class ArmamentInfo : UpgradableTraitInfo, Requires<AttackBaseInfo>
{
public readonly string Name = "primary";
@@ -79,7 +79,7 @@ namespace OpenRA.Mods.Common.Traits
public override object Create(ActorInitializer init) { return new Armament(init.Self, this); }
public void RulesetLoaded(Ruleset rules, ActorInfo ai)
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
WeaponInfo weaponInfo;
@@ -91,10 +91,12 @@ namespace OpenRA.Mods.Common.Traits
ModifiedRange = new WDist(Util.ApplyPercentageModifiers(
WeaponInfo.Range.Length,
ai.TraitInfos<IRangeModifierInfo>().Select(m => m.GetRangeModifierDefault())));
base.RulesetLoaded(rules, ai);
}
}
public class Armament : UpgradableTrait<ArmamentInfo>, INotifyCreated, ITick, IExplodeModifier
public class Armament : UpgradableTrait<ArmamentInfo>, ITick, IExplodeModifier
{
public readonly WeaponInfo Weapon;
public readonly Barrel[] Barrels;
@@ -139,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<Turreted>().FirstOrDefault(t => t.Name == Info.Turret);
ammoPool = self.TraitsImplementing<AmmoPool>().FirstOrDefault(la => la.Info.Name == Info.AmmoPoolName);
coords = self.Trait<BodyOrientation>();
rangeModifiers = self.TraitsImplementing<IRangeModifier>().ToArray().Select(m => m.GetRangeModifier());
base.Created(self);
}
protected virtual void Tick(Actor self)
@@ -168,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

View File

@@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.Traits
[PaletteReference] public readonly string MuzzlePalette = "effect";
public override object Create(ActorInitializer init) { return new AttackGarrisoned(init.Self, this); }
public void RulesetLoaded(Ruleset rules, ActorInfo ai)
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
if (PortOffsets.Length == 0)
throw new YamlException("PortOffsets must have at least one entry.");
@@ -67,6 +67,8 @@ namespace OpenRA.Mods.Common.Traits
Cone = PortCones[i],
};
}
base.RulesetLoaded(rules, ai);
}
}

View File

@@ -20,7 +20,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[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.")]
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<Actor> cargo = new Stack<Actor>();
readonly HashSet<Actor> reserves = new HashSet<Actor>();
readonly Lazy<IFacing> facing;
@@ -76,6 +75,7 @@ namespace OpenRA.Mods.Common.Traits
int totalWeight = 0;
int reservedWeight = 0;
Aircraft aircraft;
UpgradeManager upgradeManager;
CPos currentCell;
public IEnumerable<CPos> 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<UpgradeManager>();
if (init.Contains<RuntimeCargoInit>())
{
@@ -127,6 +126,7 @@ namespace OpenRA.Mods.Common.Traits
void INotifyCreated.Created(Actor self)
{
aircraft = self.TraitOrDefault<Aircraft>();
upgradeManager = self.Trait<UpgradeManager>();
}
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
{
[Desc("Can be carried by actors with the `Carryall` trait.")]
public class CarryableInfo : ITraitInfo, Requires<UpgradeManagerInfo>
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<UpgradeManager>();
}

View File

@@ -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;

View File

@@ -13,19 +13,25 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Damage taken by this actor is multiplied based on upgrade level.",
"Decrease to increase actor's apparent strength.",
[Desc("Modifies the damage applied to this actor.",
"Use 0 to make actor invulnerable.")]
public class DamageMultiplierInfo : UpgradeMultiplierTraitInfo
public class DamageMultiplierInfo : UpgradableTraitInfo
{
public override object Create(ActorInitializer init) { return new DamageMultiplier(this, init.Self.Info.Name); }
[FieldLoader.Require]
[Desc("Percentage modifier to apply.")]
public readonly int Modifier = 100;
public override object Create(ActorInitializer init) { return new DamageMultiplier(this); }
}
public class DamageMultiplier : UpgradeMultiplierTrait, IDamageModifier
public class DamageMultiplier : UpgradableTrait<DamageMultiplierInfo>, IDamageModifier
{
public DamageMultiplier(DamageMultiplierInfo info, string actorType)
: base(info, "DamageMultiplier", actorType) { }
public DamageMultiplier(DamageMultiplierInfo info)
: base(info) { }
int IDamageModifier.GetDamageModifier(Actor attacker, Damage damage) { return GetModifier(); }
int IDamageModifier.GetDamageModifier(Actor attacker, Damage damage)
{
return IsTraitDisabled ? 100 : Info.Modifier;
}
}
}

View File

@@ -11,17 +11,21 @@
namespace OpenRA.Mods.Common.Traits
{
[Desc("The firepower of this actor is multiplied based on upgrade level if specified.")]
public class FirepowerMultiplierInfo : UpgradeMultiplierTraitInfo
[Desc("Modifies the damage applied by this actor.")]
public class FirepowerMultiplierInfo : UpgradableTraitInfo
{
public override object Create(ActorInitializer init) { return new FirepowerMultiplier(this, init.Self.Info.Name); }
[FieldLoader.Require]
[Desc("Percentage modifier to apply.")]
public readonly int Modifier = 100;
public override object Create(ActorInitializer init) { return new FirepowerMultiplier(this); }
}
public class FirepowerMultiplier : UpgradeMultiplierTrait, IFirepowerModifier
public class FirepowerMultiplier : UpgradableTrait<FirepowerMultiplierInfo>, IFirepowerModifier
{
public FirepowerMultiplier(FirepowerMultiplierInfo info, string actorType)
: base(info, "FirepowerMultiplier", actorType) { }
public FirepowerMultiplier(FirepowerMultiplierInfo info)
: base(info) { }
int IFirepowerModifier.GetFirepowerModifier() { return GetModifier(); }
int IFirepowerModifier.GetFirepowerModifier() { return IsTraitDisabled ? 100 : Info.Modifier; }
}
}

View File

@@ -11,17 +11,21 @@
namespace OpenRA.Mods.Common.Traits
{
[Desc("The inaccuracy of this actor is multiplied based on upgrade level if specified.")]
public class InaccuracyMultiplierInfo : UpgradeMultiplierTraitInfo
[Desc("Modifies the inaccuracy of weapons fired by this actor.")]
public class InaccuracyMultiplierInfo : UpgradableTraitInfo
{
public override object Create(ActorInitializer init) { return new InaccuracyMultiplier(this, init.Self.Info.Name); }
[FieldLoader.Require]
[Desc("Percentage modifier to apply.")]
public readonly int Modifier = 100;
public override object Create(ActorInitializer init) { return new InaccuracyMultiplier(this); }
}
public class InaccuracyMultiplier : UpgradeMultiplierTrait, IInaccuracyModifier
public class InaccuracyMultiplier : UpgradableTrait<InaccuracyMultiplierInfo>, IInaccuracyModifier
{
public InaccuracyMultiplier(InaccuracyMultiplierInfo info, string actorType)
: base(info, "InaccuracyMultiplier", actorType) { }
public InaccuracyMultiplier(InaccuracyMultiplierInfo info)
: base(info) { }
int IInaccuracyModifier.GetInaccuracyModifier() { return GetModifier(); }
int IInaccuracyModifier.GetInaccuracyModifier() { return IsTraitDisabled ? 100 : Info.Modifier; }
}
}

View File

@@ -13,22 +13,30 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("The power usage/output of this actor is multiplied based on upgrade level if specified.")]
public class PowerMultiplierInfo : UpgradeMultiplierTraitInfo
[Desc("Modifies the power usage/output of this actor.")]
public class PowerMultiplierInfo : UpgradableTraitInfo
{
[FieldLoader.Require]
[Desc("Percentage modifier to apply.")]
public readonly int Modifier = 100;
public override object Create(ActorInitializer init) { return new PowerMultiplier(init.Self, this); }
}
public class PowerMultiplier : UpgradeMultiplierTrait, IPowerModifier, INotifyOwnerChanged
public class PowerMultiplier : UpgradableTrait<PowerMultiplierInfo>, IPowerModifier, INotifyOwnerChanged
{
PowerManager power;
public PowerMultiplier(Actor self, PowerMultiplierInfo info)
: base(info, "PowerMultiplier", self.Info.Name) { power = self.Owner.PlayerActor.Trait<PowerManager>(); }
: base(info)
{
power = self.Owner.PlayerActor.Trait<PowerManager>();
}
int IPowerModifier.GetPowerModifier() { return GetModifier(); }
protected override void TraitEnabled(Actor self) { power.UpdateActor(self); }
protected override void TraitDisabled(Actor self) { power.UpdateActor(self); }
protected override void Update(Actor self) { power.UpdateActor(self); }
int IPowerModifier.GetPowerModifier() { return IsTraitDisabled ? 100 : Info.Modifier; }
void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
{

View File

@@ -11,22 +11,21 @@
namespace OpenRA.Mods.Common.Traits
{
[Desc("Range of this actor is multiplied based on upgrade level.")]
public class RangeMultiplierInfo : UpgradeMultiplierTraitInfo, IRangeModifierInfo
[Desc("Modifies the range of weapons fired by this actor.")]
public class RangeMultiplierInfo : UpgradableTraitInfo
{
public override object Create(ActorInitializer init) { return new RangeMultiplier(this, init.Self.Info.Name); }
[FieldLoader.Require]
[Desc("Percentage modifier to apply.")]
public readonly int Modifier = 100;
int IRangeModifierInfo.GetRangeModifierDefault()
{
return BaseLevel > 0 || UpgradeTypes.Length == 0 ? 100 : Modifier[0];
}
public override object Create(ActorInitializer init) { return new RangeMultiplier(this); }
}
public class RangeMultiplier : UpgradeMultiplierTrait, IRangeModifier
public class RangeMultiplier : UpgradableTrait<RangeMultiplierInfo>, IRangeModifierInfo
{
public RangeMultiplier(RangeMultiplierInfo info, string actorType)
: base(info, "RangeMultiplier", actorType) { }
public RangeMultiplier(RangeMultiplierInfo info)
: base(info) { }
int IRangeModifier.GetRangeModifier() { return GetModifier(); }
int IRangeModifierInfo.GetRangeModifierDefault() { return IsTraitDisabled ? 100 : Info.Modifier; }
}
}

View File

@@ -11,17 +11,21 @@
namespace OpenRA.Mods.Common.Traits
{
[Desc("The reloading time of this actor is multiplied based on upgrade level if specified.")]
public class ReloadDelayMultiplierInfo : UpgradeMultiplierTraitInfo
[Desc("Modifies the reload time of weapons fired by this actor.")]
public class ReloadDelayMultiplierInfo : UpgradableTraitInfo
{
public override object Create(ActorInitializer init) { return new ReloadDelayMultiplier(this, init.Self.Info.Name); }
[FieldLoader.Require]
[Desc("Percentage modifier to apply.")]
public readonly int Modifier = 100;
public override object Create(ActorInitializer init) { return new ReloadDelayMultiplier(this); }
}
public class ReloadDelayMultiplier : UpgradeMultiplierTrait, IReloadModifier
public class ReloadDelayMultiplier : UpgradableTrait<ReloadDelayMultiplierInfo>, IReloadModifier
{
public ReloadDelayMultiplier(ReloadDelayMultiplierInfo info, string actorType)
: base(info, "ReloadDelayMultiplier", actorType) { }
public ReloadDelayMultiplier(ReloadDelayMultiplierInfo info)
: base(info) { }
int IReloadModifier.GetReloadModifier() { return GetModifier(); }
int IReloadModifier.GetReloadModifier() { return IsTraitDisabled ? 100 : Info.Modifier; }
}
}

View File

@@ -11,17 +11,21 @@
namespace OpenRA.Mods.Common.Traits
{
[Desc("The speed of this actor is multiplied based on upgrade level if specified.")]
public class SpeedMultiplierInfo : UpgradeMultiplierTraitInfo
[Desc("Modifies the movement speed of this actor.")]
public class SpeedMultiplierInfo : UpgradableTraitInfo
{
public override object Create(ActorInitializer init) { return new SpeedMultiplier(this, init.Self.Info.Name); }
[FieldLoader.Require]
[Desc("Percentage modifier to apply.")]
public readonly int Modifier = 100;
public override object Create(ActorInitializer init) { return new SpeedMultiplier(this); }
}
public class SpeedMultiplier : UpgradeMultiplierTrait, ISpeedModifier
public class SpeedMultiplier : UpgradableTrait<SpeedMultiplierInfo>, ISpeedModifier
{
public SpeedMultiplier(SpeedMultiplierInfo info, string actorType)
: base(info, "SpeedMultiplier", actorType) { }
public SpeedMultiplier(SpeedMultiplierInfo info)
: base(info) { }
int ISpeedModifier.GetSpeedModifier() { return GetModifier(); }
int ISpeedModifier.GetSpeedModifier() { return IsTraitDisabled ? 100 : Info.Modifier; }
}
}

View File

@@ -1,76 +0,0 @@
#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 System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public abstract class UpgradeMultiplierTraitInfo : ITraitInfo
{
[UpgradeUsedReference]
[Desc("Accepted upgrade types.")]
public readonly string[] UpgradeTypes = { };
[Desc("The lowest upgrade level using the scale.")]
public readonly int BaseLevel = 1;
[FieldLoader.Require]
[Desc("Percentages to apply with the first being applied at the base level.",
"Repeat last entry to accept time extensions.",
"If no upgrade types are specified, then the first/only modifier is always applied.")]
public readonly int[] Modifier = { };
public abstract object Create(ActorInitializer init);
}
public abstract class UpgradeMultiplierTrait : IUpgradable, IDisabledTrait, ISync
{
readonly UpgradeMultiplierTraitInfo info;
[Sync] int level = 0;
[Sync] public bool IsTraitDisabled { get; private set; }
public int AdjustedLevel { get { return level - info.BaseLevel; } }
public IEnumerable<string> UpgradeTypes { get { return info.UpgradeTypes; } }
protected UpgradeMultiplierTrait(UpgradeMultiplierTraitInfo info, string modifierType, string actorType)
{
if (info.Modifier.Length == 0)
throw new ArgumentException("No modifiers in " + modifierType + " for " + actorType);
this.info = info;
IsTraitDisabled = info.UpgradeTypes.Length > 0 && info.BaseLevel > 0;
level = IsTraitDisabled ? 0 : info.BaseLevel;
}
public bool AcceptsUpgradeLevel(Actor self, string type, int level)
{
return level < info.Modifier.Length + info.BaseLevel;
}
// Override to receive notice of level change.
protected virtual void Update(Actor self) { }
public void UpgradeLevelChanged(Actor self, string type, int oldLevel, int newLevel)
{
if (!UpgradeTypes.Contains(type))
return;
level = newLevel.Clamp(0, Math.Max(info.Modifier.Length + info.BaseLevel - 1, 0));
IsTraitDisabled = level < info.BaseLevel;
Update(self);
}
public int GetModifier()
{
return IsTraitDisabled ? 100 : info.Modifier[level - info.BaseLevel];
}
}
}

View File

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

View File

@@ -41,8 +41,8 @@ namespace OpenRA.Mods.Common.Traits
powerModifiers = Exts.Lazy(() => self.TraitsImplementing<IPowerModifier>().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)

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 class LeavesTrails : UpgradableTrait<LeavesTrailsInfo>, ITick, INotifyCreated
public class LeavesTrails : UpgradableTrait<LeavesTrailsInfo>, 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<BodyOrientation>();
facing = self.TraitOrDefault<IFacing>();
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;
}

View File

@@ -57,7 +57,7 @@ namespace OpenRA.Mods.Common.Traits.Render
// Defer this lookup until we really need it to ensure we get the correct value.
range = Exts.Lazy(() =>
{
var armaments = ai.TraitInfos<ArmamentInfo>().Where(a => a.UpgradeMinEnabledLevel == 0);
var armaments = ai.TraitInfos<ArmamentInfo>().Where(a => a.EnabledByDefault);
if (!armaments.Any())
return FallbackRange;

View File

@@ -41,7 +41,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
if (!EnabledByDefault)
yield break;
if (Palette != null)

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;
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>();
base.Created(self);
}
protected virtual string NormalizeInfantrySequence(Actor self, string baseSequence)

View File

@@ -59,7 +59,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
if (!EnabledByDefault)
yield break;
if (image == null)
@@ -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;

View File

@@ -1,28 +0,0 @@
#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
namespace OpenRA.Mods.Common.Traits.Render
{
public class WithRankDecorationInfo : WithDecorationInfo
{
public override object Create(ActorInitializer init) { return new WithRankDecoration(init.Self, this); }
}
public class WithRankDecoration : WithDecoration
{
public WithRankDecoration(Actor self, WithRankDecorationInfo info) : base(self, info) { }
protected override void UpgradeLevelChanged(Actor self, int oldLevel, int newLevel)
{
Anim.PlayFetchIndex(Info.Sequence, () => newLevel - 1);
}
}
}

View File

@@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
if (!EnabledByDefault)
yield break;
var body = init.Actor.TraitInfo<BodyOrientationInfo>();

View File

@@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public virtual IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
if (!EnabledByDefault)
yield break;
var anim = new Animation(init.World, image);

View File

@@ -38,7 +38,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
if (!EnabledByDefault)
yield break;
var body = init.Actor.TraitInfo<BodyOrientationInfo>();

View File

@@ -20,7 +20,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits.Render
{
[Desc("Displays a text overlay relative to the selection box.")]
public class WithTextDecorationInfo : UpgradableTraitInfo, IRulesetLoaded
public class WithTextDecorationInfo : UpgradableTraitInfo
{
[FieldLoader.Require] [Translate] public readonly string Text = null;
@@ -47,10 +47,12 @@ namespace OpenRA.Mods.Common.Traits.Render
public override object Create(ActorInitializer init) { return new WithTextDecoration(init.Self, this); }
void IRulesetLoaded<ActorInfo>.RulesetLoaded(Ruleset rules, ActorInfo info)
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
if (!Game.ModData.Manifest.Fonts.ContainsKey(Font))
throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(Font));
base.RulesetLoaded(rules, ai);
}
}

View File

@@ -37,7 +37,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public IEnumerable<VoxelAnimation> RenderPreviewVoxels(
ActorPreviewInitializer init, RenderVoxelsInfo rv, string image, Func<WRot> orientation, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
if (!EnabledByDefault)
yield break;
var body = init.Actor.TraitInfo<BodyOrientationInfo>();

View File

@@ -34,7 +34,7 @@ namespace OpenRA.Mods.Common.Traits.Render
public IEnumerable<VoxelAnimation> RenderPreviewVoxels(
ActorPreviewInitializer init, RenderVoxelsInfo rv, string image, Func<WRot> orientation, int facings, PaletteReference p)
{
if (UpgradeMinEnabledLevel > 0)
if (!EnabledByDefault)
yield break;
var body = init.Actor.TraitInfo<BodyOrientationInfo>();

View File

@@ -74,7 +74,11 @@ namespace OpenRA.Mods.Common.Traits
public WeaponInfo WeaponInfo { get; private set; }
public override object Create(ActorInitializer init) { return new NukePower(init.Self, this); }
public void RulesetLoaded(Ruleset rules, ActorInfo ai) { WeaponInfo = rules.Weapons[MissileWeapon.ToLowerInvariant()]; }
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
WeaponInfo = rules.Weapons[MissileWeapon.ToLowerInvariant()];
base.RulesetLoaded(rules, ai);
}
}
class NukePower : SupportPower

View File

@@ -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<TargetableInfo>, ITargetable, INotifyCreated
public class Targetable : UpgradableTrait<TargetableInfo>, 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<Cloak>().ToArray();
base.Created(self);
}
public virtual bool TargetableBy(Actor self, Actor viewer)

View File

@@ -10,30 +10,32 @@
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Support;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
/// <summary>Use as base class for *Info to subclass of UpgradableTrait. (See UpgradableTrait.)</summary>
public abstract class UpgradableTraitInfo : IUpgradableInfo
public abstract class UpgradableTraitInfo : IConditionConsumerInfo, IRulesetLoaded
{
static readonly IReadOnlyDictionary<string, bool> NoConditions = new ReadOnlyDictionary<string, bool>(new Dictionary<string, bool>());
[UpgradeUsedReference]
[Desc("The upgrade types which can enable or disable this trait.")]
public readonly HashSet<string> UpgradeTypes = new HashSet<string>();
[Desc("The minimum upgrade level at which this trait is enabled.", "Defaults to 0 (enabled by default).")]
public readonly int UpgradeMinEnabledLevel = 0;
[Desc("The maximum upgrade level at which the trait is enabled.",
"Defaults to UpgradeMaxAcceptedLevel (enabled for all levels greater than UpgradeMinEnabledLevel).",
"Set this to a value smaller than UpgradeMaxAcceptedLevel to disable the trait at higher levels.",
"Use UpgradeMaxAcceptedLevel: 2 (1 more) to be able to extend upgrade time.")]
public readonly int UpgradeMaxEnabledLevel = int.MaxValue;
[Desc("The maximum upgrade level that this trait will accept.")]
public readonly int UpgradeMaxAcceptedLevel = 1;
[Desc("Boolean expression defining the condition to enable this trait.")]
public readonly BooleanExpression RequiresCondition = null;
public abstract object Create(ActorInitializer init);
// HACK: A shim for all the ActorPreview code that used to query UpgradeMinEnabledLevel directly
// This can go away after we introduce an InitialUpgrades ActorInit and have the traits query the
// condition directly
public bool EnabledByDefault { get; private set; }
public virtual void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
EnabledByDefault = RequiresCondition != null ? RequiresCondition.Evaluate(NoConditions) : true;
}
}
/// <summary>
@@ -41,50 +43,59 @@ 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.
/// </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 IEnumerable<string> UpgradeTypes { get { return Info.UpgradeTypes; } }
IEnumerable<string> IConditionConsumer.Conditions
{
get
{
if (Info.RequiresCondition != null)
return Info.RequiresCondition.Variables;
return Enumerable.Empty<string>();
}
}
[Sync] public bool IsTraitDisabled { get; private set; }
public UpgradableTrait(InfoType info)
{
Info = info;
IsTraitDisabled = info.UpgradeTypes != null && info.UpgradeTypes.Count > 0 && info.UpgradeMinEnabledLevel > 0;
// Conditional traits will be enabled (if appropriate) by the UpgradeManager
// calling IConditionConsumer.ConditionsChanged at the end of INotifyCreated.
IsTraitDisabled = Info.RequiresCondition != null;
}
public bool AcceptsUpgradeLevel(Actor self, string type, int level)
protected virtual void Created(Actor self)
{
return level > 0 && level <= Info.UpgradeMaxAcceptedLevel;
if (Info.RequiresCondition == null)
TraitEnabled(self);
}
public void UpgradeLevelChanged(Actor self, string type, int oldLevel, int newLevel)
{
if (!Info.UpgradeTypes.Contains(type))
return;
void INotifyCreated.Created(Actor self) { Created(self); }
// Restrict the levels to the allowed range
oldLevel = oldLevel.Clamp(0, Info.UpgradeMaxAcceptedLevel);
newLevel = newLevel.Clamp(0, Info.UpgradeMaxAcceptedLevel);
if (oldLevel == newLevel)
void IConditionConsumer.ConditionsChanged(Actor self, IReadOnlyDictionary<string, bool> conditions)
{
if (Info.RequiresCondition == null)
return;
var wasDisabled = IsTraitDisabled;
IsTraitDisabled = newLevel < Info.UpgradeMinEnabledLevel || newLevel > Info.UpgradeMaxEnabledLevel;
UpgradeLevelChanged(self, oldLevel, newLevel);
IsTraitDisabled = !Info.RequiresCondition.Evaluate(conditions);
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) { }
}
}

View File

@@ -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<UpgradeManager>, IRulesetLoaded
{
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 UpgradeManagerInfo : TraitInfo<UpgradeManager>, Requires<IConditionConsumerInfo> { }
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 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<UpgradeSource> Sources;
public readonly HashSet<ConditionSource> 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<UpgradeSource> { new UpgradeSource(duration, source) };
Sources = new HashSet<ConditionSource> { new ConditionSource(duration, source) };
}
public void Tick()
@@ -65,29 +60,124 @@ namespace OpenRA.Mods.Common.Traits
}
}
class UpgradeState
class ConditionState
{
public readonly List<IUpgradable> Traits = new List<IUpgradable>();
public readonly List<object> Sources = new List<object>();
/// <summary>Traits that have registered to be notified when this condition changes.</summary>
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>>();
}
readonly List<TimedUpgrade> timedUpgrades = new List<TimedUpgrade>();
readonly Dictionary<IUpgradable, int> levels = new Dictionary<IUpgradable, int>();
Dictionary<string, UpgradeState> upgrades;
readonly List<TimedCondition> timedConditions = new List<TimedCondition>();
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)
{
upgrades = new Dictionary<string, UpgradeState>();
foreach (var up in self.TraitsImplementing<IUpgradable>())
foreach (var t in up.UpgradeTypes)
upgrades.GetOrAdd(t).Traits.Add(up);
state = new Dictionary<string, ConditionState>();
readOnlyConditionCache = new ReadOnlyDictionary<string, bool>(conditionCache);
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)
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);
}
/// <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
@@ -98,11 +188,11 @@ namespace OpenRA.Mods.Common.Traits
/// 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)
{
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<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)
{
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)]);
}
/// <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)
{
CheckCanManageUpgrades();
return upgrades.ContainsKey(upgrade);
CheckCanManageConditions();
return state.ContainsKey(upgrade);
}
/// <summary>Returns true only if the actor can accept another level of the upgrade.</summary>
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<int, int> action)
{
CheckCanManageUpgrades();
CheckCanManageConditions();
UpgradeState s;
if (!upgrades.TryGetValue(upgrade, out s))
ConditionState s;
if (!state.TryGetValue(upgrade, out s))
return;
s.Watchers.Add(action);
}
/// <summary>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,
/// <summary>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).</summary>
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
}
}

View File

@@ -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<WandersInfo>, INotifyCreated, INotifyIdle, INotifyBecomingIdle
public class Wanders : UpgradableTrait<WandersInfo>, 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<IMove>() as IResolveOrder;
base.Created(self);
}
public virtual void OnBecomingIdle(Actor self)

View File

@@ -103,15 +103,14 @@ namespace OpenRA.Mods.Common.Traits
[RequireExplicitImplementation]
public interface INotifyPassengerExited { void OnPassengerExited(Actor self, Actor passenger); }
public interface IUpgradable
{
IEnumerable<string> 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<string> Conditions { get; }
void ConditionsChanged(Actor self, IReadOnlyDictionary<string, bool> conditions);
}
public interface INotifyHarvesterAction
{

View File

@@ -435,6 +435,48 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
// Rename Replaced upgrade consumers with conditions
if (engineVersion < 20161117)
{
var upgradeTypesNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "UpgradeTypes");
if (upgradeTypesNode != null)
{
var upgradeMinEnabledLevel = 0;
var upgradeMaxEnabledLevel = int.MaxValue;
var upgradeMaxAcceptedLevel = 1;
var upgradeTypes = FieldLoader.GetValue<string[]>("", upgradeTypesNode.Value.Value);
var minEnabledNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "UpgradeMinEnabledLevel");
if (minEnabledNode != null)
upgradeMinEnabledLevel = FieldLoader.GetValue<int>("", minEnabledNode.Value.Value);
var maxEnabledNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "UpgradeMaxEnabledLevel");
if (maxEnabledNode != null)
upgradeMaxEnabledLevel = FieldLoader.GetValue<int>("", maxEnabledNode.Value.Value);
var maxAcceptedNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "UpgradeMaxAcceptedLevel");
if (maxAcceptedNode != null)
upgradeMaxAcceptedLevel = FieldLoader.GetValue<int>("", maxAcceptedNode.Value.Value);
var processed = false;
if (upgradeTypes.Length == 1 && upgradeMinEnabledLevel == 0 && upgradeMaxEnabledLevel == 0 && upgradeMaxAcceptedLevel == 1)
{
node.Value.Nodes.Add(new MiniYamlNode("RequiresCondition", "!" + upgradeTypes.First()));
processed = true;
}
else if (upgradeTypes.Length == 1 && upgradeMinEnabledLevel == 1 && upgradeMaxEnabledLevel == int.MaxValue && upgradeMaxAcceptedLevel == 1)
{
node.Value.Nodes.Add(new MiniYamlNode("RequiresCondition", upgradeTypes.First()));
processed = true;
}
if (processed)
node.Value.Nodes.RemoveAll(n => n.Key == "UpgradeTypes" || n.Key == "UpgradeMinEnabledLevel" ||
n.Key == "UpgradeMaxEnabledLevel" || n.Key == "UpgradeMaxAcceptedLevel");
else
Console.WriteLine("Unable to automatically migrate {0}:{1} UpgradeTypes to RequiresCondition. This must be corrected manually", parent.Key, node.Key);
}
}
UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}

View File

@@ -13,6 +13,7 @@ using System;
using System.Drawing;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Support;
using OpenRA.Traits;
using OpenRA.Widgets;
@@ -74,7 +75,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var requiresString = prereqs.Any() ? requiresLabel.Text.F(prereqs.JoinWith(", ")) : "";
requiresLabel.GetText = () => requiresString;
var power = actor.TraitInfos<PowerInfo>().Where(i => i.UpgradeMinEnabledLevel < 1).Sum(i => i.Amount);
var power = actor.TraitInfos<PowerInfo>().Where(i => i.EnabledByDefault).Sum(i => i.Amount);
var powerString = power.ToString();
powerLabel.GetText = () => powerString;
powerLabel.GetColor = () => ((pm.PowerProvided - pm.PowerDrained) >= -power || power > 0)