diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 8140facf1b..179b0fa288 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -781,6 +781,8 @@ + + diff --git a/OpenRA.Mods.Common/Traits/Buildings/BridgePlaceholder.cs b/OpenRA.Mods.Common/Traits/Buildings/BridgePlaceholder.cs index 189a87e27b..bf788c406a 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BridgePlaceholder.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BridgePlaceholder.cs @@ -15,12 +15,13 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - [Desc("Placeholder actor that transforms into another actor type when repaired.")] + [Desc("Placeholder actor used for dead segments and bridge end ramps.")] class BridgePlaceholderInfo : ITraitInfo { public readonly string Type = "GroundLevelBridge"; - [FieldLoader.Require] + public readonly DamageState DamageState = DamageState.Undamaged; + [Desc("Actor type to replace with on repair.")] [ActorReference] public readonly string ReplaceWithActor = null; @@ -54,6 +55,9 @@ namespace OpenRA.Mods.Common.Traits void IBridgeSegment.Repair(Actor repairer) { + if (Info.ReplaceWithActor == null) + return; + self.World.AddFrameEndTask(w => { self.Dispose(); @@ -68,11 +72,11 @@ namespace OpenRA.Mods.Common.Traits void IBridgeSegment.Demolish(Actor saboteur) { - // Do nothing, we're already dead + // Do nothing } string IBridgeSegment.Type { get { return Info.Type; } } - DamageState IBridgeSegment.DamageState { get { return DamageState.Dead; } } + DamageState IBridgeSegment.DamageState { get { return Info.DamageState; } } bool IBridgeSegment.Valid { get { return self.IsInWorld; } } CVec[] IBridgeSegment.NeighbourOffsets { get { return Info.NeighbourOffsets; } } CPos IBridgeSegment.Location { get { return self.Location; } } diff --git a/OpenRA.Mods.Common/Traits/Render/WithBridgeSpriteBody.cs b/OpenRA.Mods.Common/Traits/Render/WithBridgeSpriteBody.cs new file mode 100644 index 0000000000..4ab3bbab46 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/WithBridgeSpriteBody.cs @@ -0,0 +1,130 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 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.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits.Render +{ + class WithBridgeSpriteBodyInfo : WithSpriteBodyInfo + { + public readonly string Type = "GroundLevelBridge"; + + [Desc("Offset to search for the 'A' neighbour")] + public readonly CVec AOffset = CVec.Zero; + + [Desc("Position to search for the 'B' neighbour")] + public readonly CVec BOffset = CVec.Zero; + + [SequenceReference] + [Desc("Sequences to use when both neighbours are alive.")] + public readonly string[] Sequences = { "idle" }; + + [SequenceReference] + public readonly string[] ADestroyedSequences = { "adestroyed" }; + + [SequenceReference] + public readonly string[] BDestroyedSequences = { "bdestroyed" }; + + [SequenceReference] + public readonly string[] ABDestroyedSequences = { "abdestroyed" }; + + public override object Create(ActorInitializer init) { return new WithBridgeSpriteBody(init, this); } + + public override IEnumerable RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p) + { + var anim = new Animation(init.World, image); + anim.PlayFetchIndex(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), Sequences.First()), () => 0); + + yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p, rs.Scale); + } + } + + class WithBridgeSpriteBody : WithSpriteBody, INotifyRemovedFromWorld + { + readonly WithBridgeSpriteBodyInfo bridgeInfo; + readonly BridgeLayer bridgeLayer; + readonly Actor self; + + public WithBridgeSpriteBody(ActorInitializer init, WithBridgeSpriteBodyInfo info) + : base(init, info, () => 0) + { + self = init.Self; + bridgeInfo = info; + bridgeLayer = init.World.WorldActor.Trait(); + } + + protected override void OnBuildComplete(Actor self) + { + if (bridgeInfo.AOffset != CVec.Zero) + UpdateNeighbour(bridgeInfo.AOffset); + + if (bridgeInfo.BOffset != CVec.Zero) + UpdateNeighbour(bridgeInfo.BOffset); + + SetDirty(); + } + + void UpdateNeighbour(CVec offset) + { + var neighbour = bridgeLayer[self.Location + offset]; + if (neighbour == null) + return; + + var body = neighbour.TraitOrDefault(); + if (body != null && body.bridgeInfo.Type == bridgeInfo.Type) + body.SetDirty(); + } + + void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) + { + UpdateNeighbour(bridgeInfo.AOffset); + UpdateNeighbour(bridgeInfo.BOffset); + } + + void SetDirty() + { + self.World.AddFrameEndTask(w => + { + var aDestroyed = bridgeInfo.AOffset != CVec.Zero && NeighbourIsDestroyed(bridgeInfo.AOffset); + var bDestroyed = bridgeInfo.BOffset != CVec.Zero && NeighbourIsDestroyed(bridgeInfo.BOffset); + + var sequence = DefaultAnimation.CurrentSequence.Name; + if (aDestroyed && bDestroyed && bridgeInfo.ABDestroyedSequences.Any()) + sequence = bridgeInfo.ABDestroyedSequences.Random(Game.CosmeticRandom); + else if (aDestroyed && bridgeInfo.ADestroyedSequences.Any()) + sequence = bridgeInfo.ADestroyedSequences.Random(Game.CosmeticRandom); + else if (bDestroyed && bridgeInfo.BDestroyedSequences.Any()) + sequence = bridgeInfo.BDestroyedSequences.Random(Game.CosmeticRandom); + else + sequence = bridgeInfo.Sequences.Random(Game.CosmeticRandom); + + DefaultAnimation.PlayRepeating(NormalizeSequence(self, sequence)); + }); + } + + bool NeighbourIsDestroyed(CVec offset) + { + var neighbour = bridgeLayer[self.Location + offset]; + if (neighbour == null) + return false; + + var segment = neighbour.TraitOrDefault(); + if (segment == null) + return false; + + return segment.DamageState == DamageState.Dead; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Render/WithDeadBridgeSpriteBody.cs b/OpenRA.Mods.Common/Traits/Render/WithDeadBridgeSpriteBody.cs new file mode 100644 index 0000000000..a40e4e5f29 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/WithDeadBridgeSpriteBody.cs @@ -0,0 +1,100 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 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.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits.Render +{ + class WithDeadBridgeSpriteBodyInfo : WithSpriteBodyInfo + { + [ActorReference] + public readonly string[] RampActors = { }; + + [Desc("Offset to search for the 'A' neighbour")] + public readonly CVec AOffset = CVec.Zero; + + [Desc("Position to search for the 'B' neighbour")] + public readonly CVec BOffset = CVec.Zero; + + [SequenceReference] + public readonly string[] ARampSequences = { "aramp" }; + + [SequenceReference] + public readonly string[] BRampSequences = { "bramp" }; + + [SequenceReference] + public readonly string[] ABRampSequences = { "abramp" }; + + [SequenceReference] + [Desc("Placeholder sequence to use in the map editor.")] + public readonly string EditorSequence = "editor"; + + [PaletteReference] + [Desc("Palette to use for the editor placeholder.")] + public readonly string EditorPalette = "terrainalpha"; + + public override object Create(ActorInitializer init) { return new WithDeadBridgeSpriteBody(init, this); } + + public override IEnumerable RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p) + { + var anim = new Animation(init.World, image); + var sequence = init.World.Type == WorldType.Editor ? EditorSequence : Sequence; + var palette = init.World.Type == WorldType.Editor ? init.WorldRenderer.Palette(EditorPalette) : p; + anim.PlayFetchIndex(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), sequence), () => 0); + yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, palette, rs.Scale); + } + } + + class WithDeadBridgeSpriteBody : WithSpriteBody + { + readonly WithDeadBridgeSpriteBodyInfo bridgeInfo; + readonly BridgeLayer bridgeLayer; + + public WithDeadBridgeSpriteBody(ActorInitializer init, WithDeadBridgeSpriteBodyInfo info) + : base(init, info, () => 0) + { + bridgeInfo = info; + bridgeLayer = init.World.WorldActor.Trait(); + } + + bool RampExists(Actor self, CVec offset) + { + var neighbour = bridgeLayer[self.Location + offset]; + if (neighbour == null) + return false; + + return bridgeInfo.RampActors.Contains(neighbour.Info.Name); + } + + protected override void OnBuildComplete(Actor self) + { + self.World.AddFrameEndTask(w => + { + var aRamp = bridgeInfo.AOffset != CVec.Zero && RampExists(self, bridgeInfo.AOffset); + var bRamp = bridgeInfo.BOffset != CVec.Zero && RampExists(self, bridgeInfo.BOffset); + + var sequence = DefaultAnimation.CurrentSequence.Name; + if (aRamp && bRamp && bridgeInfo.ABRampSequences.Any()) + sequence = bridgeInfo.ABRampSequences.Random(Game.CosmeticRandom); + else if (aRamp && bridgeInfo.ARampSequences.Any()) + sequence = bridgeInfo.ARampSequences.Random(Game.CosmeticRandom); + else if (bRamp && bridgeInfo.BRampSequences.Any()) + sequence = bridgeInfo.BRampSequences.Random(Game.CosmeticRandom); + + DefaultAnimation.PlayRepeating(NormalizeSequence(self, sequence)); + }); + } + } +} diff --git a/mods/ts/rules/bridges.yaml b/mods/ts/rules/bridges.yaml index 49bc2b6fde..98cc31f891 100644 --- a/mods/ts/rules/bridges.yaml +++ b/mods/ts/rules/bridges.yaml @@ -49,19 +49,32 @@ LOBRDG_A: NeighbourOffsets: 1,-1, 1,1 SpawnActorOnDeath: Actor: lobrdg_a_d + -WithSpriteBody: + WithBridgeSpriteBody: + AOffset: 1,-1 + BOffset: 1,1 + Sequences: idle, idle2, idle3, idle4 + ADestroyedSequences: adead + BDestroyedSequences: bdead + ABDestroyedSequences: abdead LOBRDG_A_D: Inherits: LOBRDG_A - -RenderSprites: - RenderSpritesEditorOnly: - Palette: terrainalpha EditorOnlyTooltip: Name: Dead Bridge -GroundLevelBridge: -AppearsOnRadar: BridgePlaceholder: + DamageState: Dead ReplaceWithActor: lobrdg_a NeighbourOffsets: 1,-1, 1,1 + -WithBridgeSpriteBody: + WithDeadBridgeSpriteBody: + RampActors: lobrdg_r_ne, lobrdg_r_sw + AOffset: 1,-1 + BOffset: 1,1 + CustomSelectionSize: + CustomBounds: 96, 48 LOBRDG_B: Inherits: ^LowBridge @@ -72,52 +85,69 @@ LOBRDG_B: NeighbourOffsets: -1,1, 1,1 SpawnActorOnDeath: Actor: lobrdg_b_d + -WithSpriteBody: + WithBridgeSpriteBody: + AOffset: -1,1 + BOffset: 1,1 + Sequences: idle, idle2, idle3, idle4 + ADestroyedSequences: adead + BDestroyedSequences: bdead + ABDestroyedSequences: abdead LOBRDG_B_D: Inherits: LOBRDG_B - -RenderSprites: - RenderSpritesEditorOnly: - Palette: terrainalpha EditorOnlyTooltip: Name: Dead Bridge -GroundLevelBridge: -AppearsOnRadar: BridgePlaceholder: + DamageState: Dead ReplaceWithActor: lobrdg_b NeighbourOffsets: -1,1, 1,1 + -WithBridgeSpriteBody: + WithDeadBridgeSpriteBody: + RampActors: lobrdg_r_nw, lobrdg_r_se + AOffset: 1,1 + BOffset: -1,1 + CustomSelectionSize: + CustomBounds: 96, 48 LOBRDG_R_SE: Inherits: ^LowBridgeRamp Building: Footprint: _ _ _ Dimensions: 1, 3 + BridgePlaceholder: + NeighbourOffsets: -1,1 EditorOnlyTooltip: Name: Bridge Ramp - Description: South East LOBRDG_R_NW: Inherits: ^LowBridgeRamp Building: Footprint: _ _ _ Dimensions: 1, 3 + BridgePlaceholder: + NeighbourOffsets: 1,1 EditorOnlyTooltip: Name: Bridge Ramp - Description: North West LOBRDG_R_NE: Inherits: ^LowBridgeRamp Building: Footprint: ___ Dimensions: 3, 1 + BridgePlaceholder: + NeighbourOffsets: 1,1 EditorOnlyTooltip: Name: Bridge Ramp - Description: North East LOBRDG_R_SW: Inherits: ^LowBridgeRamp Building: Footprint: ___ Dimensions: 3, 1 + BridgePlaceholder: + NeighbourOffsets: 1,-1 EditorOnlyTooltip: Name: Bridge Ramp - Description: South West diff --git a/mods/ts/sequences/bridges.yaml b/mods/ts/sequences/bridges.yaml index 9c22f720c1..76102f50f5 100644 --- a/mods/ts/sequences/bridges.yaml +++ b/mods/ts/sequences/bridges.yaml @@ -8,29 +8,39 @@ lobrdg_a: Inherits: ^bridge - idle: lobrdg10 # lobrdg11, 12, 13 - damaged-idle: lobrdg16 - sw: lobrdg14 - damaged-sw: lobrdg17 - ne: lobrdg15 - damaged-ne: lobrdg18 + idle: lobrdg10 + idle2: lobrdg11 + idle3: lobrdg12 + idle4: lobrdg13 + adead: lobrdg15 + bdead: lobrdg14 + abdead: lobrdg16 lobrdg_a_d: Inherits: ^bridge - idle: lobrdg10 # actually lobrdg28 + idle: lobrdg28 + aramp: lobrdg17 + bramp: lobrdg18 + abramp: lobrdg28 + editor: lobrdg10 lobrdg_b: Inherits: ^bridge - idle: lobrdg01 # lobrdg02, 03, 04 - damaged-idle: lobrdg07 - se: lobrdg06 - damaged-se: lobrdg09 - nw: lobrdg05 - damaged-nw: lobrdg08 + idle: lobrdg01 + idle2: lobrdg02 + idle3: lobrdg03 + idle4: lobrdg04 + adead: lobrdg05 + bdead: lobrdg06 + abdead: lobrdg07 lobrdg_b_d: Inherits: ^bridge - idle: lobrdg01 # actually lobrdg27 + idle: lobrdg27 + aramp: lobrdg08 + bramp: lobrdg09 + abramp: lobrdg27 + editor: lobrdg01 lobrdg_r_se: Inherits: ^bridge