Added IDisabledTrait & rewrote upgrade code using a level-based approach.

Upgradeable traits are notified whenever an upgrade of their declared types are granted or revoked.  The traits maintain their own internal level counter, which is then used to enable or disable the trait functionality.  A trait can register for multiple upgrade types which then all affect the internal level counter.

	IDisabledTrait for identifying (and filtering) disabled traits
	UpgradableTrait provides an abstract base for traits to support upgrade levels
	Added IDisabledTrait support to GlobalButtonOrderGenerator

	Includes rework by pchote with alterations.
This commit is contained in:
atlimit8
2014-10-13 18:09:16 -05:00
parent 76cf5b8b98
commit bbd54cb32f
38 changed files with 545 additions and 415 deletions

View File

@@ -12,6 +12,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.GameRules;
using OpenRA.Mods.Common;
using OpenRA.Primitives;
using OpenRA.Traits;
@@ -24,7 +25,7 @@ namespace OpenRA.Mods.RA
}
[Desc("Allows you to attach weapons to the unit (use @IdentifierSuffix for > 1)")]
public class ArmamentInfo : ITraitInfo, Requires<AttackBaseInfo>
public class ArmamentInfo : UpgradableTraitInfo, ITraitInfo, Requires<AttackBaseInfo>
{
public readonly string Name = "primary";
@@ -53,18 +54,11 @@ namespace OpenRA.Mods.RA
[Desc("Use multiple muzzle images if non-zero")]
public readonly int MuzzleSplitFacings = 0;
[Desc("Enable only if this upgrade is enabled.")]
public readonly string RequiresUpgrade = null;
[Desc("Disable if this upgrade is enabled.")]
public readonly string RestrictedByUpgrade = null;
public object Create(ActorInitializer init) { return new Armament(init.self, this); }
}
public class Armament : ITick, IExplodeModifier, IUpgradable
public class Armament : UpgradableTrait<ArmamentInfo>, ITick, IExplodeModifier
{
public readonly ArmamentInfo Info;
public readonly WeaponInfo Weapon;
public readonly Barrel[] Barrels;
@@ -78,13 +72,10 @@ namespace OpenRA.Mods.RA
public int FireDelay { get; private set; }
public int Burst { get; private set; }
bool requiresUpgrade;
bool restrictedByUpgrade;
public Armament(Actor self, ArmamentInfo info)
: base(info)
{
this.self = self;
Info = info;
// We can't resolve these until runtime
Turret = Exts.Lazy(() => self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.Name == info.Turret));
@@ -108,27 +99,11 @@ namespace OpenRA.Mods.RA
barrels.Add(new Barrel { Offset = WVec.Zero, Yaw = WAngle.Zero });
Barrels = barrels.ToArray();
// Disable if an upgrade is required
requiresUpgrade = info.RequiresUpgrade != null;
}
public bool AcceptsUpgrade(string type)
{
return type == Info.RequiresUpgrade || type == Info.RestrictedByUpgrade;
}
public void UpgradeAvailable(Actor self, string type, bool available)
{
if (type == Info.RequiresUpgrade)
requiresUpgrade = !available;
else if (type == Info.RestrictedByUpgrade)
restrictedByUpgrade = available;
}
public void Tick(Actor self)
{
if (requiresUpgrade || restrictedByUpgrade)
if (IsTraitDisabled)
return;
if (FireDelay > 0)
@@ -226,7 +201,7 @@ namespace OpenRA.Mods.RA
return barrel;
}
public bool IsReloading { get { return FireDelay > 0 || requiresUpgrade || restrictedByUpgrade; } }
public bool IsReloading { get { return FireDelay > 0 || IsTraitDisabled; } }
public bool ShouldExplode(Actor self) { return !IsReloading; }
public WVec MuzzleOffset(Actor self, Barrel b)

View File

@@ -15,6 +15,7 @@ using OpenRA.Traits;
using OpenRA.Primitives;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Traits;
using OpenRA.Mods.Common;
using OpenRA.Mods.Common.Orders;
namespace OpenRA.Mods.RA

View File

