diff --git a/OpenRA.Mods.Cnc/Activities/LayMines.cs b/OpenRA.Mods.Cnc/Activities/LayMines.cs index 56a82d2df2..1329d495ac 100644 --- a/OpenRA.Mods.Cnc/Activities/LayMines.cs +++ b/OpenRA.Mods.Cnc/Activities/LayMines.cs @@ -53,11 +53,11 @@ namespace OpenRA.Mods.Cnc.Activities if (rearmTarget == null) return new Wait(20); - // Add a CloseEnough range of 512 to the Repair activity in order to ensure that we're at the host actor + // Add a CloseEnough range of 512 to the Rearm/Repair activities in order to ensure that we're at the host actor return ActivityUtils.SequenceActivities( new MoveAdjacentTo(self, Target.FromActor(rearmTarget)), movement.MoveTo(self.World.Map.CellContaining(rearmTarget.CenterPosition), rearmTarget), - new Rearm(self), + new Rearm(self, rearmTarget, new WDist(512)), new Repair(self, rearmTarget, new WDist(512)), this); } diff --git a/OpenRA.Mods.Common/Activities/Rearm.cs b/OpenRA.Mods.Common/Activities/Rearm.cs index 418888ae8e..e53b0efe2b 100644 --- a/OpenRA.Mods.Common/Activities/Rearm.cs +++ b/OpenRA.Mods.Common/Activities/Rearm.cs @@ -13,16 +13,20 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; -using OpenRA.Mods.Common.Traits.Render; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { public class Rearm : Activity { + readonly Target host; + readonly WDist closeEnough; readonly AmmoPool[] ammoPools; - public Rearm(Actor self) + public Rearm(Actor self, Actor host, WDist closeEnough) { + this.host = Target.FromActor(host); + this.closeEnough = closeEnough; ammoPools = self.TraitsImplementing().Where(p => !p.AutoReloads).ToArray(); } @@ -33,6 +37,28 @@ namespace OpenRA.Mods.Common.Activities // HACK: this really shouldn't be managed from here foreach (var pool in ammoPools) pool.RemainingTicks = pool.Info.ReloadDelay; + + if (host.Type == TargetType.Invalid) + return; + + foreach (var notify in host.Actor.TraitsImplementing()) + notify.RearmingStarted(host.Actor, self); + } + + protected override void OnLastRun(Actor self) + { + if (host.Type == TargetType.Invalid) + return; + + foreach (var notify in host.Actor.TraitsImplementing()) + notify.RearmingFinished(host.Actor, self); + } + + protected override void OnActorDispose(Actor self) + { + // If the actor died (or will be disposed directly) this tick, Activity.TickOuter won't be ticked again, + // so we need to run OnLastRun directly (otherwise it would be skipped completely). + OnLastRun(self); } public override Activity Tick(Actor self) @@ -40,11 +66,10 @@ namespace OpenRA.Mods.Common.Activities if (IsCanceled) return NextActivity; - // HACK: check if we are on the helipad/airfield/etc. - var hostBuilding = self.World.ActorMap.GetActorsAt(self.Location) - .FirstOrDefault(a => a.Info.HasTraitInfo()); + if (host.Type == TargetType.Invalid) + return NextActivity; - if (hostBuilding == null || !hostBuilding.IsInWorld) + if (closeEnough.LengthSquared > 0 && !host.IsInRange(self.CenterPosition, closeEnough)) return NextActivity; var complete = true; @@ -52,7 +77,7 @@ namespace OpenRA.Mods.Common.Activities { if (!pool.FullAmmo()) { - Reload(self, hostBuilding, pool); + Reload(self, host.Actor, pool); complete = false; } } @@ -60,12 +85,12 @@ namespace OpenRA.Mods.Common.Activities return complete ? NextActivity : this; } - void Reload(Actor self, Actor hostBuilding, AmmoPool ammoPool) + void Reload(Actor self, Actor host, AmmoPool ammoPool) { if (--ammoPool.RemainingTicks <= 0) { - foreach (var host in hostBuilding.TraitsImplementing()) - host.Rearming(hostBuilding, self); + foreach (var notify in host.TraitsImplementing()) + notify.Rearming(host, self); ammoPool.RemainingTicks = ammoPool.Info.ReloadDelay; if (!string.IsNullOrEmpty(ammoPool.Info.RearmSound)) diff --git a/OpenRA.Mods.Common/Activities/Repair.cs b/OpenRA.Mods.Common/Activities/Repair.cs index 7cc87b6e38..27e4b7a719 100644 --- a/OpenRA.Mods.Common/Activities/Repair.cs +++ b/OpenRA.Mods.Common/Activities/Repair.cs @@ -137,5 +137,12 @@ namespace OpenRA.Mods.Common.Activities foreach (var depot in host.Actor.TraitsImplementing()) depot.AfterRepair(host.Actor, self); } + + protected override void OnActorDispose(Actor self) + { + // If the actor died (or will be disposed directly) this tick, Activity.TickOuter won't be ticked again, + // so we need to run OnLastRun directly (otherwise it would be skipped completely). + OnLastRun(self); + } } } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index b82ddb4b87..53827ce162 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -475,8 +475,7 @@ - - + @@ -912,6 +911,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index 2346e28aa9..89326548f9 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -489,7 +489,7 @@ namespace OpenRA.Mods.Common.Traits { var name = a.Info.Name; if (Info.RearmBuildings.Contains(name)) - yield return new Rearm(self); + yield return new Rearm(self, a, WDist.Zero); // The ResupplyAircraft activity guarantees that we're on the helipad if (Info.RepairBuildings.Contains(name)) diff --git a/OpenRA.Mods.Common/Traits/Render/WithRearmAnimation.cs b/OpenRA.Mods.Common/Traits/Render/WithRearmAnimation.cs deleted file mode 100644 index edf429eb81..0000000000 --- a/OpenRA.Mods.Common/Traits/Render/WithRearmAnimation.cs +++ /dev/null @@ -1,58 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2018 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.Linq; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits.Render -{ - [Desc("Replaces the building animation when it rearms a unit.")] - public class WithRearmAnimationInfo : ConditionalTraitInfo, Requires - { - [Desc("Sequence name to use")] - [SequenceReference] public readonly string Sequence = "active"; - - [Desc("Which sprite body to play the animation on.")] - public readonly string Body = "body"; - - public override object Create(ActorInitializer init) { return new WithRearmAnimation(init.Self, this); } - } - - public class WithRearmAnimation : ConditionalTrait, INotifyRearm, INotifyBuildComplete, INotifySold - { - readonly WithSpriteBody spriteBody; - bool buildComplete; - - public WithRearmAnimation(Actor self, WithRearmAnimationInfo info) - : base(info) - { - spriteBody = self.TraitsImplementing().Single(w => w.Info.Name == Info.Body); - } - - void INotifyRearm.Rearming(Actor self, Actor target) - { - if (buildComplete && !IsTraitDisabled) - spriteBody.PlayCustomAnimation(self, Info.Sequence); - } - - void INotifyBuildComplete.BuildingComplete(Actor self) - { - buildComplete = true; - } - - void INotifySold.Selling(Actor self) - { - buildComplete = false; - } - - void INotifySold.Sold(Actor self) { } - } -} \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Render/WithRepairAnimation.cs b/OpenRA.Mods.Common/Traits/Render/WithRepairAnimation.cs deleted file mode 100644 index 9665a94d0c..0000000000 --- a/OpenRA.Mods.Common/Traits/Render/WithRepairAnimation.cs +++ /dev/null @@ -1,62 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2018 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.Linq; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits.Render -{ - [Desc("Replaces the building animation when it repairs a unit.")] - public class WithRepairAnimationInfo : ConditionalTraitInfo, Requires - { - [Desc("Sequence name to use")] - [SequenceReference] public readonly string Sequence = "active"; - - [Desc("Which sprite body to play the animation on.")] - public readonly string Body = "body"; - - public override object Create(ActorInitializer init) { return new WithRepairAnimation(init.Self, this); } - } - - public class WithRepairAnimation : ConditionalTrait, INotifyRepair, INotifyBuildComplete, INotifySold - { - readonly WithSpriteBody spriteBody; - bool buildComplete; - - public WithRepairAnimation(Actor self, WithRepairAnimationInfo info) - : base(info) - { - spriteBody = self.TraitsImplementing().Single(w => w.Info.Name == Info.Body); - } - - void INotifyRepair.BeforeRepair(Actor self, Actor target) { } - - void INotifyRepair.RepairTick(Actor self, Actor target) - { - if (buildComplete && !IsTraitDisabled) - spriteBody.PlayCustomAnimation(self, Info.Sequence); - } - - void INotifyRepair.AfterRepair(Actor self, Actor target) { } - - void INotifyBuildComplete.BuildingComplete(Actor self) - { - buildComplete = true; - } - - void INotifySold.Selling(Actor self) - { - buildComplete = false; - } - - void INotifySold.Sold(Actor self) { } - } -} \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Render/WithResupplyAnimation.cs b/OpenRA.Mods.Common/Traits/Render/WithResupplyAnimation.cs new file mode 100644 index 0000000000..b23e369d5c --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/WithResupplyAnimation.cs @@ -0,0 +1,111 @@ +#region Copyright & License Information +/* + * Copyright 2007-2018 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.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits.Render +{ + [Flags] + public enum ResupplyType + { + Rearm = 1, + Repair = 2 + } + + [Desc("Replaces the default animation when actor resupplies a unit.")] + public class WithResupplyAnimationInfo : ConditionalTraitInfo, Requires + { + [Desc("Sequence name to use")] + [SequenceReference] public readonly string Sequence = "active"; + + [Desc("Which sprite body to play the animation on.")] + public readonly string Body = "body"; + + [Desc("Events leading to the animation getting played. Possible values currently are: Rearm, Repair.")] + public readonly ResupplyType PlayAnimationOn = ResupplyType.Rearm | ResupplyType.Repair; + + public override object Create(ActorInitializer init) { return new WithResupplyAnimation(init.Self, this); } + } + + public class WithResupplyAnimation : ConditionalTrait, INotifyRepair, INotifyRearm, INotifyBuildComplete, INotifySold, ITick + { + readonly WithSpriteBody spriteBody; + bool buildComplete; + bool animPlaying; + bool repairing; + bool rearming; + + public WithResupplyAnimation(Actor self, WithResupplyAnimationInfo info) + : base(info) + { + spriteBody = self.TraitsImplementing().Single(w => w.Info.Name == Info.Body); + } + + void ITick.Tick(Actor self) + { + if (!buildComplete || IsTraitDisabled) + return; + + if (!animPlaying + && ((repairing && Info.PlayAnimationOn.HasFlag(ResupplyType.Repair)) + || (rearming && Info.PlayAnimationOn.HasFlag(ResupplyType.Rearm)))) + { + spriteBody.PlayCustomAnimationRepeating(self, Info.Sequence); + animPlaying = true; + } + else if (animPlaying + && (!repairing || !Info.PlayAnimationOn.HasFlag(ResupplyType.Repair)) + && (!rearming || !Info.PlayAnimationOn.HasFlag(ResupplyType.Rearm))) + { + spriteBody.CancelCustomAnimation(self); + animPlaying = false; + } + } + + void INotifyRepair.BeforeRepair(Actor self, Actor target) + { + repairing = true; + } + + void INotifyRepair.RepairTick(Actor self, Actor target) { } + + void INotifyRepair.AfterRepair(Actor self, Actor target) + { + repairing = false; + } + + void INotifyRearm.RearmingStarted(Actor self, Actor target) + { + rearming = true; + } + + void INotifyRearm.Rearming(Actor self, Actor target) { } + + void INotifyRearm.RearmingFinished(Actor self, Actor target) + { + rearming = false; + } + + void INotifyBuildComplete.BuildingComplete(Actor self) + { + buildComplete = true; + } + + void INotifySold.Selling(Actor self) + { + buildComplete = false; + } + + void INotifySold.Sold(Actor self) { } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Repairable.cs b/OpenRA.Mods.Common/Traits/Repairable.cs index 3b9fd97991..0fcabfca5d 100644 --- a/OpenRA.Mods.Common/Traits/Repairable.cs +++ b/OpenRA.Mods.Common/Traits/Repairable.cs @@ -124,7 +124,7 @@ namespace OpenRA.Mods.Common.Traits // will need to be rewritten anyway, so this is OK for now. self.QueueActivity(movement.MoveTo(self.World.Map.CellContaining(targetActor.CenterPosition), targetActor)); if (CanRearmAt(targetActor) && CanRearm()) - self.QueueActivity(new Rearm(self)); + self.QueueActivity(new Rearm(self, targetActor, new WDist(512))); // Add a CloseEnough range of 512 to ensure we're at the host actor self.QueueActivity(new Repair(self, targetActor, new WDist(512))); diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 88e5cd725a..b9e264f9cb 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -311,7 +311,12 @@ namespace OpenRA.Mods.Common.Traits } [RequireExplicitImplementation] - public interface INotifyRearm { void Rearming(Actor host, Actor other); } + public interface INotifyRearm + { + void RearmingStarted(Actor host, Actor other); + void Rearming(Actor host, Actor other); + void RearmingFinished(Actor host, Actor other); + } [RequireExplicitImplementation] public interface IRenderInfantrySequenceModifier diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20180923/MergeRearmAndRepairAnimation.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/MergeRearmAndRepairAnimation.cs new file mode 100644 index 0000000000..79f7442083 --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/MergeRearmAndRepairAnimation.cs @@ -0,0 +1,107 @@ +#region Copyright & License Information +/* + * Copyright 2007-2018 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; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class MergeRearmAndRepairAnimation : UpdateRule + { + public override string Name { get { return "WithRearmAnimation and WithRepairAnimation were merged to WithResupplyAnimation"; } } + public override string Description + { + get + { + return "The WithRearmAnimation and WithRepairAnimation traits were merged intto a single\n" + + "WithResupplyAnimation trait."; + } + } + + bool displayedMessage; + public override IEnumerable AfterUpdate(ModData modData) + { + var message = "If an actor had both a WithRearmAnimation and a WithRepairAnimation\n" + + "or multiple traits of either type, you may want to check the update results for possible\n" + + "redundant entries.\n"; + + if (!displayedMessage) + yield return message; + + displayedMessage = true; + } + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + { + var rearmAnims = actorNode.ChildrenMatching("WithRearmAnimation"); + var repairAnims = actorNode.ChildrenMatching("WithRepairAnimation"); + var rearmAnimsTotal = rearmAnims.Count(); + var repairAnimsTotal = repairAnims.Count(); + + if (rearmAnimsTotal == 0 && repairAnimsTotal == 0) + yield break; + else if (rearmAnimsTotal == 1 && repairAnimsTotal == 0) + foreach (var rearmAnim in rearmAnims) + rearmAnim.RenameKey("WithResupplyAnimation"); + else if (rearmAnimsTotal == 0 && repairAnimsTotal == 1) + foreach (var repairAnim in repairAnims) + repairAnim.RenameKey("WithResupplyAnimation"); + else if (rearmAnimsTotal == 1 && repairAnimsTotal == 1) + { + var rearmAnim = rearmAnims.First(); + var repairAnim = repairAnims.First(); + var rearmSequence = rearmAnim.LastChildMatching("Sequence"); + var rearmBody = rearmAnim.LastChildMatching("Body"); + var repairSequence = repairAnim.LastChildMatching("Sequence"); + var repairBody = repairAnim.LastChildMatching("Body"); + var matchingSequences = (rearmSequence == null && repairSequence == null) + || (rearmSequence != null && repairSequence != null && rearmSequence.Value.Value == repairSequence.Value.Value); + var matchingBodies = (rearmBody == null && repairBody == null) + || (rearmBody != null && repairBody != null && rearmBody.Value.Value == repairBody.Value.Value); + + // If neither animation strays from the default values, we can safely merge them + if (matchingSequences && matchingBodies) + { + rearmAnim.RenameKey("WithResupplyAnimation"); + actorNode.RemoveNode(repairAnim); + } + else + { + rearmAnim.RenameKey("WithResupplyAnimation@Rearm", false, true); + repairAnim.RenameKey("WithResupplyAnimation@Repair", false, true); + } + } + else + { + // If we got here, we have more than one of at least one of the two animation traits. + var rearmAnimCount = 0; + foreach (var rearmAnim in rearmAnims) + { + ++rearmAnimCount; + rearmAnim.RenameKey("WithResupplyAnimation@Rearm" + rearmAnimCount.ToString(), false, true); + var playOnRearmNode = new MiniYamlNode("PlayAnimationOn", "Rearm"); + rearmAnim.AddNode(playOnRearmNode); + } + + var repairAnimCount = 0; + foreach (var repairAnim in repairAnims) + { + ++repairAnimCount; + repairAnim.RenameKey("WithResupplyAnimation@Repair" + repairAnimCount.ToString(), false, true); + var playOnRepairNode = new MiniYamlNode("PlayAnimationOn", "Repair"); + repairAnim.AddNode(playOnRepairNode); + } + } + + yield break; + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs index d726b6b2d5..2cc6e5e7ab 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs @@ -91,6 +91,7 @@ namespace OpenRA.Mods.Common.UpdateRules // Bleed only changes here new RenameEditorTilesetFilter(), new DefineNotificationDefaults(), + new MergeRearmAndRepairAnimation(), }) }; diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml index 9bb167499e..01a1a8fb8f 100644 --- a/mods/cnc/rules/structures.yaml +++ b/mods/cnc/rules/structures.yaml @@ -521,7 +521,7 @@ HPAD: HpPerStep: 1000 PlayerExperience: 25 StartRepairingNotification: Repairing - WithRepairAnimation: + WithResupplyAnimation: RallyPoint: ProductionQueue@GDI: Type: Aircraft.GDI @@ -654,7 +654,7 @@ FIX: PlayerExperience: 25 StartRepairingNotification: Repairing RallyPoint: - WithRepairAnimation: + WithResupplyAnimation: Power: Amount: -20 ProvidesPrerequisite@buildingname: diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml index ff2c8c0ec6..b8c039af98 100644 --- a/mods/ra/rules/structures.yaml +++ b/mods/ra/rules/structures.yaml @@ -1338,7 +1338,7 @@ HPAD: ReferencePoint: Top ZOffset: 256 RequiresCondition: primary - WithRearmAnimation: + WithResupplyAnimation: AFLD: Inherits: ^Building @@ -1478,7 +1478,7 @@ AFLD: ReferencePoint: Top ZOffset: 256 RequiresCondition: primary - WithRearmAnimation: + WithResupplyAnimation: AFLD.Ukraine: Inherits: AFLD @@ -1874,8 +1874,7 @@ FIX: StartRepairingNotification: Repairing FinishRepairingNotification: UnitRepaired PlayerExperience: 15 - WithRepairAnimation: - WithRearmAnimation: + WithResupplyAnimation: Power: Amount: -30 ProvidesPrerequisite@buildingname: