From 78d9e89cfce3e922e81866e38ffffe000a49ac01 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 26 Aug 2019 10:04:53 +0100 Subject: [PATCH 1/5] Fix DamageModifier crashes when an actor is demolished. Demolish calls GetDamageModifier with a null Damage. --- OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs | 2 +- OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs b/OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs index bc620d2e06d5..59331fc5cd2e 100644 --- a/OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs +++ b/OpenRA.Mods.Common/Traits/Infantry/TakeCover.cs @@ -92,7 +92,7 @@ int IDamageModifier.GetDamageModifier(Actor attacker, Damage damage) if (!IsProne) return 100; - if (damage.DamageTypes.IsEmpty) + if (damage == null || damage.DamageTypes.IsEmpty) return 100; var modifierPercentages = info.DamageModifiers.Where(x => damage.DamageTypes.Contains(x.Key)).Select(x => x.Value); diff --git a/OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs b/OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs index e02073b00813..f822ffdadd20 100644 --- a/OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs +++ b/OpenRA.Mods.Common/Traits/Infantry/TerrainModifiesDamage.cs @@ -42,7 +42,7 @@ public TerrainModifiesDamage(Actor self, TerrainModifiesDamageInfo info) int IDamageModifier.GetDamageModifier(Actor attacker, Damage damage) { - if (attacker.Owner.IsAlliedWith(self.Owner) && damage.Value < 0 && !Info.ModifyHealing) + if (!Info.ModifyHealing && attacker.Owner.IsAlliedWith(self.Owner) && damage != null && damage.Value < 0) return FullDamage; var world = self.World; From 916fc8fa898c710467eaad5f89bb1a66b4a6e8c1 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 7 Oct 2018 15:02:04 +0000 Subject: [PATCH 2/5] Add support for custom SupportPowerInstances. --- .../Traits/SupportPowers/SupportPower.cs | 5 ++ .../SupportPowers/SupportPowerManager.cs | 46 +++++++++---------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs index b63fd3096b14..1fdda61d1dd9 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs @@ -125,6 +125,11 @@ public SupportPower(Actor self, SupportPowerInfo info) this.info = info; } + public virtual SupportPowerInstance CreateInstance(string key, SupportPowerManager manager) + { + return new SupportPowerInstance(key, info, manager); + } + public virtual void Charging(Actor self, string key) { Game.Sound.PlayToPlayer(SoundType.UI, self.Owner, Info.BeginChargeSound); diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs index d969bedcee1b..5a82e2803d19 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs @@ -60,12 +60,7 @@ void ActorAdded(Actor a) if (!Powers.ContainsKey(key)) { - Powers.Add(key, new SupportPowerInstance(key, this) - { - Instances = new List(), - RemainingTime = t.Info.StartFullyCharged ? 0 : t.Info.ChargeInterval, - TotalTime = t.Info.ChargeInterval, - }); + Powers.Add(key, t.CreateInstance(key, this)); if (t.Info.Prerequisites.Any()) { @@ -145,7 +140,6 @@ public void PrerequisitesUnavailable(string key) return; sp.PrerequisitesAvailable(false); - sp.RemainingTime = sp.TotalTime; } public void PrerequisitesItemHidden(string key) { } @@ -154,15 +148,15 @@ public void PrerequisitesUnavailable(string key) public class SupportPowerInstance { - readonly SupportPowerManager manager; + protected readonly SupportPowerManager Manager; public readonly string Key; - public List Instances; - public int RemainingTime; - public int TotalTime; + public readonly List Instances = new List(); + public readonly int TotalTime; + public int RemainingTime { get; private set; } public bool Active { get; private set; } - public bool Disabled { get { return (!prereqsAvailable && !manager.DevMode.AllTech) || !instancesEnabled || oneShotFired; } } + public bool Disabled { get { return (!prereqsAvailable && !Manager.DevMode.AllTech) || !instancesEnabled || oneShotFired; } } public SupportPowerInfo Info { get { return Instances.Select(i => i.Info).FirstOrDefault(); } } public bool Ready { get { return Active && RemainingTime == 0; } } @@ -170,21 +164,27 @@ public class SupportPowerInstance bool instancesEnabled; bool prereqsAvailable = true; bool oneShotFired; + bool notifiedCharging; + bool notifiedReady; - public SupportPowerInstance(string key, SupportPowerManager manager) + public SupportPowerInstance(string key, SupportPowerInfo info, SupportPowerManager manager) { - this.manager = manager; Key = key; + TotalTime = info.ChargeInterval; + RemainingTime = info.StartFullyCharged ? 0 : info.ChargeInterval; + + Manager = manager; } - public void PrerequisitesAvailable(bool available) + public virtual void PrerequisitesAvailable(bool available) { prereqsAvailable = available; + + if (!available) + RemainingTime = TotalTime; } - bool notifiedCharging; - bool notifiedReady; - public void Tick() + public virtual void Tick() { instancesEnabled = Instances.Any(i => !i.IsTraitDisabled); if (!instancesEnabled) @@ -197,7 +197,7 @@ public void Tick() if (Active) { var power = Instances.First(); - if (manager.DevMode.FastCharge && RemainingTime > 25) + if (Manager.DevMode.FastCharge && RemainingTime > 25) RemainingTime = 25; if (RemainingTime > 0) @@ -217,7 +217,7 @@ public void Tick() } } - public void Target() + public virtual void Target() { if (!Ready) return; @@ -226,10 +226,10 @@ public void Target() if (power == null) return; - power.SelectTarget(power.Self, Key, manager); + power.SelectTarget(power.Self, Key, Manager); } - public void Activate(Order order) + public virtual void Activate(Order order) { if (!Ready) return; @@ -247,7 +247,7 @@ public void Activate(Order order) return; // Note: order.Subject is the *player* actor - power.Activate(power.Self, order, manager); + power.Activate(power.Self, order, Manager); RemainingTime = TotalTime; notifiedCharging = notifiedReady = false; From a26404e248f6bc09c7a304270b382cbb1af98885 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 7 Oct 2018 15:03:19 +0000 Subject: [PATCH 3/5] Rename *Time to *Ticks and increase internal resolution. --- .../Traits/Render/SupportPowerChargeBar.cs | 2 +- .../SupportPowers/SupportPowerManager.cs | 29 ++++++++++--------- .../Logic/Ingame/SupportPowerTooltipLogic.cs | 4 +-- .../ObserverSupportPowerIconsWidget.cs | 6 ++-- .../Widgets/SupportPowerTimerWidget.cs | 2 +- .../Widgets/SupportPowersWidget.cs | 6 ++-- 6 files changed, 26 insertions(+), 23 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/Render/SupportPowerChargeBar.cs b/OpenRA.Mods.Common/Traits/Render/SupportPowerChargeBar.cs index c023c9843a0a..946a4314b171 100644 --- a/OpenRA.Mods.Common/Traits/Render/SupportPowerChargeBar.cs +++ b/OpenRA.Mods.Common/Traits/Render/SupportPowerChargeBar.cs @@ -51,7 +51,7 @@ float ISelectionBar.GetValue() if (viewer != null && !Info.DisplayStances.HasStance(self.Owner.Stances[viewer])) return 0; - return 1 - (float)power.RemainingTime / power.TotalTime; + return 1 - (float)power.RemainingTicks / power.TotalTicks; } Color ISelectionBar.GetColor() { return Info.Color; } diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs index 5a82e2803d19..22c129f842df 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs @@ -153,13 +153,15 @@ public class SupportPowerInstance public readonly string Key; public readonly List Instances = new List(); - public readonly int TotalTime; - public int RemainingTime { get; private set; } + public readonly int TotalTicks; + + protected int remainingSubTicks; + public int RemainingTicks { get { return remainingSubTicks / 100; } } public bool Active { get; private set; } public bool Disabled { get { return (!prereqsAvailable && !Manager.DevMode.AllTech) || !instancesEnabled || oneShotFired; } } public SupportPowerInfo Info { get { return Instances.Select(i => i.Info).FirstOrDefault(); } } - public bool Ready { get { return Active && RemainingTime == 0; } } + public bool Ready { get { return Active && RemainingTicks == 0; } } bool instancesEnabled; bool prereqsAvailable = true; @@ -170,8 +172,8 @@ public class SupportPowerInstance public SupportPowerInstance(string key, SupportPowerInfo info, SupportPowerManager manager) { Key = key; - TotalTime = info.ChargeInterval; - RemainingTime = info.StartFullyCharged ? 0 : info.ChargeInterval; + TotalTicks = info.ChargeInterval; + remainingSubTicks = info.StartFullyCharged ? 0 : TotalTicks * 100; Manager = manager; } @@ -181,14 +183,14 @@ public virtual void PrerequisitesAvailable(bool available) prereqsAvailable = available; if (!available) - RemainingTime = TotalTime; + remainingSubTicks = TotalTicks * 100; } public virtual void Tick() { instancesEnabled = Instances.Any(i => !i.IsTraitDisabled); if (!instancesEnabled) - RemainingTime = TotalTime; + remainingSubTicks = TotalTicks * 100; Active = !Disabled && Instances.Any(i => !i.IsTraitPaused); if (!Active) @@ -197,18 +199,19 @@ public virtual void Tick() if (Active) { var power = Instances.First(); - if (Manager.DevMode.FastCharge && RemainingTime > 25) - RemainingTime = 25; + if (Manager.DevMode.FastCharge && remainingSubTicks > 2500) + remainingSubTicks = 2500; + + if (remainingSubTicks > 0) + remainingSubTicks = (remainingSubTicks - 100).Clamp(0, TotalTicks * 100); - if (RemainingTime > 0) - --RemainingTime; if (!notifiedCharging) { power.Charging(power.Self, Key); notifiedCharging = true; } - if (RemainingTime == 0 + if (RemainingTicks == 0 && !notifiedReady) { power.Charged(power.Self, Key); @@ -248,7 +251,7 @@ public virtual void Activate(Order order) // Note: order.Subject is the *player* actor power.Activate(power.Self, order, Manager); - RemainingTime = TotalTime; + remainingSubTicks = TotalTicks * 100; notifiedCharging = notifiedReady = false; if (Info.OneShot) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/SupportPowerTooltipLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/SupportPowerTooltipLogic.cs index 598c2eb846c7..73155902e5ea 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/SupportPowerTooltipLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/SupportPowerTooltipLogic.cs @@ -47,7 +47,7 @@ public SupportPowerTooltipLogic(Widget widget, TooltipContainerWidget tooltipCon // HACK: This abuses knowledge of the internals of WidgetUtils.FormatTime // to efficiently work when the label is going to change, requiring a panel relayout - var remainingSeconds = (int)Math.Ceiling(sp.RemainingTime * world.Timestep / 1000f); + var remainingSeconds = (int)Math.Ceiling(sp.RemainingTicks * world.Timestep / 1000f); var hotkey = icon.Hotkey != null ? icon.Hotkey.GetValue() : Hotkey.Invalid; if (sp == lastPower && hotkey == lastHotkey && lastRemainingSeconds == remainingSeconds) @@ -59,7 +59,7 @@ public SupportPowerTooltipLogic(Widget widget, TooltipContainerWidget tooltipCon descLabel.Text = sp.Info.LongDesc.Replace("\\n", "\n"); var descSize = descFont.Measure(descLabel.Text); - var remaining = WidgetUtils.FormatTime(sp.RemainingTime, world.Timestep); + var remaining = WidgetUtils.FormatTime(sp.RemainingTicks, world.Timestep); var total = WidgetUtils.FormatTime(sp.Info.ChargeInterval, world.Timestep); timeLabel.Text = "{0} / {1}".F(remaining, total); var timeSize = timeFont.Measure(timeLabel.Text); diff --git a/OpenRA.Mods.Common/Widgets/ObserverSupportPowerIconsWidget.cs b/OpenRA.Mods.Common/Widgets/ObserverSupportPowerIconsWidget.cs index c151a692baac..83fb806f0f49 100644 --- a/OpenRA.Mods.Common/Widgets/ObserverSupportPowerIconsWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ObserverSupportPowerIconsWidget.cs @@ -132,8 +132,8 @@ public override void Draw() var clock = clocks[power.a.Key]; clock.PlayFetchIndex(ClockSequence, - () => item.TotalTime == 0 ? 0 : ((item.TotalTime - item.RemainingTime) - * (clock.CurrentSequence.Length - 1) / item.TotalTime)); + () => item.TotalTicks == 0 ? 0 : ((item.TotalTicks - item.RemainingTicks) + * (clock.CurrentSequence.Length - 1) / item.TotalTicks)); clock.Tick(); WidgetUtils.DrawSHPCentered(clock.Image, location + 0.5f * iconSize, worldRenderer.Palette(ClockPalette), 0.5f); @@ -149,7 +149,7 @@ static string GetOverlayForItem(SupportPowerInstance item, int timestep) { if (item.Disabled) return "ON HOLD"; if (item.Ready) return "READY"; - return WidgetUtils.FormatTime(item.RemainingTime, timestep); + return WidgetUtils.FormatTime(item.RemainingTicks, timestep); } public override Widget Clone() diff --git a/OpenRA.Mods.Common/Widgets/SupportPowerTimerWidget.cs b/OpenRA.Mods.Common/Widgets/SupportPowerTimerWidget.cs index dad9a69fd8c2..89f8e0e882da 100644 --- a/OpenRA.Mods.Common/Widgets/SupportPowerTimerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/SupportPowerTimerWidget.cs @@ -57,7 +57,7 @@ public override void Tick() texts = displayedPowers.Select(p => { - var time = WidgetUtils.FormatTime(p.RemainingTime, false, timestep); + var time = WidgetUtils.FormatTime(p.RemainingTicks, false, timestep); var text = Format.F(p.Info.Description, time); var self = p.Instances[0].Self; var playerColor = self.Owner.Color; diff --git a/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs b/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs index a6f1720e9452..3c32031da400 100644 --- a/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs +++ b/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs @@ -205,8 +205,8 @@ public override void Draw() // Charge progress var sp = p.Power; clock.PlayFetchIndex(ClockSequence, - () => sp.TotalTime == 0 ? clock.CurrentSequence.Length - 1 : (sp.TotalTime - sp.RemainingTime) - * (clock.CurrentSequence.Length - 1) / sp.TotalTime); + () => sp.TotalTicks == 0 ? clock.CurrentSequence.Length - 1 : (sp.TotalTicks - sp.RemainingTicks) + * (clock.CurrentSequence.Length - 1) / sp.TotalTicks); clock.Tick(); WidgetUtils.DrawSHPCentered(clock.Image, p.Pos + iconOffset, p.IconClockPalette); @@ -224,7 +224,7 @@ public override void Draw() p.Pos + holdOffset, Color.White, Color.Black, 1); else - overlayFont.DrawTextWithContrast(WidgetUtils.FormatTime(p.Power.RemainingTime, worldRenderer.World.Timestep), + overlayFont.DrawTextWithContrast(WidgetUtils.FormatTime(p.Power.RemainingTicks, worldRenderer.World.Timestep), p.Pos + timeOffset, Color.White, Color.Black, 1); } From a74ff921ad5ff19d7aaebaa514eb44c5ac252175 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Tue, 9 Oct 2018 19:28:02 +0100 Subject: [PATCH 4/5] Allow support powers to override the icon overlay/tooltip labels. --- .../Traits/SupportPowers/SupportPowerManager.cs | 17 ++++++++++------- .../Logic/Ingame/SupportPowerTooltipLogic.cs | 14 ++++++++++---- .../Widgets/SupportPowersWidget.cs | 10 +++++++++- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs index 22c129f842df..19a7b7dc8e30 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs @@ -105,13 +105,6 @@ public void ResolveOrder(Actor self, Order order) Powers[order.OrderString].Activate(order); } - // Deprecated. Remove after SupportPowerBinWidget is removed. - public void Target(string key) - { - if (Powers.ContainsKey(key)) - Powers[key].Target(); - } - static readonly SupportPowerInstance[] NoInstances = { }; public IEnumerable GetPowersForActor(Actor a) @@ -260,6 +253,16 @@ public virtual void Activate(Order order) oneShotFired = true; } } + + public virtual string IconOverlayTextOverride() + { + return null; + } + + public virtual string TooltipTimeTextOverride() + { + return null; + } } public class SelectGenericPowerTarget : OrderGenerator diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/SupportPowerTooltipLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/SupportPowerTooltipLogic.cs index 73155902e5ea..698c4cd29571 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/SupportPowerTooltipLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/SupportPowerTooltipLogic.cs @@ -59,11 +59,17 @@ public SupportPowerTooltipLogic(Widget widget, TooltipContainerWidget tooltipCon descLabel.Text = sp.Info.LongDesc.Replace("\\n", "\n"); var descSize = descFont.Measure(descLabel.Text); - var remaining = WidgetUtils.FormatTime(sp.RemainingTicks, world.Timestep); - var total = WidgetUtils.FormatTime(sp.Info.ChargeInterval, world.Timestep); - timeLabel.Text = "{0} / {1}".F(remaining, total); - var timeSize = timeFont.Measure(timeLabel.Text); + var customLabel = sp.TooltipTimeTextOverride(); + if (customLabel == null) + { + var remaining = WidgetUtils.FormatTime(sp.RemainingTicks, world.Timestep); + var total = WidgetUtils.FormatTime(sp.Info.ChargeInterval, world.Timestep); + timeLabel.Text = "{0} / {1}".F(remaining, total); + } + else + timeLabel.Text = customLabel; + var timeSize = timeFont.Measure(timeLabel.Text); var hotkeyWidth = 0; hotkeyLabel.Visible = hotkey.IsValid(); if (hotkeyLabel.Visible) diff --git a/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs b/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs index 3c32031da400..dc080fd7f4ea 100644 --- a/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs +++ b/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs @@ -215,7 +215,15 @@ public override void Draw() // Overlay foreach (var p in icons.Values) { - if (p.Power.Ready) + var customText = p.Power.IconOverlayTextOverride(); + if (customText != null) + { + var customOffset = iconOffset - overlayFont.Measure(customText) / 2; + overlayFont.DrawTextWithContrast(customText, + p.Pos + customOffset, + Color.White, Color.Black, 1); + } + else if (p.Power.Ready) overlayFont.DrawTextWithContrast(ReadyText, p.Pos + readyOffset, Color.White, Color.Black, 1); From 9409d47684b0964758ba09c7dcba5c0adecdd411 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 26 Aug 2019 10:04:39 +0100 Subject: [PATCH 5/5] Add GrantPrerequisiteChargeDrainPower and DrainPrerequisitePowerOnDamage. These traits implement the Firestorm defense charge/drain logic. --- .../Traits/DrainPrerequisitePowerOnDamage.cs | 68 ++++++ .../GrantPrerequisiteChargeDrainPower.cs | 211 ++++++++++++++++++ .../SupportPowers/SupportPowerManager.cs | 2 +- 3 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 OpenRA.Mods.Cnc/Traits/DrainPrerequisitePowerOnDamage.cs create mode 100644 OpenRA.Mods.Cnc/Traits/SupportPowers/GrantPrerequisiteChargeDrainPower.cs diff --git a/OpenRA.Mods.Cnc/Traits/DrainPrerequisitePowerOnDamage.cs b/OpenRA.Mods.Cnc/Traits/DrainPrerequisitePowerOnDamage.cs new file mode 100644 index 000000000000..c600a35f1e8a --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/DrainPrerequisitePowerOnDamage.cs @@ -0,0 +1,68 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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 OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + [Desc("Converts damage to a charge level of a GrantPrerequisiteChargeDrainPower.")] + public class DrainPrerequisitePowerOnDamageInfo : ConditionalTraitInfo + { + [Desc("The OrderName of the GrantPrerequisiteChargeDrainPower to drain.")] + public readonly string OrderName = "GrantPrerequisiteChargeDrainPowerInfoOrder"; + + [Desc("Damage is multiplied by this number when converting damage to drain ticks.")] + public readonly int DamageMultiplier = 1; + + [Desc("Damage is divided by this number when converting damage to drain ticks.")] + public readonly int DamageDivisor = 600; + + public override object Create(ActorInitializer init) { return new DrainPrerequisitePowerOnDamage(init.Self, this); } + } + + public class DrainPrerequisitePowerOnDamage : ConditionalTrait, INotifyOwnerChanged, IDamageModifier + { + SupportPowerManager spm; + + public DrainPrerequisitePowerOnDamage(Actor self, DrainPrerequisitePowerOnDamageInfo info) + : base(info) { } + + protected override void Created(Actor self) + { + base.Created(self); + spm = self.Owner.PlayerActor.Trait(); + } + + void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) + { + spm = newOwner.PlayerActor.Trait(); + } + + int IDamageModifier.GetDamageModifier(Actor self, Damage damage) + { + if (!IsTraitDisabled && damage != null) + { + var damageSubTicks = (int)(damage.Value * 100L * Info.DamageMultiplier / Info.DamageDivisor); + + SupportPowerInstance spi; + if (spm.Powers.TryGetValue(Info.OrderName, out spi)) + { + var dspi = spi as GrantPrerequisiteChargeDrainPower.DischargeableSupportPowerInstance; + if (dspi != null) + dspi.Discharge(damageSubTicks); + } + } + + return 100; + } + } +} diff --git a/OpenRA.Mods.Cnc/Traits/SupportPowers/GrantPrerequisiteChargeDrainPower.cs b/OpenRA.Mods.Cnc/Traits/SupportPowers/GrantPrerequisiteChargeDrainPower.cs new file mode 100644 index 000000000000..9285a7144e46 --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/SupportPowers/GrantPrerequisiteChargeDrainPower.cs @@ -0,0 +1,211 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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.Collections.Generic; +using System.Linq; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + [Desc("Grants a prerequisite while discharging at a configurable rate.")] + public class GrantPrerequisiteChargeDrainPowerInfo : SupportPowerInfo, ITechTreePrerequisiteInfo + { + [Desc("Rate at which the power discharges compared to charging")] + public readonly int DischargeModifier = 300; + + [FieldLoader.Require] + [Desc("The prerequisite type that this provides.")] + public readonly string Prerequisite = null; + + [Translate] + [Desc("Label to display over the support power icon and in its tooltip while the power is active.")] + public readonly string ActiveText = "ACTIVE"; + + [Translate] + [Desc("Label to display over the support power icon and in its tooltip while the power is available but not active.")] + public readonly string AvailableText = "READY"; + + IEnumerable ITechTreePrerequisiteInfo.Prerequisites(ActorInfo info) + { + yield return Prerequisite; + } + + public override object Create(ActorInitializer init) { return new GrantPrerequisiteChargeDrainPower(init.Self, this); } + } + + public class GrantPrerequisiteChargeDrainPower : SupportPower, ITechTreePrerequisite, INotifyOwnerChanged + { + readonly GrantPrerequisiteChargeDrainPowerInfo info; + TechTree techTree; + bool active; + + public GrantPrerequisiteChargeDrainPower(Actor self, GrantPrerequisiteChargeDrainPowerInfo info) + : base(self, info) + { + this.info = info; + } + + protected override void Created(Actor self) + { + // Special case handling is required for the Player actor. + // Created is called before Player.PlayerActor is assigned, + // so we must query other player traits from self, knowing that + // it refers to the same actor as self.Owner.PlayerActor + var playerActor = self.Info.Name == "player" ? self : self.Owner.PlayerActor; + + techTree = playerActor.Trait(); + + base.Created(self); + } + + void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) + { + techTree = newOwner.PlayerActor.Trait(); + active = false; + } + + public override SupportPowerInstance CreateInstance(string key, SupportPowerManager manager) + { + return new DischargeableSupportPowerInstance(key, info, manager); + } + + public void Activate(Actor self, SupportPowerInstance instance) + { + active = true; + techTree.ActorChanged(self); + } + + public void Deactivate(Actor self, SupportPowerInstance instance) + { + active = false; + techTree.ActorChanged(self); + } + + IEnumerable ITechTreePrerequisite.ProvidesPrerequisites + { + get + { + if (!active) + yield break; + + yield return info.Prerequisite; + } + } + + public class DischargeableSupportPowerInstance : SupportPowerInstance + { + // Whether the power is available to activate (even if not fully charged) + bool available; + + // Whether the power is active right now + // Note that this is fundamentally different to SupportPowerInstance.Active + // which has a much closer meaning to available above. + bool active; + + // Additional discharge rate accrued from damage + int additionalDischargeSubTicks = 0; + + public DischargeableSupportPowerInstance(string key, GrantPrerequisiteChargeDrainPowerInfo info, SupportPowerManager manager) + : base(key, info, manager) { } + + void Deactivate() + { + active = false; + notifiedCharging = false; + + // Fully depleting the charge disables the power until it is again fully charged + if (!Active || remainingSubTicks >= TotalTicks * 100) + available = false; + + foreach (var p in Instances) + ((GrantPrerequisiteChargeDrainPower)p).Deactivate(p.Self, this); + } + + public override void Tick() + { + var orig = remainingSubTicks; + base.Tick(); + + if (Ready) + available = true; + + if (active && !Active) + Deactivate(); + + if (active) + { + remainingSubTicks = orig + ((GrantPrerequisiteChargeDrainPowerInfo)Info).DischargeModifier + additionalDischargeSubTicks; + additionalDischargeSubTicks = 0; + + if (remainingSubTicks > TotalTicks * 100) + { + remainingSubTicks = TotalTicks * 100; + Deactivate(); + } + } + } + + public void Discharge(int subTicks) + { + additionalDischargeSubTicks += subTicks; + } + + public override void Target() + { + if (available && Active) + Manager.Self.World.IssueOrder(new Order(Key, Manager.Self, false) { ExtraData = active ? 0U : 1U }); + } + + public override void Activate(Order order) + { + if (active && order.ExtraData == 0) + { + Deactivate(); + return; + } + + if (!available || order.ExtraData != 1) + return; + + var power = Instances.FirstOrDefault(i => !i.IsTraitPaused); + if (power == null) + return; + + active = true; + + // Only play the activation sound once! + power.PlayLaunchSounds(); + + foreach (var p in Instances) + ((GrantPrerequisiteChargeDrainPower)p).Activate(p.Self, this); + } + + public override string IconOverlayTextOverride() + { + var info = Info as GrantPrerequisiteChargeDrainPowerInfo; + if (info == null || !Active) + return null; + + return active ? info.ActiveText : available ? info.AvailableText : null; + } + + public override string TooltipTimeTextOverride() + { + var info = Info as GrantPrerequisiteChargeDrainPowerInfo; + if (info == null || !Active) + return null; + + return active ? info.ActiveText : available ? info.AvailableText : null; + } + } + } +} diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs index 19a7b7dc8e30..30e0909ae162 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs @@ -159,7 +159,7 @@ public class SupportPowerInstance bool instancesEnabled; bool prereqsAvailable = true; bool oneShotFired; - bool notifiedCharging; + protected bool notifiedCharging; bool notifiedReady; public SupportPowerInstance(string key, SupportPowerInfo info, SupportPowerManager manager)