@@ -13,12 +13,13 @@ using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
[Desc("This unit can cloak and uncloak in specific situations.")]
public class CloakInfo : ITraitInfo
public class CloakInfo : UpgradableTraitInfo, ITraitInfo
{
[Desc("Measured in game ticks.")]
public readonly int InitialDelay = 10;
@@ -30,9 +31,6 @@ namespace OpenRA.Mods.RA
public readonly bool UncloakOnMove = false;
public readonly bool UncloakOnUnload = true;
[Desc("Enable only if this upgrade is enabled.")]
public readonly string RequiresUpgrade = null;
public readonly string CloakSound = null;
public readonly string UncloakSound = null;
public readonly string Palette = "cloak";
@@ -42,44 +40,26 @@ namespace OpenRA.Mods.RA
public object Create(ActorInitializer init) { return new Cloak(init.self, this); }
}
public class Cloak : IUpgradable, IRenderModifier, INotifyDamageStateChanged, INotifyAttack, ITick, IVisibilityModifier, IRadarColorModifier, ISync
public class Cloak : UpgradableTrait<CloakInfo>, IRenderModifier, INotifyDamageStateChanged, INotifyAttack, ITick, IVisibilityModifier, IRadarColorModifier
{
[Sync] int remainingTime;
[Sync] bool damageDisabled;
[Sync] bool disabled;
Actor self;
public readonly CloakInfo Info;
CPos? lastPos;
public Cloak(Actor self, CloakInfo info)
: base (info)
{
this.self = self;
Info = info;
remainingTime = info.InitialDelay;
// Disable if an upgrade is required
disabled = info.RequiresUpgrade != null;
}
public bool AcceptsUpgrade(string type)
protected override void UpgradeDisabled(Actor self)
{
return type == Info.RequiresUpgrade;
}
public void UpgradeAvailable(Actor self, string type, bool available)
{
if (type == Info.RequiresUpgrade)
{
disabled = !available;
if (disabled)
{
Uncloak();
remainingTime = Info.InitialDelay;
}
}
Uncloak();
remainingTime = Info.InitialDelay;
}
public void Uncloak() { Uncloak(Info.CloakDelay); }
@@ -94,7 +74,7 @@ namespace OpenRA.Mods.RA
public void Attacking(Actor self, Target target, Armament a, Barrel barrel) { if (Info.UncloakOnAttack) Uncloak(); }
public bool Cloaked { get { return !disabled && remainingTime <= 0; } }
public bool Cloaked { get { return !IsTraitDisabled && remainingTime <= 0; } }
public void DamageStateChanged(Actor self, AttackInfo e)
{
@@ -105,7 +85,7 @@ namespace OpenRA.Mods.RA
public IEnumerable<IRenderable> ModifyRender(Actor self, WorldRenderer wr, IEnumerable<IRenderable> r)
{
if (remainingTime > 0 || disabled)
if (remainingTime > 0 || IsTraitDisabled)
return r;
if (Cloaked && IsVisible(self, self.World.RenderPlayer))
@@ -121,10 +101,10 @@ namespace OpenRA.Mods.RA
public void Tick(Actor self)
{
if (disabled)
if (IsTraitDisabled)
return;
if (remainingTime > 0 && !disabled && !damageDisabled && --remainingTime <= 0)
if (remainingTime > 0 && !IsTraitDisabled && !damageDisabled && --remainingTime <= 0)
Sound.Play(Info.CloakSound, self.CenterPosition);
if (self.IsDisabled())

View File

@@ -9,6 +9,7 @@
#endregion
using System.Linq;
using OpenRA.Mods.Common;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Crates

View File

@@ -11,40 +11,23 @@
using System;
using System.Collections.Generic;
using OpenRA.GameRules;
using OpenRA.Mods.Common;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public class DisableUpgradeInfo : ITraitInfo
public class DisableUpgradeInfo : UpgradableTraitInfo, ITraitInfo
{
public readonly string RequiresUpgrade = "disable";
public object Create(ActorInitializer init) { return new DisableUpgrade(this); }
}
public class DisableUpgrade : IUpgradable, IDisable, IDisableMove
public class DisableUpgrade : UpgradableTrait<DisableUpgradeInfo>, IDisable, IDisableMove
{
readonly DisableUpgradeInfo info;
bool enabled;
public DisableUpgrade(DisableUpgradeInfo info)
{
this.info = info;
}
: base(info) { }
public bool AcceptsUpgrade(string type)
{
return type == info.RequiresUpgrade;
}
public void UpgradeAvailable(Actor self, string type, bool available)
{
if (type == info.RequiresUpgrade)
enabled = available;
}
public bool Disabled { get { return enabled; } }
public bool MoveDisabled(Actor self) { return enabled; }
// Disable the actor when this trait is enabled.
public bool Disabled { get { return !IsTraitDisabled; } }
public bool MoveDisabled(Actor self) { return !IsTraitDisabled; }
}
}

View File

@@ -47,7 +47,7 @@ namespace OpenRA.Mods.RA
{ 200, new[] { "firepower", "damage", "speed", "reload", "inaccuracy" } },
{ 400, new[] { "firepower", "damage", "speed", "reload", "inaccuracy" } },
{ 800, new[] { "firepower", "damage", "speed", "reload", "inaccuracy" } },
{ 1600, new[] { "firepower", "damage", "speed", "reload", "inaccuracy", "selfheal" } }
{ 1600, new[] { "firepower", "damage", "speed", "reload", "inaccuracy", "eliteweapon", "selfheal" } }
};
}

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using OpenRA.GameRules;
using OpenRA.Mods.Common;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
@@ -36,7 +37,7 @@ namespace OpenRA.Mods.RA
public object Create(ActorInitializer init) { return new GainsStatUpgrades(this); }
}
public class GainsStatUpgrades : IUpgradable, IFirepowerModifier, IDamageModifier, ISpeedModifier, IReloadModifier, IInaccuracyModifier
public class GainsStatUpgrades : IUpgradable, IFirepowerModifier, IDamageModifier, ISpeedModifier, IReloadModifier, IInaccuracyModifier, IDisabledTrait
{
readonly GainsStatUpgradesInfo info;
[Sync] int firepowerLevel = 0;
@@ -44,34 +45,59 @@ namespace OpenRA.Mods.RA
[Sync] int damageLevel = 0;
[Sync] int reloadLevel = 0;
[Sync] int inaccuracyLevel = 0;
public bool IsTraitDisabled { get { return firepowerLevel == 0 && speedLevel == 0 && damageLevel == 0 && reloadLevel == 0 && inaccuracyLevel == 0; } }
public IEnumerable<string> UpgradeTypes
{
get
{
yield return info.FirepowerUpgrade;
yield return info.DamageUpgrade;
yield return info.SpeedUpgrade;
yield return info.ReloadUpgrade;
yield return info.InaccuracyUpgrade;
}
}
public GainsStatUpgrades(GainsStatUpgradesInfo info)
{
this.info = info;
}
public bool AcceptsUpgrade(string type)
public bool AcceptsUpgradeLevel(Actor self, string type, int level)
{
return (type == info.FirepowerUpgrade && firepowerLevel < info.FirepowerModifier.Length)
|| (type == info.DamageUpgrade && damageLevel < info.DamageModifier.Length)
|| (type == info.SpeedUpgrade && speedLevel < info.SpeedModifier.Length)
|| (type == info.ReloadUpgrade && reloadLevel < info.ReloadModifier.Length)
|| (type == info.InaccuracyUpgrade && inaccuracyLevel < info.InaccuracyModifier.Length);
if (level < 0)
return false;
if (type == info.FirepowerUpgrade)
return level <= info.FirepowerModifier.Length;
if (type == info.DamageUpgrade)
return level <= info.DamageModifier.Length;
if (type == info.SpeedUpgrade)
return level <= info.SpeedModifier.Length;
if (type == info.ReloadUpgrade)
return level <= info.ReloadModifier.Length;
if (type == info.InaccuracyUpgrade)
return level <= info.InaccuracyModifier.Length;
return false;
}
public void UpgradeAvailable(Actor self, string type, bool available)
public void UpgradeLevelChanged(Actor self, string type, int oldLevel, int newLevel)
{
var mod = available ? 1 : -1;
if (type == info.FirepowerUpgrade)
firepowerLevel = (firepowerLevel + mod).Clamp(0, info.FirepowerModifier.Length);
firepowerLevel = newLevel.Clamp(0, info.FirepowerModifier.Length);
else if (type == info.DamageUpgrade)
damageLevel = (damageLevel + mod).Clamp(0, info.DamageModifier.Length);
damageLevel = newLevel.Clamp(0, info.DamageModifier.Length);
else if (type == info.SpeedUpgrade)
speedLevel = (speedLevel + mod).Clamp(0, info.SpeedModifier.Length);
speedLevel = newLevel.Clamp(0, info.SpeedModifier.Length);
else if (type == info.ReloadUpgrade)
reloadLevel = (reloadLevel + mod).Clamp(0, info.ReloadModifier.Length);
reloadLevel = newLevel.Clamp(0, info.ReloadModifier.Length);
else if (type == info.InaccuracyUpgrade)
inaccuracyLevel = (inaccuracyLevel + mod).Clamp(0, info.InaccuracyModifier.Length);
inaccuracyLevel = newLevel.Clamp(0, info.InaccuracyModifier.Length);
}
public int GetDamageModifier(Actor attacker, DamageWarhead warhead)

View File

@@ -12,7 +12,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenRA.Mods.RA;
using OpenRA.Mods.Common;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
@@ -30,6 +30,7 @@ namespace OpenRA.Mods.RA
readonly GlobalUpgradableInfo info;
readonly GlobalUpgradeManager globalManager;
readonly UpgradeManager manager;
bool wasAvailable;
public GlobalUpgradable(Actor self, GlobalUpgradableInfo info)
{
@@ -52,13 +53,17 @@ namespace OpenRA.Mods.RA
public void PrerequisitesUpdated(Actor self, bool available)
{
foreach (var u in info.Upgrades)
{
if (available)
if (available == wasAvailable)
return;
if (available)
foreach (var u in info.Upgrades)
manager.GrantUpgrade(self, u, this);
else
else
foreach (var u in info.Upgrades)
manager.RevokeUpgrade(self, u, this);
}
wasAvailable = available;
}
}
}

