From 2ef6f7c0cc1ffe34585c9c48da8c742b9210c0ae Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 19 May 2018 19:38:19 +0000 Subject: [PATCH] Replace repair indicator effect with a Decoration subclass. --- OpenRA.Mods.Common/Effects/RepairIndicator.cs | 73 -------------- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 3 +- .../Traits/Buildings/RepairableBuilding.cs | 99 +++++++++++-------- .../Traits/Player/AllyRepair.cs | 9 +- .../Render/WithBuildingRepairDecoration.cs | 68 +++++++++++++ .../Traits/Render/WithDecoration.cs | 19 ++-- .../Rules/SplitRepairDecoration.cs | 69 +++++++++++++ OpenRA.Mods.Common/UpdateRules/UpdatePath.cs | 3 +- mods/cnc/rules/defaults.yaml | 12 +++ mods/d2k/rules/arrakis.yaml | 1 + mods/d2k/rules/defaults.yaml | 6 ++ mods/ra/maps/soviet-02b/rules.yaml | 1 + mods/ra/rules/defaults.yaml | 6 ++ mods/ts/rules/defaults.yaml | 6 +- 14 files changed, 250 insertions(+), 125 deletions(-) delete mode 100644 OpenRA.Mods.Common/Effects/RepairIndicator.cs create mode 100644 OpenRA.Mods.Common/Traits/Render/WithBuildingRepairDecoration.cs create mode 100644 OpenRA.Mods.Common/UpdateRules/Rules/SplitRepairDecoration.cs diff --git a/OpenRA.Mods.Common/Effects/RepairIndicator.cs b/OpenRA.Mods.Common/Effects/RepairIndicator.cs deleted file mode 100644 index 14b72c2582..0000000000 --- a/OpenRA.Mods.Common/Effects/RepairIndicator.cs +++ /dev/null @@ -1,73 +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.Collections.Generic; -using System.Linq; -using OpenRA.Effects; -using OpenRA.Graphics; -using OpenRA.Mods.Common.Traits; - -namespace OpenRA.Mods.Common.Effects -{ - class RepairIndicator : IEffect, IEffectAboveShroud - { - readonly Actor building; - readonly Animation anim; - readonly RepairableBuilding rb; - - int shownPlayer = 0; - - public RepairIndicator(Actor building) - { - this.building = building; - - rb = building.Trait(); - anim = new Animation(building.World, rb.Info.IndicatorImage, () => !rb.RepairActive || rb.IsTraitDisabled); - - CycleRepairer(); - } - - void IEffect.Tick(World world) - { - if (!building.IsInWorld || building.IsDead || !rb.Repairers.Any()) - world.AddFrameEndTask(w => w.Remove(this)); - - anim.Tick(); - } - - IEnumerable IEffect.Render(WorldRenderer wr) { return SpriteRenderable.None; } - - IEnumerable IEffectAboveShroud.RenderAboveShroud(WorldRenderer wr) - { - if (building.Disposed || rb.Repairers.Count == 0 || wr.World.FogObscures(building)) - return SpriteRenderable.None; - - PaletteReference palette; - if (!string.IsNullOrEmpty(rb.Info.IndicatorPalette)) - palette = wr.Palette(rb.Info.IndicatorPalette); - else - palette = wr.Palette(rb.Info.IndicatorPalettePrefix + rb.Repairers[shownPlayer % rb.Repairers.Count].InternalName); - - // Size shouldn't scale with pixel doubling - // TODO: Remove this RepairIndicator entirely in favor of using WithDecoration - var zoom = 1f / wr.Viewport.Zoom; - return anim.Render(building.CenterPosition, WVec.Zero, 0, palette, zoom); - } - - void CycleRepairer() - { - anim.PlayThen(rb.Info.IndicatorSequence, CycleRepairer); - - if (++shownPlayer == rb.Repairers.Count) - shownPlayer = 0; - } - } -} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index aae092c0bc..c6546c6698 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -147,7 +147,6 @@ - @@ -882,6 +881,8 @@ + + diff --git a/OpenRA.Mods.Common/Traits/Buildings/RepairableBuilding.cs b/OpenRA.Mods.Common/Traits/Buildings/RepairableBuilding.cs index 3dc10c9142..dbccffe965 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/RepairableBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/RepairableBuilding.cs @@ -36,23 +36,40 @@ namespace OpenRA.Mods.Common.Traits [Desc("Cancel the repair state when the trait is disabled.")] public readonly bool CancelWhenDisabled = false; - public readonly string IndicatorImage = "allyrepair"; - [SequenceReference("IndicatorImage")] public readonly string IndicatorSequence = "repair"; - - [Desc("Overrides the IndicatorPalettePrefix.")] - [PaletteReference] public readonly string IndicatorPalette = ""; - - [Desc("Suffixed by the internal repairing player name.")] - public readonly string IndicatorPalettePrefix = "player"; - [Desc("Experience gained by a player for repairing structures of allied players.")] public readonly int PlayerExperience = 0; + [GrantedConditionReference] + [Desc("The condition to grant to self while being repaired.")] + public readonly string RepairCondition = null; + public override object Create(ActorInitializer init) { return new RepairableBuilding(init.Self, this); } } public class RepairableBuilding : ConditionalTrait, ITick { + readonly Health health; + readonly Predicate isNotActiveAlly; + readonly Stack repairTokens = new Stack(); + ConditionManager conditionManager; + int remainingTicks; + + public readonly List Repairers = new List(); + public bool RepairActive { get; private set; } + + public RepairableBuilding(Actor self, RepairableBuildingInfo info) + : base(info) + { + health = self.Trait(); + isNotActiveAlly = player => player.WinState != WinState.Undefined || player.Stances[self.Owner] != Stance.Ally; + } + + protected override void Created(Actor self) + { + base.Created(self); + conditionManager = self.TraitOrDefault(); + } + [Sync] public int RepairersHash { @@ -65,40 +82,39 @@ namespace OpenRA.Mods.Common.Traits } } - public readonly List Repairers = new List(); - - readonly Health health; - public bool RepairActive = false; - - readonly Predicate isNotActiveAlly; - - public RepairableBuilding(Actor self, RepairableBuildingInfo info) - : base(info) + void UpdateCondition(Actor self) { - health = self.Trait(); - isNotActiveAlly = player => player.WinState != WinState.Undefined || player.Stances[self.Owner] != Stance.Ally; + if (conditionManager == null || string.IsNullOrEmpty(Info.RepairCondition)) + return; + + var currentRepairers = Repairers.Count; + while (Repairers.Count > repairTokens.Count) + repairTokens.Push(conditionManager.GrantCondition(self, Info.RepairCondition)); + + while (Repairers.Count < repairTokens.Count && repairTokens.Count > 0) + conditionManager.RevokeCondition(self, repairTokens.Pop()); } public void RepairBuilding(Actor self, Player player) { - if (!IsTraitDisabled && self.AppearsFriendlyTo(player.PlayerActor)) + if (IsTraitDisabled || !self.AppearsFriendlyTo(player.PlayerActor)) + return; + + // Remove the player if they are already repairing + if (Repairers.Remove(player)) { - // If the player won't affect the repair, we won't add him - if (!Repairers.Remove(player) && Repairers.Count < Info.RepairBonuses.Length) - { - Repairers.Add(player); - Game.Sound.PlayNotification(self.World.Map.Rules, player, "Speech", "Repairing", player.Faction.InternalName); - - self.World.AddFrameEndTask(w => - { - if (!self.IsDead) - w.Add(new RepairIndicator(self)); - }); - } + UpdateCondition(self); + return; } - } + + // Don't add new players if the limit has already been reached + if (Repairers.Count >= Info.RepairBonuses.Length - 1) + return; - int remainingTicks; + Repairers.Add(player); + Game.Sound.PlayNotification(self.World.Map.Rules, player, "Speech", "Repairing", player.Faction.InternalName); + UpdateCondition(self); + } void ITick.Tick(Actor self) { @@ -107,7 +123,7 @@ namespace OpenRA.Mods.Common.Traits if (RepairActive && Info.CancelWhenDisabled) { Repairers.Clear(); - RepairActive = false; + UpdateCondition(self); } return; @@ -115,11 +131,15 @@ namespace OpenRA.Mods.Common.Traits if (remainingTicks == 0) { - Repairers.RemoveAll(isNotActiveAlly); - - // If after the previous operation there's no repairers left, stop + Repairers.RemoveAll(isNotActiveAlly); + UpdateCondition(self); + + // If after the previous operation there's no repairers left, stop if (Repairers.Count == 0) + { + RepairActive = false; return; + } var buildingValue = self.GetSellValue(); @@ -160,6 +180,7 @@ namespace OpenRA.Mods.Common.Traits Repairers.Clear(); RepairActive = false; + UpdateCondition(self); return; } diff --git a/OpenRA.Mods.Common/Traits/Player/AllyRepair.cs b/OpenRA.Mods.Common/Traits/Player/AllyRepair.cs index c65d17c876..e29f21a237 100644 --- a/OpenRA.Mods.Common/Traits/Player/AllyRepair.cs +++ b/OpenRA.Mods.Common/Traits/Player/AllyRepair.cs @@ -23,9 +23,12 @@ namespace OpenRA.Mods.Common.Traits if (order.OrderString == "RepairBuilding" && order.Target.Type == TargetType.Actor) { var building = order.Target.Actor; - if (building.Info.HasTraitInfo()) - if (building.AppearsFriendlyTo(self)) - building.Trait().RepairBuilding(building, self.Owner); + if (!building.AppearsFriendlyTo(self)) + return; + + var rb = building.TraitOrDefault(); + if (rb != null) + rb.RepairBuilding(building, self.Owner); } } } diff --git a/OpenRA.Mods.Common/Traits/Render/WithBuildingRepairDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithBuildingRepairDecoration.cs new file mode 100644 index 0000000000..879c84b1da --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/WithBuildingRepairDecoration.cs @@ -0,0 +1,68 @@ +#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.Collections.Generic; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Support; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits.Render +{ + [Desc("Displays a custom UI overlay relative to the actor's mouseover bounds.")] + public class WithBuildingRepairDecorationInfo : WithDecorationInfo, Requires + { + public override object Create(ActorInitializer init) { return new WithBuildingRepairDecoration(init.Self, this); } + } + + public class WithBuildingRepairDecoration : WithDecoration + { + readonly RepairableBuilding rb; + readonly WithBuildingRepairDecorationInfo info; + int shownPlayer = 0; + + public WithBuildingRepairDecoration(Actor self, WithBuildingRepairDecorationInfo info) + : base(self, info) + { + this.info = info; + rb = self.Trait(); + anim = new Animation(self.World, info.Image, + () => !rb.RepairActive || rb.IsTraitDisabled || !ShouldRender(self)); + anim.PlayThen(info.Sequence, CycleRepairer); + CycleRepairer(); + } + + protected override bool ShouldRender(Actor self) + { + if (!rb.Repairers.Any()) + return false; + + return base.ShouldRender(self); + } + + protected override PaletteReference GetPalette(Actor self, WorldRenderer wr) + { + if (!info.IsPlayerPalette) + return wr.Palette(info.Palette); + + return wr.Palette(info.Palette + rb.Repairers[shownPlayer % rb.Repairers.Count].InternalName); + } + + void CycleRepairer() + { + anim.PlayThen(info.Sequence, CycleRepairer); + + if (++shownPlayer == rb.Repairers.Count) + shownPlayer = 0; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs index a070eabea4..939da5e19b 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs @@ -60,7 +60,7 @@ namespace OpenRA.Mods.Common.Traits.Render public class WithDecoration : ConditionalTrait, ITick, IRenderAboveShroud, IRenderAboveShroudWhenSelected { - protected readonly Animation Anim; + protected Animation anim; readonly IDecorationBounds[] decorationBounds; readonly string image; @@ -68,8 +68,8 @@ namespace OpenRA.Mods.Common.Traits.Render : base(info) { image = info.Image ?? self.Info.Name; - Anim = new Animation(self.World, image, () => self.World.Paused); - Anim.PlayRepeating(info.Sequence); + anim = new Animation(self.World, image, () => self.World.Paused); + anim.PlayRepeating(info.Sequence); decorationBounds = self.TraitsImplementing().ToArray(); } @@ -85,6 +85,11 @@ namespace OpenRA.Mods.Common.Traits.Render return true; } + protected virtual PaletteReference GetPalette(Actor self, WorldRenderer wr) + { + return wr.Palette(Info.Palette + (Info.IsPlayerPalette ? self.Owner.InternalName : "")); + } + IEnumerable IRenderAboveShroud.RenderAboveShroud(Actor self, WorldRenderer wr) { return !Info.RequiresSelection ? RenderInner(self, wr) : SpriteRenderable.None; @@ -100,14 +105,14 @@ namespace OpenRA.Mods.Common.Traits.Render IEnumerable RenderInner(Actor self, WorldRenderer wr) { - if (IsTraitDisabled || self.IsDead || !self.IsInWorld || Anim == null) + if (IsTraitDisabled || self.IsDead || !self.IsInWorld || anim == null) return Enumerable.Empty(); if (!ShouldRender(self) || self.World.FogObscures(self)) return Enumerable.Empty(); var bounds = decorationBounds.FirstNonEmptyBounds(self, wr); - var halfSize = (0.5f * Anim.Image.Size.XY).ToInt2(); + var halfSize = (0.5f * anim.Image.Size.XY).ToInt2(); var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2; var sizeOffset = -halfSize; @@ -136,10 +141,10 @@ namespace OpenRA.Mods.Common.Traits.Render var pxPos = wr.Viewport.WorldToViewPx(boundsOffset) + sizeOffset; return new IRenderable[] { - new UISpriteRenderable(Anim.Image, self.CenterPosition, pxPos, Info.ZOffset, wr.Palette(Info.Palette + (Info.IsPlayerPalette ? self.Owner.InternalName : "")), 1f) + new UISpriteRenderable(anim.Image, self.CenterPosition, pxPos, Info.ZOffset, GetPalette(self, wr), 1f) }; } - void ITick.Tick(Actor self) { Anim.Tick(); } + void ITick.Tick(Actor self) { anim.Tick(); } } } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/SplitRepairDecoration.cs b/OpenRA.Mods.Common/UpdateRules/Rules/SplitRepairDecoration.cs new file mode 100644 index 0000000000..e30884fec0 --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/SplitRepairDecoration.cs @@ -0,0 +1,69 @@ +#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; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class SplitRepairDecoration : UpdateRule + { + public override string Name { get { return "WithBuildingRepairDecoration trait split from RepairableBuilding"; } } + public override string Description + { + get + { + return "Rendering for the building repair indicator has been moved to a new\n" + + "WithBuildingRepairDecoration trait. The indicator definitions are automatically\n" + + "migrated from RepairableBuilding to WithBuildingRepairDecoration."; + } + } + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + { + // RepairableBuilding is hardcoded to only support one instance per actor + var rb = actorNode.LastChildMatching("RepairableBuilding"); + if (rb != null) + { + var imageNode = rb.LastChildMatching("IndicatorImage"); + var sequenceNode = rb.LastChildMatching("IndicatorSequence"); + var paletteNode = rb.LastChildMatching("IndicatorPalette"); + var palettePrefixNode = rb.LastChildMatching("IndicatorPalettePrefix"); + + var decoration = new MiniYamlNode("WithBuildingRepairDecoration", ""); + decoration.AddNode("Image", imageNode != null ? imageNode.Value.Value : "allyrepair"); + decoration.AddNode("Sequence", sequenceNode != null ? sequenceNode.Value.Value : "repair"); + decoration.AddNode("ReferencePoint", "Center"); + + if (paletteNode != null) + { + decoration.AddNode("Palette", paletteNode.Value.Value); + } + else + { + decoration.AddNode("Palette", palettePrefixNode != null ? palettePrefixNode.Value.Value : "player"); + decoration.AddNode("IsPlayerPalette", true); + } + + actorNode.AddNode(decoration); + + rb.RemoveNode(imageNode); + rb.RemoveNode(sequenceNode); + rb.RemoveNode(paletteNode); + rb.RemoveNode(palettePrefixNode); + } + + if (actorNode.LastChildMatching("-RepairableBuilding") != null) + actorNode.AddNode("-WithBuildingRepairDecoration", ""); + + yield break; + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs index cf766216dd..5abb168ac4 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs @@ -53,7 +53,8 @@ namespace OpenRA.Mods.Common.UpdateRules new RemovePaletteFromCurrentTileset(), new DefineLocomotors(), new DefineOwnerLostAction(), - new RenameEmitInfantryOnSell() + new RenameEmitInfantryOnSell(), + new SplitRepairDecoration(), }) }; diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index 7a1e94f2ff..ce1ff214ac 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -697,6 +697,12 @@ Capturable: CaptureThreshold: 55 WithMakeAnimation: + WithBuildingRepairDecoration: + Image: allyrepair + Sequence: repair + ReferencePoint: Center + Palette: player + IsPlayerPalette: True ^CivBuilding: Inherits: ^Building @@ -744,6 +750,12 @@ ShowOwnerRow: True EditorTilesetFilter: Categories: Tech building + WithBuildingRepairDecoration: + Image: allyrepair + Sequence: repair + ReferencePoint: Center + Palette: player + IsPlayerPalette: True ^CivField: Inherits: ^CivBuilding diff --git a/mods/d2k/rules/arrakis.yaml b/mods/d2k/rules/arrakis.yaml index 33eb8fac6f..361831e3d0 100644 --- a/mods/d2k/rules/arrakis.yaml +++ b/mods/d2k/rules/arrakis.yaml @@ -147,3 +147,4 @@ sietch: ProvidesPrerequisite@buildingname: -WithMakeAnimation: -WithCrumbleOverlay: + -WithBuildingRepairDecoration: diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index 9dea8d40bb..08327dc3a9 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -430,6 +430,12 @@ EditorTilesetFilter: Categories: Building CommandBarBlacklist: + WithBuildingRepairDecoration: + Image: allyrepair + Sequence: repair + ReferencePoint: Center + Palette: player + IsPlayerPalette: True ^Defense: Inherits: ^Building diff --git a/mods/ra/maps/soviet-02b/rules.yaml b/mods/ra/maps/soviet-02b/rules.yaml index 27d6820552..0d7e78f3bb 100644 --- a/mods/ra/maps/soviet-02b/rules.yaml +++ b/mods/ra/maps/soviet-02b/rules.yaml @@ -90,6 +90,7 @@ AFLD: GUN: -RepairableBuilding: + -WithBuildingRepairDecoration: HARV: Harvester: diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 0960da8a79..19ad67f62b 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -622,6 +622,12 @@ String: Structure Sellable: SellSounds: cashturn.aud + WithBuildingRepairDecoration: + Image: allyrepair + Sequence: repair + ReferencePoint: Center + Palette: player + IsPlayerPalette: True ^ScienceBuilding: Inherits: ^Building diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index ac2f75484a..042e6bec11 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -344,7 +344,6 @@ Capturable: RepairableBuilding: RepairStep: 700 - IndicatorPalette: mouse PlayerExperience: 25 WithDeathAnimation: DeathSequence: dead @@ -365,6 +364,11 @@ Pieces: 3, 5 ThrowsShrapnel@LARGE: Pieces: 2, 3 + WithBuildingRepairDecoration: + Image: allyrepair + Sequence: repair + ReferencePoint: Center + Palette: mouse ^CivBuilding: Inherits: ^BasicBuilding