diff --git a/OpenRA.Mods.Common/Activities/SpriteHarvesterDockSequence.cs b/OpenRA.Mods.Common/Activities/SpriteHarvesterDockSequence.cs index a5b78bdc92..89da5f73bf 100644 --- a/OpenRA.Mods.Common/Activities/SpriteHarvesterDockSequence.cs +++ b/OpenRA.Mods.Common/Activities/SpriteHarvesterDockSequence.cs @@ -15,25 +15,37 @@ namespace OpenRA.Mods.Common.Activities { public class SpriteHarvesterDockSequence : HarvesterDockSequence { - readonly RenderUnit ru; + readonly WithSpriteBody wsb; + readonly WithDockingAnimation wda; public SpriteHarvesterDockSequence(Actor self, Actor refinery, int dockAngle, bool isDragRequired, WVec dragOffset, int dragLength) : base(self, refinery, dockAngle, isDragRequired, dragOffset, dragLength) { - ru = self.Trait(); + wsb = self.Trait(); + wda = self.Trait(); } public override Activity OnStateDock(Actor self) { - ru.PlayCustomAnimation(self, "dock", () => ru.PlayCustomAnimationRepeating(self, "dock-loop")); + foreach (var trait in self.TraitsImplementing()) + trait.Docked(); + + wsb.PlayCustomAnimation(self, wda.Info.DockSequence, () => wsb.PlayCustomAnimationRepeating(self, wda.Info.DockLoopSequence)); dockingState = State.Loop; return this; } public override Activity OnStateUndock(Actor self) { - ru.PlayCustomAnimationBackwards(self, "dock", () => dockingState = State.Complete); + wsb.PlayCustomAnimationBackwards(self, wda.Info.DockSequence, + () => + { + dockingState = State.Complete; + foreach (var trait in self.TraitsImplementing()) + trait.Undocked(); + }); dockingState = State.Wait; + return this; } } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 10188907f4..f1549aaa4d 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -397,7 +397,6 @@ - @@ -421,7 +420,9 @@ + + diff --git a/OpenRA.Mods.Common/Traits/Render/RenderHarvester.cs b/OpenRA.Mods.Common/Traits/Render/RenderHarvester.cs deleted file mode 100644 index a3f8ca054c..0000000000 --- a/OpenRA.Mods.Common/Traits/Render/RenderHarvester.cs +++ /dev/null @@ -1,63 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2015 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 OpenRA.Activities; -using OpenRA.Graphics; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits -{ - class RenderHarvesterInfo : RenderUnitInfo, Requires - { - public readonly string[] ImagesByFullness = { "harv" }; - - [SequenceReference("ImagesByFullness")] public readonly string HarvestSequence = "harvest"; - - public override object Create(ActorInitializer init) { return new RenderHarvester(init, this); } - } - - class RenderHarvester : RenderUnit, INotifyHarvesterAction - { - Harvester harv; - RenderHarvesterInfo info; - - public RenderHarvester(ActorInitializer init, RenderHarvesterInfo info) - : base(init, info) - { - this.info = info; - harv = init.Self.Trait(); - - // HACK: Force images to be loaded up-front - foreach (var image in info.ImagesByFullness) - new Animation(init.World, image); - } - - public override void Tick(Actor self) - { - var desiredState = harv.Fullness * (info.ImagesByFullness.Length - 1) / 100; - var desiredImage = info.ImagesByFullness[desiredState]; - - if (DefaultAnimation.Name != desiredImage) - DefaultAnimation.ChangeImage(desiredImage, info.Sequence); - - base.Tick(self); - } - - public void Harvested(Actor self, ResourceType resource) - { - if (DefaultAnimation.CurrentSequence.Name != info.HarvestSequence) - PlayCustomAnim(self, info.HarvestSequence); - } - - public void MovingToResources(Actor self, CPos targetCell, Activity next) { } - public void MovingToRefinery(Actor self, CPos targetCell, Activity next) { } - public void MovementCancelled(Actor self) { } - } -} diff --git a/OpenRA.Mods.Common/Traits/Render/WithDockingAnimation.cs b/OpenRA.Mods.Common/Traits/Render/WithDockingAnimation.cs new file mode 100644 index 0000000000..252b4a53d2 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/WithDockingAnimation.cs @@ -0,0 +1,35 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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 OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + public class WithDockingAnimationInfo : ITraitInfo, Requires, Requires + { + [Desc("Displayed when docking to refinery.")] + [SequenceReference] public readonly string DockSequence = "dock"; + + [Desc("Looped while unloading at refinery.")] + [SequenceReference] public readonly string DockLoopSequence = "dock-loop"; + + public object Create(ActorInitializer init) { return new WithDockingAnimation(init, this); } + } + + public class WithDockingAnimation + { + public readonly WithDockingAnimationInfo Info; + + public WithDockingAnimation(ActorInitializer init, WithDockingAnimationInfo info) + { + Info = info; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Render/WithHarvestAnimation.cs b/OpenRA.Mods.Common/Traits/Render/WithHarvestAnimation.cs new file mode 100644 index 0000000000..fdc0bc196d --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/WithHarvestAnimation.cs @@ -0,0 +1,87 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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 OpenRA.Activities; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + public class WithHarvestAnimationInfo : ITraitInfo, Requires, Requires + { + [Desc("Prefix added to idle and harvest sequences depending on fullness of harvester.")] + [SequenceReference(null, true)] public readonly string[] PrefixByFullness = { "" }; + + [Desc("Displayed while harvesting.")] + [SequenceReference] public readonly string HarvestSequence = "harvest"; + + public object Create(ActorInitializer init) { return new WithHarvestAnimation(init, this); } + } + + public class WithHarvestAnimation : ITick, INotifyHarvesterAction + { + public readonly WithHarvestAnimationInfo Info; + readonly WithSpriteBody wsb; + readonly Harvester harv; + + public bool IsModifying; + + public WithHarvestAnimation(ActorInitializer init, WithHarvestAnimationInfo info) + { + Info = info; + harv = init.Self.Trait(); + wsb = init.Self.Trait(); + } + + protected virtual string NormalizeHarvesterSequence(Actor self, string baseSequence) + { + var desiredState = harv.Fullness * (Info.PrefixByFullness.Length - 1) / 100; + var desiredPrefix = Info.PrefixByFullness[desiredState]; + + if (wsb.DefaultAnimation.HasSequence(desiredPrefix + baseSequence)) + return desiredPrefix + baseSequence; + else + return baseSequence; + } + + public void Tick(Actor self) + { + if (!IsModifying && !string.IsNullOrEmpty(wsb.Info.Sequence) && wsb.DefaultAnimation.HasSequence(NormalizeHarvesterSequence(self, wsb.Info.Sequence))) + { + if (wsb.DefaultAnimation.CurrentSequence.Name != NormalizeHarvesterSequence(self, wsb.Info.Sequence)) + wsb.DefaultAnimation.ReplaceAnim(NormalizeHarvesterSequence(self, wsb.Info.Sequence)); + } + } + + public void Harvested(Actor self, ResourceType resource) + { + if (!IsModifying && !string.IsNullOrEmpty(Info.HarvestSequence) && wsb.DefaultAnimation.HasSequence(NormalizeHarvesterSequence(self, Info.HarvestSequence))) + { + IsModifying = true; + wsb.PlayCustomAnimation(self, NormalizeHarvesterSequence(self, Info.HarvestSequence), () => IsModifying = false); + } + } + + // If IsModifying isn't set to true, the docking animation + // will be overridden by the WithHarvestAnimation fullness modifier. + public void Docked() + { + IsModifying = true; + } + + public void Undocked() + { + IsModifying = false; + } + + public void MovingToResources(Actor self, CPos targetCell, Activity next) { } + public void MovingToRefinery(Actor self, CPos targetCell, Activity next) { } + public void MovementCancelled(Actor self) { } + } +} diff --git a/OpenRA.Mods.Common/Traits/Render/WithHarvestOverlay.cs b/OpenRA.Mods.Common/Traits/Render/WithHarvestOverlay.cs index ccb9bdc602..ce6ed88fa1 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithHarvestOverlay.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithHarvestOverlay.cs @@ -62,6 +62,8 @@ namespace OpenRA.Mods.Common.Traits public void MovingToResources(Actor self, CPos targetCell, Activity next) { } public void MovingToRefinery(Actor self, CPos targetCell, Activity next) { } public void MovementCancelled(Actor self) { } + public void Docked() { } + public void Undocked() { } public static int ZOffsetFromCenter(Actor self, WPos pos, int offset) { diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 5dd5863784..880ce354c6 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -58,6 +58,8 @@ namespace OpenRA.Mods.Common.Traits void MovingToRefinery(Actor self, CPos targetCell, Activity next); void MovementCancelled(Actor self); void Harvested(Actor self, ResourceType resource); + void Docked(); + void Undocked(); } public interface ITechTreePrerequisite diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index 26456975ab..3e568972eb 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -1268,6 +1268,55 @@ namespace OpenRA.Mods.Common.UtilityCommands if (rrlc != null) rrlc.Key = "-WithLandingCraftAnimation"; } + + // Replaced RenderHarvester with WithFacingSpriteBody + WithHarvestAnimation + WithDockingAnimation. + // Note: These rules are set up to do approximately the right thing for maps, but + // mods might need additional manual tweaks. This is the best we can do without having + // much smarter rules parsing, because we currently can't reason about inherited traits. + if (depth == 0) + { + var childKeySequence = new[] { "Sequence" }; + var childKeyIBF = new[] { "ImagesByFullness" }; + var childKeysExcludeFromRS = new[] { "Sequence", "ImagesByFullness", "HarvestSequence" }; + + var rh = node.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("RenderHarvester")); + if (rh != null) + { + rh.Key = "WithHarvestAnimation"; + + var rsNodes = rh.Value.Nodes.Where(n => !childKeysExcludeFromRS.Contains(n.Key)).ToList(); + var wfsbNodes = rh.Value.Nodes.Where(n => childKeySequence.Contains(n.Key)).ToList(); + var ibfNode = rh.Value.Nodes.Where(n => childKeyIBF.Contains(n.Key)).ToList(); + + if (rsNodes.Any()) + node.Value.Nodes.Add(new MiniYamlNode("RenderSprites", new MiniYaml("", rsNodes))); + else + node.Value.Nodes.Add(new MiniYamlNode("RenderSprites", "")); + + if (wfsbNodes.Any()) + node.Value.Nodes.Add(new MiniYamlNode("WithFacingSpriteBody", new MiniYaml("", wfsbNodes))); + else + node.Value.Nodes.Add(new MiniYamlNode("WithFacingSpriteBody", "")); + + node.Value.Nodes.Add(new MiniYamlNode("AutoSelectionSize", "")); + node.Value.Nodes.Add(new MiniYamlNode("WithDockingAnimation", "")); + + rh.Value.Nodes.RemoveAll(n => rsNodes.Contains(n)); + rh.Value.Nodes.RemoveAll(n => wfsbNodes.Contains(n)); + + if (ibfNode.Any()) + rh.Value.Nodes.RemoveAll(n => ibfNode.Contains(n)); + Console.WriteLine("The 'ImagesByFullness' property from the removed RenderHarvester trait has been"); + Console.WriteLine("replaced with a 'PrefixByFullness' property on the new WithHarvestAnimation trait."); + Console.WriteLine("This cannot be reliably upgraded, as the actor sequences need to be adapted as well."); + Console.WriteLine("Therefore, WithHarvestAnimation will use the default (no prefix) after upgrading."); + Console.WriteLine("See RA's harvester for reference on how to re-implement this feature using the new trait."); + } + + var rrh = node.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("-RenderHarvester")); + if (rrh != null) + rrh.Key = "-WithHarvestAnimation"; + } } UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); diff --git a/OpenRA.Mods.D2k/Traits/Carryable.cs b/OpenRA.Mods.D2k/Traits/Carryable.cs index 30302305b5..ed9616507b 100644 --- a/OpenRA.Mods.D2k/Traits/Carryable.cs +++ b/OpenRA.Mods.D2k/Traits/Carryable.cs @@ -93,6 +93,8 @@ namespace OpenRA.Mods.D2k.Traits // We do not handle Harvested notification public void Harvested(Actor self, ResourceType resource) { } + public void Docked() { } + public void Undocked() { } public Actor GetClosestIdleCarrier() { diff --git a/mods/cnc/rules/vehicles.yaml b/mods/cnc/rules/vehicles.yaml index 62c48afeff..d768d3fb33 100644 --- a/mods/cnc/rules/vehicles.yaml +++ b/mods/cnc/rules/vehicles.yaml @@ -70,10 +70,8 @@ HARV: LeavesHusk: HuskActor: HARV.Husk -GainsExperience: - -RenderSprites: - -WithFacingSpriteBody: - -AutoSelectionSize: - RenderHarvester: + WithHarvestAnimation: + WithDockingAnimation: Explodes: Weapon: TiberiumExplosion SelectionDecorations: diff --git a/mods/d2k/rules/vehicles.yaml b/mods/d2k/rules/vehicles.yaml index 84f4b6bc43..31773a4140 100644 --- a/mods/d2k/rules/vehicles.yaml +++ b/mods/d2k/rules/vehicles.yaml @@ -83,6 +83,7 @@ harvester: -AutoSelectionSize: WithHarvestOverlay: Palette: effect50alpha + WithDockingAnimation: AttractsWorms: Intensity: 700 SelectionDecorations: diff --git a/mods/ra/rules/vehicles.yaml b/mods/ra/rules/vehicles.yaml index e2f193502e..e5718dd778 100644 --- a/mods/ra/rules/vehicles.yaml +++ b/mods/ra/rules/vehicles.yaml @@ -265,11 +265,9 @@ HARV: Crushes: wall, mine, crate, infantry RevealsShroud: Range: 4c0 - -WithFacingSpriteBody: - -AutoSelectionSize: - -RenderSprites: - RenderHarvester: - ImagesByFullness: harvempty, harvhalf, harv + WithHarvestAnimation: + PrefixByFullness: empty-, half-, full- + WithDockingAnimation: GpsDot: String: Harvester LeavesHusk: diff --git a/mods/ra/sequences/vehicles.yaml b/mods/ra/sequences/vehicles.yaml index 8f5a647dc3..464060a5e9 100644 --- a/mods/ra/sequences/vehicles.yaml +++ b/mods/ra/sequences/vehicles.yaml @@ -14,9 +14,27 @@ truk: icon: trukicon harv: - idle: + idle: harvempty Facings: 32 - harvest: + harvest: harvempty + Start: 32 + Length: 8 + Facings: 8 + empty-idle: harvempty + Facings: 32 + empty-harvest: harvempty + Start: 32 + Length: 8 + Facings: 8 + half-idle: harvhalf + Facings: 32 + half-harvest: harvhalf + Start: 32 + Length: 8 + Facings: 8 + full-idle: + Facings: 32 + full-harvest: Start: 32 Length: 8 Facings: 8 @@ -29,34 +47,6 @@ harv: icon: harvicon Start: 0 -harvhalf: - idle: - Facings: 32 - harvest: - Start: 32 - Length: 8 - Facings: 8 - dock: harv - Start: 96 - Length: 8 - dock-loop: harv - Start: 104 - Length: 7 - -harvempty: - idle: - Facings: 32 - harvest: - Start: 32 - Length: 8 - Facings: 8 - dock: harv - Start: 96 - Length: 8 - dock-loop: harv - Start: 104 - Length: 7 - hhusk: idle: Facings: 32