View File

@@ -11,41 +11,24 @@
using System;
using System.Collections.Generic;
using OpenRA.GameRules;
using OpenRA.Mods.Common;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public class InvulnerabilityUpgradeInfo : ITraitInfo
public class InvulnerabilityUpgradeInfo : UpgradableTraitInfo, ITraitInfo
{
public readonly string RequiresUpgrade = "invulnerability";
public object Create(ActorInitializer init) { return new InvulnerabilityUpgrade(this); }
}
public class InvulnerabilityUpgrade : IUpgradable, IDamageModifier
public class InvulnerabilityUpgrade : UpgradableTrait<InvulnerabilityUpgradeInfo>, IDamageModifier
{
readonly InvulnerabilityUpgradeInfo info;
bool enabled;
public InvulnerabilityUpgrade(InvulnerabilityUpgradeInfo info)
{
this.info = info;
}
public bool AcceptsUpgrade(string type)
{
return type == info.RequiresUpgrade;
}
public void UpgradeAvailable(Actor self, string type, bool available)
{
if (type == info.RequiresUpgrade)
enabled = available;
}
: base (info) { }
public int GetDamageModifier(Actor attacker, DamageWarhead warhead)
{
return enabled ? 0 : 100;
return IsTraitDisabled ? 100 : 0;
}
}
}

