diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index 15aaec6864..872f2a56de 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -575,11 +575,51 @@ namespace OpenRA.Mods.Common.UtilityCommands } } - // Routed unit upgrades via the UnitUpgradeManager trait if (engineVersion < 20141001) { + // Routed unit upgrades via the UnitUpgradeManager trait if (depth == 0 && node.Value.Nodes.Any(n => n.Key.StartsWith("GainsStatUpgrades"))) node.Value.Nodes.Add(new MiniYamlNode("UnitUpgradeManager", new MiniYaml(""))); + + // Replaced IronCurtainPower -> GrantUpgradePower + if (depth == 1 && node.Key == "IronCurtainPower") + { + node.Key = "GrantUpgradePower@IRONCURTAIN"; + node.Value.Nodes.Add(new MiniYamlNode("Upgrades", "invulnerability")); + + var durationNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Duration"); + if (durationNode != null) + durationNode.Value.Value = (int.Parse(durationNode.Value.Value) * 25).ToString(); + else + node.Value.Nodes.Add(new MiniYamlNode("Duration", "600")); + + var soundNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "IronCurtainSound"); + if (soundNode != null) + soundNode.Key = "GrantUpgradeSound"; + } + + if (depth == 0 && node.Value.Nodes.Any(n => n.Key.StartsWith("IronCurtainable"))) + { + node.Value.Nodes.RemoveAll(n => n.Key.StartsWith("IronCurtainable")); + + var overlayKeys = new List(); + overlayKeys.Add(new MiniYamlNode("RequiresUpgrade", "invulnerability")); + node.Value.Nodes.Add(new MiniYamlNode("UpgradeOverlay@IRONCURTAIN", new MiniYaml("", overlayKeys))); + + var invulnKeys = new List(); + invulnKeys.Add(new MiniYamlNode("RequiresUpgrade", "invulnerability")); + node.Value.Nodes.Add(new MiniYamlNode("InvulnerabilityUpgrade@IRONCURTAIN", new MiniYaml("", invulnKeys))); + + var barKeys = new List(); + barKeys.Add(new MiniYamlNode("Upgrade", "invulnerability")); + node.Value.Nodes.Add(new MiniYamlNode("TimedUpgradeBar", new MiniYaml("", barKeys))); + + if (!node.Value.Nodes.Any(n => n.Key.StartsWith("UnitUpgradeManager"))) + node.Value.Nodes.Add(new MiniYamlNode("UnitUpgradeManager", new MiniYaml(""))); + } + + if (depth == 1 && node.Key == "-IronCurtainable") + node.Key = "-InvulnerabilityUpgrade@IRONCURTAIN"; } UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); diff --git a/OpenRA.Mods.RA/Effects/InvulnEffect.cs b/OpenRA.Mods.RA/Effects/InvulnEffect.cs deleted file mode 100644 index da804ea74f..0000000000 --- a/OpenRA.Mods.RA/Effects/InvulnEffect.cs +++ /dev/null @@ -1,44 +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.Collections.Generic; -using OpenRA.Effects; -using OpenRA.Graphics; - -namespace OpenRA.Mods.RA.Effects -{ - class InvulnEffect : IEffect - { - Actor a; - IronCurtainable b; - - public InvulnEffect(Actor a) - { - this.a = a; - this.b = a.Trait(); - } - - public void Tick( World world ) - { - if (a.IsDead() || b.GetDamageModifier(null, null) > 0) - world.AddFrameEndTask(w => w.Remove(this)); - } - - public IEnumerable Render(WorldRenderer wr) - { - if (a.Destroyed) // Tick will clean up - yield break; - - foreach (var r in a.Render(wr)) - if (!r.IsDecoration) - yield return r.WithPalette(wr.Palette("invuln")); - } - } -} diff --git a/OpenRA.Mods.RA/InvulnerabilityUpgrade.cs b/OpenRA.Mods.RA/InvulnerabilityUpgrade.cs new file mode 100644 index 0000000000..ffa34449a3 --- /dev/null +++ b/OpenRA.Mods.RA/InvulnerabilityUpgrade.cs @@ -0,0 +1,51 @@ +#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 OpenRA.GameRules; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class InvulnerabilityUpgradeInfo : ITraitInfo + { + public readonly string RequiresUpgrade = "invulnerability"; + + public object Create(ActorInitializer init) { return new InvulnerabilityUpgrade(this); } + } + + public class InvulnerabilityUpgrade : IUpgradable, 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; + } + + public int GetDamageModifier(Actor attacker, DamageWarhead warhead) + { + return enabled ? 0 : 100; + } + } +} diff --git a/OpenRA.Mods.RA/IronCurtainable.cs b/OpenRA.Mods.RA/IronCurtainable.cs deleted file mode 100644 index 8fae33fc2b..0000000000 --- a/OpenRA.Mods.RA/IronCurtainable.cs +++ /dev/null @@ -1,55 +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.Drawing; -using OpenRA.GameRules; -using OpenRA.Mods.RA.Effects; -using OpenRA.Traits; - -namespace OpenRA.Mods.RA -{ - class IronCurtainableInfo : TraitInfo { } - - class IronCurtainable : IDamageModifier, ITick, ISync, ISelectionBar - { - [Sync] int RemainingTicks = 0; - int TotalTicks; - - public void Tick(Actor self) - { - if (RemainingTicks > 0) - RemainingTicks--; - } - - public int GetDamageModifier(Actor attacker, DamageWarhead warhead) - { - return RemainingTicks > 0 ? 0 : 100; - } - - public void Activate(Actor self, int duration) - { - if (RemainingTicks == 0) - self.World.AddFrameEndTask(w => w.Add(new InvulnEffect(self))); // do not stack the invuln effect - - RemainingTicks = duration; - TotalTicks = duration; - } - - // Show the remaining time as a bar - public float GetValue() - { - if (RemainingTicks == 0) // otherwise an empty bar is rendered all the time - return 0f; - - return (float)RemainingTicks / TotalTicks; - } - public Color GetColor() { return Color.Red; } - } -} \ No newline at end of file diff --git a/OpenRA.Mods.RA/Modifiers/UpgradeOverlay.cs b/OpenRA.Mods.RA/Modifiers/UpgradeOverlay.cs new file mode 100644 index 0000000000..2443df4b76 --- /dev/null +++ b/OpenRA.Mods.RA/Modifiers/UpgradeOverlay.cs @@ -0,0 +1,63 @@ +#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.Collections.Generic; +using OpenRA.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + [Desc("Display a colored overlay when a timed upgrade is active.")] + public class UpgradeOverlayInfo : ITraitInfo + { + [Desc("Upgrade that is required before this overlay is rendered")] + public readonly string RequiresUpgrade = null; + + [Desc("Palette to use when rendering the overlay")] + public readonly string Palette = "invuln"; + + public object Create(ActorInitializer init) { return new UpgradeOverlay(this); } + } + + public class UpgradeOverlay : IRenderModifier, IUpgradable + { + readonly UpgradeOverlayInfo info; + bool enabled; + + public UpgradeOverlay(UpgradeOverlayInfo info) + { + this.info = info; + } + + public IEnumerable ModifyRender(Actor self, WorldRenderer wr, IEnumerable r) + { + foreach (var a in r) + { + yield return a; + + if (enabled && !a.IsDecoration) + yield return a.WithPalette(wr.Palette(info.Palette)) + .WithZOffset(a.ZOffset + 1) + .AsDecoration(); + } + } + + public bool AcceptsUpgrade(string type) + { + return type == info.RequiresUpgrade; + } + + public void UpgradeAvailable(Actor self, string type, bool available) + { + if (type == info.RequiresUpgrade) + enabled = available; + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index cd7ccb0fed..ca87819c27 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -220,7 +220,6 @@ - @@ -245,7 +244,6 @@ - @@ -359,7 +357,6 @@ - diff --git a/OpenRA.Mods.RA/SupportPowers/IronCurtainPower.cs b/OpenRA.Mods.RA/SupportPowers/GrantUpgradePower.cs similarity index 66% rename from OpenRA.Mods.RA/SupportPowers/IronCurtainPower.cs rename to OpenRA.Mods.RA/SupportPowers/GrantUpgradePower.cs index e0ba5b4003..c2003e6fc9 100644 --- a/OpenRA.Mods.RA/SupportPowers/IronCurtainPower.cs +++ b/OpenRA.Mods.RA/SupportPowers/GrantUpgradePower.cs @@ -17,22 +17,27 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA { - class IronCurtainPowerInfo : SupportPowerInfo + class GrantUpgradePowerInfo : SupportPowerInfo { - [Desc("Seconds")] - public readonly int Duration = 10; + [Desc("The upgrades to apply.")] + public readonly string[] Upgrades = { }; + + [Desc("Duration of the upgrade (in ticks). Set to 0 for a permanent upgrade.")] + public readonly int Duration = 0; + [Desc("Cells")] public readonly int Range = 1; - public readonly string IronCurtainSound = "ironcur9.aud"; + public readonly string GrantUpgradeSound = "ironcur9.aud"; - public override object Create(ActorInitializer init) { return new IronCurtainPower(init.self, this); } + public override object Create(ActorInitializer init) { return new GrantUpgradePower(init.self, this); } } - class IronCurtainPower : SupportPower + class GrantUpgradePower : SupportPower { - IronCurtainPowerInfo info; + GrantUpgradePowerInfo info; - public IronCurtainPower(Actor self, IronCurtainPowerInfo info) : base(self, info) + public GrantUpgradePower(Actor self, GrantUpgradePowerInfo info) + : base(self, info) { this.info = info; } @@ -49,38 +54,59 @@ namespace OpenRA.Mods.RA self.Trait().PlayCustomAnim(self, "active"); - Sound.Play(info.IronCurtainSound, self.World.Map.CenterOfCell(order.TargetLocation)); + Sound.Play(info.GrantUpgradeSound, self.World.Map.CenterOfCell(order.TargetLocation)); - foreach (var target in UnitsInRange(order.TargetLocation) - .Where(a => a.Owner.Stances[self.Owner] == Stance.Ally)) - target.Trait().Activate(target, ((IronCurtainPowerInfo)Info).Duration * 25); + foreach (var a in UnitsInRange(order.TargetLocation)) + { + var um = a.TraitOrDefault(); + if (um == null) + continue; + + foreach (var u in info.Upgrades) + { + if (!um.AcceptsUpgrade(a, u)) + continue; + + if (info.Duration > 0) + um.GrantTimedUpgrade(a, u, info.Duration); + else + um.GrantUpgrade(a, u, this); + } + } } public IEnumerable UnitsInRange(CPos xy) { - var range = ((IronCurtainPowerInfo)Info).Range; + var range = info.Range; var tiles = self.World.Map.FindTilesInCircle(xy, range); var units = new List(); foreach (var t in tiles) units.AddRange(self.World.ActorMap.GetUnitsAt(t)); - return units.Distinct().Where(a => a.HasTrait()); + return units.Distinct().Where(a => + { + if (!a.Owner.IsAlliedWith(self.Owner)) + return false; + + var um = a.TraitOrDefault(); + return um != null && info.Upgrades.Any(u => um.AcceptsUpgrade(a, u)); + }); } class SelectTarget : IOrderGenerator { - readonly IronCurtainPower power; + readonly GrantUpgradePower power; readonly int range; readonly Sprite tile; readonly SupportPowerManager manager; readonly string order; - public SelectTarget(World world, string order, SupportPowerManager manager, IronCurtainPower power) + public SelectTarget(World world, string order, SupportPowerManager manager, GrantUpgradePower power) { this.manager = manager; this.order = order; this.power = power; - this.range = ((IronCurtainPowerInfo)power.Info).Range; + this.range = power.info.Range; tile = world.Map.SequenceProvider.GetSequence("overlay", "target-select").GetSprite(0); } @@ -101,8 +127,7 @@ namespace OpenRA.Mods.RA public IEnumerable RenderAfterWorld(WorldRenderer wr, World world) { var xy = wr.Viewport.ViewToWorld(Viewport.LastMousePos); - var targetUnits = power.UnitsInRange(xy).Where(a => a.Owner.Stances[power.self.Owner] == Stance.Ally); - foreach (var unit in targetUnits) + foreach (var unit in power.UnitsInRange(xy)) yield return new SelectionBoxRenderable(unit, Color.Red); } @@ -117,7 +142,7 @@ namespace OpenRA.Mods.RA public string GetCursor(World world, CPos xy, MouseInput mi) { - return power.UnitsInRange(xy).Any(a => a.Owner.Stances[power.self.Owner] == Stance.Ally) ? "ability" : "move-blocked"; + return power.UnitsInRange(xy).Any() ? "ability" : "move-blocked"; } } } diff --git a/mods/ra/maps/bomber-john/map.yaml b/mods/ra/maps/bomber-john/map.yaml index abcccc4bb3..3434f0f84f 100644 --- a/mods/ra/maps/bomber-john/map.yaml +++ b/mods/ra/maps/bomber-john/map.yaml @@ -862,16 +862,17 @@ Rules: Duration: 999999 KillCargo: yes Range: 3 - IronCurtainPower: + GrantUpgradePower@IRONCURTAIN: Icon: invuln ChargeTime: 30 Description: Invulnerability LongDesc: Makes a unit invulnerable\nfor 3 seconds. - Duration: 3 + Duration: 75 SelectTargetSound: slcttgt1.aud BeginChargeSound: ironchg1.aud EndChargeSound: ironrdy1.aud Range: 1 + Upgrades: invulnerability Power: Amount: 0 MINVV: @@ -928,16 +929,17 @@ Rules: Duration: 999999 KillCargo: yes Range: 3 - IronCurtainPower: + GrantUpgradePower@IRONCURTAIN: Icon: invuln ChargeTime: 30 Description: Invulnerability LongDesc: Makes a unit invulnerable\nfor 3 seconds. - Duration: 3 + Duration: 75 SelectTargetSound: slcttgt1.aud BeginChargeSound: ironchg1.aud EndChargeSound: ironrdy1.aud Range: 1 + Upgrades: invulnerability Sequences: miner: diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 53158a0255..4d251eb0d3 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -20,7 +20,6 @@ Chronoshiftable: Passenger: CargoType: Vehicle - IronCurtainable: AttackMove: HiddenUnderFog: GainsExperience: @@ -61,6 +60,12 @@ DamageCooldown: 125 RequiresUpgrade: selfheal UpgradeManager: + UpgradeOverlay@IRONCURTAIN: + RequiresUpgrade: invulnerability + InvulnerabilityUpgrade@IRONCURTAIN: + RequiresUpgrade: invulnerability + TimedUpgradeBar: + Upgrade: invulnerability ^Tank: AppearsOnRadar: @@ -84,7 +89,6 @@ Chronoshiftable: Passenger: CargoType: Vehicle - IronCurtainable: AttackMove: HiddenUnderFog: GainsExperience: @@ -125,6 +129,12 @@ DamageCooldown: 125 RequiresUpgrade: selfheal UpgradeManager: + UpgradeOverlay@IRONCURTAIN: + RequiresUpgrade: invulnerability + InvulnerabilityUpgrade@IRONCURTAIN: + RequiresUpgrade: invulnerability + TimedUpgradeBar: + Upgrade: invulnerability ^Infantry: AppearsOnRadar: @@ -250,8 +260,13 @@ HealIfBelow: 1 DamageCooldown: 125 RequiresUpgrade: selfheal - IronCurtainable: UpgradeManager: + UpgradeOverlay@IRONCURTAIN: + RequiresUpgrade: invulnerability + InvulnerabilityUpgrade@IRONCURTAIN: + RequiresUpgrade: invulnerability + TimedUpgradeBar: + Upgrade: invulnerability ^Plane: AppearsOnRadar: @@ -295,8 +310,13 @@ HealIfBelow: 1 DamageCooldown: 125 RequiresUpgrade: selfheal - IronCurtainable: UpgradeManager: + UpgradeOverlay@IRONCURTAIN: + RequiresUpgrade: invulnerability + InvulnerabilityUpgrade@IRONCURTAIN: + RequiresUpgrade: invulnerability + TimedUpgradeBar: + Upgrade: invulnerability ^Helicopter: Inherits: ^Plane @@ -352,8 +372,13 @@ LuaScriptEvents: Demolishable: ScriptTriggers: - IronCurtainable: UpgradeManager: + UpgradeOverlay@IRONCURTAIN: + RequiresUpgrade: invulnerability + InvulnerabilityUpgrade@IRONCURTAIN: + RequiresUpgrade: invulnerability + TimedUpgradeBar: + Upgrade: invulnerability ^Defense: Inherits: ^Building diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml index 363809b66e..02266ff5c2 100644 --- a/mods/ra/rules/structures.yaml +++ b/mods/ra/rules/structures.yaml @@ -213,17 +213,18 @@ IRON: Range: 10c0 Bib: HasMinibib: Yes - IronCurtainPower: + GrantUpgradePower@IRONCURTAIN: Icon: invuln ChargeTime: 120 Description: Invulnerability LongDesc: Makes a group of units invulnerable\nfor 20 seconds. - Duration: 20 + Duration: 500 SelectTargetSound: slcttgt1.aud InsufficientPowerSound: nopowr1.aud BeginChargeSound: ironchg1.aud EndChargeSound: ironrdy1.aud DisplayRadarPing: True + Upgrades: invulnerability SupportPowerChargeBar: Power: Amount: -200 diff --git a/mods/ra/rules/vehicles.yaml b/mods/ra/rules/vehicles.yaml index c804276bf6..6e4e7752c1 100644 --- a/mods/ra/rules/vehicles.yaml +++ b/mods/ra/rules/vehicles.yaml @@ -651,7 +651,7 @@ DTRK: Weapon: MiniNuke EmptyWeapon: MiniNuke DemoTruck: - -IronCurtainable: + -InvulnerabilityUpgrade@IRONCURTAIN: Chronoshiftable: ExplodeInstead: yes