View File

@@ -12,57 +12,38 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenRA.Effects;
using OpenRA.Mods.Common;
using OpenRA.Primitives;
using OpenRA.Traits;
using OpenRA.Effects;
namespace OpenRA.Mods.RA
{
class KillsSelfInfo : ITraitInfo
class KillsSelfInfo : UpgradableTraitInfo, ITraitInfo
{
[Desc("Enable only if this upgrade is enabled.")]
public readonly string RequiresUpgrade = null;
[Desc("Remove the actor from the world (and destroy it) instead of killing it.")]
public readonly bool RemoveInstead = false;
public object Create(ActorInitializer init) { return new KillsSelf(init.self, this); }
public object Create(ActorInitializer init) { return new KillsSelf(this); }
}
class KillsSelf : INotifyAddedToWorld, IUpgradable
class KillsSelf : UpgradableTrait<KillsSelfInfo>, INotifyAddedToWorld
{
readonly KillsSelfInfo info;
readonly Actor self;
public KillsSelf(Actor self, KillsSelfInfo info)
{
this.info = info;
this.self = self;
}
public KillsSelf(KillsSelfInfo info)
: base(info) { }
public void AddedToWorld(Actor self)
{
if (info.RequiresUpgrade == null)
Kill();
if (!IsTraitDisabled)
UpgradeEnabled(self);
}
public bool AcceptsUpgrade(string type)
{
return type == info.RequiresUpgrade;
}
public void UpgradeAvailable(Actor self, string type, bool available)
{
if (type == info.RequiresUpgrade)
Kill();
}
void Kill()
protected override void UpgradeEnabled(Actor self)
{
if (self.IsDead)
return;
if (info.RemoveInstead || !self.HasTrait<Health>())
if (Info.RemoveInstead || !self.HasTrait<Health>())
self.Destroy();
else
self.Kill(self);

View File

@@ -463,10 +463,8 @@
<Compile Include="Scripting\Properties\HelicopterProperties.cs" />
<Compile Include="Scripting\Properties\PlaneProperties.cs" />
<Compile Include="SupportPowers\GrantUpgradePower.cs" />
<Compile Include="TimedUpgradeBar.cs" />
<Compile Include="InvulnerabilityUpgrade.cs" />
<Compile Include="DisableUpgrade.cs" />
<Compile Include="UpgradeManager.cs" />
<Compile Include="KillsSelf.cs" />
<Compile Include="Warheads\GrantUpgradeWarhead.cs" />
<Compile Include="Crates\GrantUpgradeCrateAction.cs" />

View File

@@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Power;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Orders
{
@@ -39,7 +40,8 @@ namespace OpenRA.Mods.RA.Orders
if (mi.Button == MouseButton.Left)
{
var underCursor = world.ScreenMap.ActorsAt(mi)
.FirstOrDefault(a => a.Owner == world.LocalPlayer && a.HasTrait<T>());
.FirstOrDefault(a => a.Owner == world.LocalPlayer && a.TraitsImplementing<T>()
.Any(Exts.IsTraitEnabled));
if (underCursor != null)
yield return new Order(order, underCursor, false);

View File

@@ -8,6 +8,7 @@
*/
#endregion
using OpenRA.Mods.Common;
using OpenRA.Scripting;
using OpenRA.Traits;

View File

@@ -8,62 +8,43 @@
*/
#endregion
using System;
using System.Linq;
using OpenRA.Mods.Common;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
[Desc("Attach this to actors which should be able to regenerate their health points.")]
class SelfHealingInfo : ITraitInfo, Requires<HealthInfo>
class SelfHealingInfo : UpgradableTraitInfo, ITraitInfo, Requires<HealthInfo>
{
public readonly int Step = 5;
public readonly int Ticks = 5;
public readonly float HealIfBelow = .5f;
public readonly int DamageCooldown = 0;
[Desc("Enable only if this upgrade is enabled.")]
public readonly string RequiresUpgrade = null;
public virtual object Create(ActorInitializer init) { return new SelfHealing(init.self, this); }
}
class SelfHealing : ITick, ISync, INotifyDamage, IUpgradable
class SelfHealing : UpgradableTrait<SelfHealingInfo>, ITick, INotifyDamage
{
readonly SelfHealingInfo info;
readonly Health health;
[Sync] int ticks;
[Sync] int damageTicks;
[Sync] bool disabled;
public SelfHealing(Actor self, SelfHealingInfo info)
: base (info)
{
this.info = info;
health = self.Trait<Health>();
// Disable if an upgrade is required
disabled = info.RequiresUpgrade != null;
}
public bool AcceptsUpgrade(string type)
{
return type == info.RequiresUpgrade;
}
public void UpgradeAvailable(Actor self, string type, bool available)
{
if (type == info.RequiresUpgrade)
disabled = !available;
}
public void Tick(Actor self)
{
if (self.IsDead || disabled)
if (self.IsDead || IsTraitDisabled)
return;
if (health.HP >= info.HealIfBelow * health.MaxHP)
if (health.HP >= Info.HealIfBelow * health.MaxHP)
return;
if (damageTicks > 0)
@@ -74,15 +55,15 @@ namespace OpenRA.Mods.RA
if (--ticks <= 0)
{
ticks = info.Ticks;
self.InflictDamage(self, -info.Step, null);
ticks = Info.Ticks;
self.InflictDamage(self, -Info.Step, null);
}
}
public void Damaged(Actor self, AttackInfo e)
{
if (e.Damage > 0)
damageTicks = info.DamageCooldown;
damageTicks = Info.DamageCooldown;
}
}
}

View File

@@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common;
using OpenRA.Mods.RA.Render;
using OpenRA.Traits;

View File

@@ -1,58 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2014 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Drawing;
using System.Linq;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
[Desc("Visualizes the remaining time for an upgrade.")]
class TimedUpgradeBarInfo : ITraitInfo, Requires<UpgradeManagerInfo>
{
[Desc("Upgrade that this bar corresponds to")]
public readonly string Upgrade = null;
public readonly Color Color = Color.Red;
public object Create(ActorInitializer init) { return new TimedUpgradeBar(init.self, this); }
}
class TimedUpgradeBar : ISelectionBar
{
readonly TimedUpgradeBarInfo info;
readonly Actor self;
float value;
public TimedUpgradeBar(Actor self, TimedUpgradeBarInfo info)
{
this.self = self;
this.info = info;
self.Trait<UpgradeManager>().RegisterWatcher(info.Upgrade, Update);
}
public void Update(int duration, int remaining)
{
value = remaining * 1f / duration;
}
public float GetValue()
{
if (!self.Owner.IsAlliedWith(self.World.RenderPlayer))
return 0;
return value;
}
public Color GetColor() { return info.Color; }
}
}

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using OpenRA.GameRules;
using OpenRA.Mods.Common;
using OpenRA.Traits;
namespace OpenRA.Mods.RA

View File

@@ -1,134 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2014 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
public class UpgradeManagerInfo : ITraitInfo
{
public object Create(ActorInitializer init) { return new UpgradeManager(init); }
}
public class UpgradeManager : ITick
{
class TimedUpgrade
{
public readonly string Upgrade;
public readonly int Duration;
public int Remaining;
public TimedUpgrade(string upgrade, int duration)
{
Upgrade = upgrade;
Duration = duration;
Remaining = duration;
}
public void Tick() { Remaining--; }
}
readonly List<TimedUpgrade> timedUpgrades = new List<TimedUpgrade>();
readonly Dictionary<string, List<object>> sources = new Dictionary<string, List<object>>();
readonly Dictionary<string, List<Action<int, int>>> watchers = new Dictionary<string, List<Action<int, int>>>();
readonly Lazy<IEnumerable<IUpgradable>> upgradable;
public UpgradeManager(ActorInitializer init)
{
upgradable = Exts.Lazy(() => init.self.TraitsImplementing<IUpgradable>());
}
public void GrantTimedUpgrade(Actor self, string upgrade, int duration)
{
var timed = timedUpgrades.FirstOrDefault(u => u.Upgrade == upgrade);
if (timed == null)
{
timed = new TimedUpgrade(upgrade, duration);
timedUpgrades.Add(timed);
GrantUpgrade(self, upgrade, timed);
}
else
timed.Remaining = Math.Max(duration, timed.Remaining);
}
public void GrantUpgrade(Actor self, string upgrade, object source)
{
List<object> ss;
if (!sources.TryGetValue(upgrade, out ss))
{
ss = new List<object>();
sources.Add(upgrade, ss);
foreach (var up in upgradable.Value)
if (up.AcceptsUpgrade(upgrade))
up.UpgradeAvailable(self, upgrade, true);
}
// Track the upgrade source so that the upgrade can be removed without conflicts
ss.Add(source);
}
public void RevokeUpgrade(Actor self, string upgrade, object source)
{
// This upgrade may have been granted by multiple sources
// We must be careful to only remove the upgrade after all
// sources have been revoked
List<object> ss;
if (!sources.TryGetValue(upgrade, out ss))
return;
ss.Remove(source);
if (!ss.Any())
{
foreach (var up in upgradable.Value)
if (up.AcceptsUpgrade(upgrade))
up.UpgradeAvailable(self, upgrade, false);
sources.Remove(upgrade);
}
}
public bool AcceptsUpgrade(Actor self, string upgrade)
{
return upgradable.Value.Any(up => up.AcceptsUpgrade(upgrade));
}
public void RegisterWatcher(string upgrade, Action<int, int> action)
{
if (!watchers.ContainsKey(upgrade))
watchers.Add(upgrade, new List<Action<int, int>>());
watchers[upgrade].Add(action);
}
public void Tick(Actor self)
{
foreach (var u in timedUpgrades)
{
u.Tick();
if (u.Remaining <= 0)
RevokeUpgrade(self, u.Upgrade, u);
List<Action<int, int>> actions;
if (watchers.TryGetValue(u.Upgrade, out actions))
foreach (var a in actions)
a(u.Duration, u.Remaining);
}
timedUpgrades.RemoveAll(u => u.Remaining <= 0);
}
}
}

View File

@@ -13,6 +13,7 @@ using System.Linq;
using OpenRA.Effects;
using OpenRA.GameRules;
using OpenRA.Traits;
using OpenRA.Mods.Common;
using OpenRA.Mods.RA.Effects;
namespace OpenRA.Mods.RA