diff --git a/OpenRA.Mods.Common/Activities/RepairBridge.cs b/OpenRA.Mods.Common/Activities/RepairBridge.cs index 58e18e4197..192a1f8691 100644 --- a/OpenRA.Mods.Common/Activities/RepairBridge.cs +++ b/OpenRA.Mods.Common/Activities/RepairBridge.cs @@ -16,27 +16,47 @@ namespace OpenRA.Mods.Common.Activities { class RepairBridge : Enter { + readonly Actor target; + readonly LegacyBridgeHut legacyHut; readonly BridgeHut hut; readonly string notification; public RepairBridge(Actor self, Actor target, EnterBehaviour enterBehaviour, string notification) : base(self, target, enterBehaviour) { - hut = target.Trait(); + this.target = target; + legacyHut = target.TraitOrDefault(); + hut = target.TraitOrDefault(); this.notification = notification; } protected override bool CanReserve(Actor self) { - return hut.BridgeDamageState != DamageState.Undamaged && !hut.Repairing && hut.Bridge.GetHut(0) != null && hut.Bridge.GetHut(1) != null; + if (legacyHut != null) + return legacyHut.BridgeDamageState != DamageState.Undamaged && !legacyHut.Repairing && legacyHut.Bridge.GetHut(0) != null && legacyHut.Bridge.GetHut(1) != null; + + if (hut != null) + return hut.BridgeDamageState != DamageState.Undamaged && !hut.Repairing; + + return false; } protected override void OnInside(Actor self) { - if (hut.BridgeDamageState == DamageState.Undamaged || hut.Repairing || hut.Bridge.GetHut(0) == null || hut.Bridge.GetHut(1) == null) - return; + if (legacyHut != null) + { + if (legacyHut.BridgeDamageState == DamageState.Undamaged || legacyHut.Repairing || legacyHut.Bridge.GetHut(0) == null || legacyHut.Bridge.GetHut(1) == null) + return; - hut.Repair(self); + legacyHut.Repair(self); + } + else if (hut != null) + { + if (hut.BridgeDamageState == DamageState.Undamaged || hut.Repairing) + return; + + hut.Repair(target, self); + } Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", notification, self.Owner.Faction.InternalName); } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 12f913b9e7..179b0fa288 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -274,7 +274,7 @@ - + @@ -503,7 +503,7 @@ - + @@ -777,6 +777,12 @@ + + + + + + diff --git a/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs b/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs index 979ecdf9c1..c586e7f8ed 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs @@ -78,7 +78,7 @@ namespace OpenRA.Mods.Common.Traits { readonly BuildingInfo building; readonly Bridge[] neighbours = new Bridge[2]; - readonly BridgeHut[] huts = new BridgeHut[2]; // Huts before this / first & after this / last + readonly LegacyBridgeHut[] huts = new LegacyBridgeHut[2]; // Huts before this / first & after this / last readonly Health health; readonly Actor self; readonly BridgeInfo info; @@ -88,7 +88,7 @@ namespace OpenRA.Mods.Common.Traits ushort template; Dictionary footprint; - public BridgeHut Hut { get; private set; } + public LegacyBridgeHut Hut { get; private set; } public bool IsDangling { get { return isDangling.Value; } } public Bridge(Actor self, BridgeInfo info) @@ -135,7 +135,7 @@ namespace OpenRA.Mods.Common.Traits return tileSet.GetTerrainIndex(new TerrainTile(template, (byte)index)); } - public void LinkNeighbouringBridges(World world, BridgeLayer bridges) + public void LinkNeighbouringBridges(World world, LegacyBridgeLayer bridges) { for (var d = 0; d <= 1; d++) { @@ -152,7 +152,7 @@ namespace OpenRA.Mods.Common.Traits } } - internal void AddHut(BridgeHut hut) + internal void AddHut(LegacyBridgeHut hut) { // TODO: This method is incomprehensible and fragile, and should be rewritten. if (huts[0] == huts[1]) @@ -170,8 +170,8 @@ namespace OpenRA.Mods.Common.Traits Hut = null; } - public BridgeHut GetHut(int index) { return huts[index]; } - public Bridge GetNeighbor(int[] offset, BridgeLayer bridges) + public LegacyBridgeHut GetHut(int index) { return huts[index]; } + public Bridge GetNeighbor(int[] offset, LegacyBridgeLayer bridges) { if (offset == null) return null; diff --git a/OpenRA.Mods.Common/Traits/Buildings/BridgeHut.cs b/OpenRA.Mods.Common/Traits/Buildings/BridgeHut.cs index f0f5449750..f18bfa6661 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BridgeHut.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BridgeHut.cs @@ -9,6 +9,7 @@ */ #endregion +using System.Collections.Generic; using System.Linq; using OpenRA.Traits; @@ -17,40 +18,206 @@ namespace OpenRA.Mods.Common.Traits [Desc("Allows bridges to be targeted for demolition and repair.")] class BridgeHutInfo : IDemolishableInfo, ITraitInfo { + [Desc("Bridge types to act on")] + public readonly string[] Types = { "GroundLevelBridge" }; + + [Desc("Offsets to look for adjacent bridges to act on")] + public readonly CVec[] NeighbourOffsets = { }; + + [Desc("Delay between each segment repair step")] + public readonly int RepairPropagationDelay = 20; + + [Desc("Delay between each segment demolish step")] + public readonly int DemolishPropagationDelay = 5; + + [Desc("Hide the repair cursor if the bridge is only damaged (not destroyed)")] + public readonly bool RequireForceAttackForHeal = false; + public bool IsValidTarget(ActorInfo actorInfo, Actor saboteur) { return false; } // TODO: bridges don't support frozen under fog - public object Create(ActorInitializer init) { return new BridgeHut(init); } + public object Create(ActorInitializer init) { return new BridgeHut(init.World, this); } } - class BridgeHut : IDemolishable + class BridgeHut : INotifyCreated, IDemolishable, ITick { - public readonly Bridge FirstBridge; - public readonly Bridge Bridge; - public DamageState BridgeDamageState { get { return Bridge.AggregateDamageState(); } } - public bool Repairing { get { return repairDirections > 0; } } - int repairDirections = 0; + public readonly BridgeHutInfo Info; + readonly BridgeLayer bridgeLayer; - public BridgeHut(ActorInitializer init) + // Fixed at map load + readonly List segmentLocations = new List(); + + // Changes as segments are killed and repaired + readonly Dictionary segments = new Dictionary(); + readonly HashSet dirtyLocations = new HashSet(); + + // Enabled during a repair action + int repairStep; + int repairDelay; + Actor repairRepairer; + + // Enabled during a demolish action + int demolishStep; + int demolishDelay; + Actor demolishSaboteur; + + public BridgeHut(World world, BridgeHutInfo info) { - Bridge = init.Get().ActorValue.Trait(); - Bridge.AddHut(this); - FirstBridge = Bridge.Enumerate(0, true).Last(); + Info = info; + bridgeLayer = world.WorldActor.Trait(); } - public void Repair(Actor repairer) + void INotifyCreated.Created(Actor self) { - repairDirections = Bridge.GetHut(0) != this && Bridge.GetHut(1) != this ? 2 : 1; - Bridge.Do((b, d) => b.Repair(repairer, d, () => repairDirections--)); + self.World.AddFrameEndTask(w => + { + // Bridge segments and huts are expected to be placed in the map + // editor or spawned during the normal actor loading + // + // The number and location of bridge segments are calculated here, + // and assumed to not change for the remaining lifetime of the world + // + // Bridge segment footprints and neighbour offsets are assumed to remain + // the same when a segment is destroyed or repaired. + var seed = Info.NeighbourOffsets.Select(v => self.Location + v); + var processed = new HashSet(); + while (true) + { + var step = NextNeighbourStep(seed, processed).ToList(); + if (!step.Any()) + break; + + foreach (var s in step) + segments[s.Location] = s; + + segmentLocations.Add(step.Select(s => s.Location).ToArray()); + seed = step.SelectMany(s => s.NeighbourOffsets.Select(n => s.Location + n)).ToList(); + } + + repairStep = demolishStep = segmentLocations.Count; + }); + } + + void ITick.Tick(Actor self) + { + // Update any dead segments + dirtyLocations.Clear(); + foreach (var kv in segments) + if (!kv.Value.Valid) + dirtyLocations.Add(kv.Key); + + foreach (var c in dirtyLocations) + segments[c] = bridgeLayer[c].TraitOrDefault(); + + if (repairStep < segmentLocations.Count && --repairDelay <= 0) + RepairStep(); + + if (demolishStep < segmentLocations.Count && --demolishDelay <= 0) + DemolishStep(); + } + + IEnumerable NextNeighbourStep(IEnumerable seed, HashSet processed) + { + foreach (var c in seed) + { + var bridge = bridgeLayer[c]; + if (bridge == null) + continue; + + var segment = bridge.TraitOrDefault(); + if (segment != null && Info.Types.Contains(segment.Type) && processed.Add(segment.Location)) + yield return segment; + } + } + + public void Repair(Actor self, Actor repairer) + { + if (Info.RepairPropagationDelay > 0) + { + repairStep = 0; + repairRepairer = repairer; + RepairStep(); + } + else + foreach (var s in segments.Values) + s.Repair(repairer); + } + + public void RepairStep() + { + // Find the next segment that needs to be repaired + while (repairStep < segmentLocations.Count) + { + var stepDamage = segmentLocations[repairStep] + .Select(c => segments[c]) + .Max(s => s.DamageState); + + if (stepDamage > DamageState.Undamaged) + break; + + repairStep++; + } + + if (repairStep < segmentLocations.Count) + foreach (var c in segmentLocations[repairStep]) + segments[c].Repair(repairRepairer); + + repairDelay = Info.RepairPropagationDelay; } public void Demolish(Actor self, Actor saboteur) { - Bridge.Do((b, d) => b.Demolish(saboteur, d)); + if (Info.DemolishPropagationDelay > 0) + { + demolishStep = 0; + demolishSaboteur = saboteur; + DemolishStep(); + } + else + foreach (var s in segments.Values) + s.Demolish(saboteur); + } + + public void DemolishStep() + { + // Find the next segment to demolish + while (demolishStep < segmentLocations.Count) + { + var stepDamage = segmentLocations[demolishStep] + .Select(c => segments[c]) + .Max(s => s.DamageState); + + if (stepDamage < DamageState.Dead) + break; + + demolishStep++; + } + + if (demolishStep < segmentLocations.Count) + foreach (var c in segmentLocations[demolishStep]) + segments[c].Demolish(demolishSaboteur); + + demolishDelay = Info.DemolishPropagationDelay; + + // Always advance at least one step (prevents sticking on placeholders) + demolishStep++; } public bool IsValidTarget(Actor self, Actor saboteur) { - return BridgeDamageState != DamageState.Dead; + return true; } + + public DamageState BridgeDamageState + { + get + { + if (!segments.Any()) + return DamageState.Undamaged; + + return segments.Values.Max(s => s.DamageState); + } + } + + public bool Repairing { get { return repairStep < segmentLocations.Count; } } } } diff --git a/OpenRA.Mods.Common/Traits/Buildings/BridgePlaceholder.cs b/OpenRA.Mods.Common/Traits/Buildings/BridgePlaceholder.cs new file mode 100644 index 0000000000..bf788c406a --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/BridgePlaceholder.cs @@ -0,0 +1,84 @@ +#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.Linq; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Placeholder actor used for dead segments and bridge end ramps.")] + class BridgePlaceholderInfo : ITraitInfo + { + public readonly string Type = "GroundLevelBridge"; + + public readonly DamageState DamageState = DamageState.Undamaged; + + [Desc("Actor type to replace with on repair.")] + [ActorReference] public readonly string ReplaceWithActor = null; + + public readonly CVec[] NeighbourOffsets = { }; + + public object Create(ActorInitializer init) { return new BridgePlaceholder(init.Self, this); } + } + + class BridgePlaceholder : IBridgeSegment, INotifyAddedToWorld, INotifyRemovedFromWorld + { + public readonly BridgePlaceholderInfo Info; + readonly Actor self; + readonly BridgeLayer bridgeLayer; + + public BridgePlaceholder(Actor self, BridgePlaceholderInfo info) + { + Info = info; + this.self = self; + bridgeLayer = self.World.WorldActor.Trait(); + } + + void INotifyAddedToWorld.AddedToWorld(Actor self) + { + bridgeLayer.Add(self); + } + + void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) + { + bridgeLayer.Remove(self); + } + + void IBridgeSegment.Repair(Actor repairer) + { + if (Info.ReplaceWithActor == null) + return; + + self.World.AddFrameEndTask(w => + { + self.Dispose(); + + w.CreateActor(Info.ReplaceWithActor, new TypeDictionary + { + new LocationInit(self.Location), + new OwnerInit(self.Owner), + }); + }); + } + + void IBridgeSegment.Demolish(Actor saboteur) + { + // Do nothing + } + + string IBridgeSegment.Type { get { return Info.Type; } } + 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; } } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Buildings/GroundLevelBridge.cs b/OpenRA.Mods.Common/Traits/Buildings/GroundLevelBridge.cs new file mode 100644 index 0000000000..2fb1d3c5f5 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/GroundLevelBridge.cs @@ -0,0 +1,119 @@ +#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.GameRules; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Bridge actor that can't be passed underneath.")] + class GroundLevelBridgeInfo : ITraitInfo, IRulesetLoaded, Requires, Requires + { + public readonly string TerrainType = "Bridge"; + + public readonly string Type = "GroundLevelBridge"; + + public readonly CVec[] NeighbourOffsets = { }; + + [Desc("The name of the weapon to use when demolishing the bridge")] + [WeaponReference] public readonly string DemolishWeapon = "Demolish"; + + public WeaponInfo DemolishWeaponInfo { get; private set; } + + public void RulesetLoaded(Ruleset rules, ActorInfo ai) { DemolishWeaponInfo = rules.Weapons[DemolishWeapon.ToLowerInvariant()]; } + + public object Create(ActorInitializer init) { return new GroundLevelBridge(init.Self, this); } + } + + class GroundLevelBridge : IBridgeSegment, INotifyAddedToWorld, INotifyRemovedFromWorld + { + public readonly GroundLevelBridgeInfo Info; + readonly Actor self; + readonly BridgeLayer bridgeLayer; + readonly IEnumerable cells; + readonly Health health; + + public GroundLevelBridge(Actor self, GroundLevelBridgeInfo info) + { + Info = info; + this.self = self; + health = self.Trait(); + + bridgeLayer = self.World.WorldActor.Trait(); + var buildingInfo = self.Info.TraitInfo(); + cells = FootprintUtils.PathableTiles(self.Info.Name, buildingInfo, self.Location); + } + + void UpdateTerrain(Actor self, byte terrainIndex) + { + foreach (var cell in cells) + self.World.Map.CustomTerrain[cell] = terrainIndex; + + var domainIndex = self.World.WorldActor.TraitOrDefault(); + if (domainIndex != null) + domainIndex.UpdateCells(self.World, cells); + } + + void INotifyAddedToWorld.AddedToWorld(Actor self) + { + bridgeLayer.Add(self); + + var tileSet = self.World.Map.Rules.TileSet; + var terrainIndex = tileSet.GetTerrainIndex(Info.TerrainType); + UpdateTerrain(self, terrainIndex); + KillInvalidActorsInFootprint(self); + } + + void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) + { + bridgeLayer.Remove(self); + + UpdateTerrain(self, byte.MaxValue); + KillInvalidActorsInFootprint(self); + } + + void KillInvalidActorsInFootprint(Actor self) + { + foreach (var c in cells) + foreach (var a in self.World.ActorMap.GetActorsAt(c)) + if (a.Info.HasTraitInfo() && !a.Trait().CanEnterCell(c)) + a.Kill(self); + } + + void IBridgeSegment.Repair(Actor repairer) + { + health.InflictDamage(self, repairer, new Damage(-health.MaxHP), true); + } + + void IBridgeSegment.Demolish(Actor saboteur) + { + self.World.AddFrameEndTask(w => + { + if (self.IsDead) + return; + + // Use .FromPos since this actor is dead. Cannot use Target.FromActor + Info.DemolishWeaponInfo.Impact(Target.FromPos(self.CenterPosition), saboteur, Enumerable.Empty()); + + self.World.WorldActor.Trait().AddEffect(15, self.CenterPosition, 6); + self.Kill(saboteur); + }); + } + + string IBridgeSegment.Type { get { return Info.Type; } } + DamageState IBridgeSegment.DamageState { get { return self.GetDamageState(); } } + bool IBridgeSegment.Valid { get { return self.IsInWorld; } } + CVec[] IBridgeSegment.NeighbourOffsets { get { return Info.NeighbourOffsets; } } + CPos IBridgeSegment.Location { get { return self.Location; } } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Buildings/LegacyBridgeHut.cs b/OpenRA.Mods.Common/Traits/Buildings/LegacyBridgeHut.cs new file mode 100644 index 0000000000..6b18a5b73a --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/LegacyBridgeHut.cs @@ -0,0 +1,56 @@ +#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.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Allows bridges to be targeted for demolition and repair.")] + class LegacyBridgeHutInfo : IDemolishableInfo, ITraitInfo + { + public bool IsValidTarget(ActorInfo actorInfo, Actor saboteur) { return false; } // TODO: bridges don't support frozen under fog + + public object Create(ActorInitializer init) { return new LegacyBridgeHut(init); } + } + + class LegacyBridgeHut : IDemolishable + { + public readonly Bridge FirstBridge; + public readonly Bridge Bridge; + public DamageState BridgeDamageState { get { return Bridge.AggregateDamageState(); } } + public bool Repairing { get { return repairDirections > 0; } } + int repairDirections = 0; + + public LegacyBridgeHut(ActorInitializer init) + { + Bridge = init.Get().ActorValue.Trait(); + Bridge.AddHut(this); + FirstBridge = Bridge.Enumerate(0, true).Last(); + } + + public void Repair(Actor repairer) + { + repairDirections = Bridge.GetHut(0) != this && Bridge.GetHut(1) != this ? 2 : 1; + Bridge.Do((b, d) => b.Repair(repairer, d, () => repairDirections--)); + } + + public void Demolish(Actor self, Actor saboteur) + { + Bridge.Do((b, d) => b.Demolish(saboteur, d)); + } + + public bool IsValidTarget(Actor self, Actor saboteur) + { + return BridgeDamageState != DamageState.Dead; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs b/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs index 9a7a513aef..2deaad02e5 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs @@ -85,6 +85,14 @@ namespace OpenRA.Mods.Common.Traits.Render public class RenderSprites : IRender, ITick, INotifyOwnerChanged, INotifyEffectiveOwnerChanged, IActorPreviewInitModifier { + static readonly Pair[] DamagePrefixes = + { + Pair.New(DamageState.Critical, "critical-"), + Pair.New(DamageState.Heavy, "damaged-"), + Pair.New(DamageState.Medium, "scratched-"), + Pair.New(DamageState.Light, "scuffed-") + }; + class AnimationWrapper { public readonly AnimationWithOffset Animation; @@ -196,27 +204,27 @@ namespace OpenRA.Mods.Common.Traits.Render anims.RemoveAll(a => a.Animation == anim); } - public static string NormalizeSequence(Animation anim, DamageState state, string sequence) + public static string UnnormalizeSequence(string sequence) { - var states = new Pair[] - { - Pair.New(DamageState.Critical, "critical-"), - Pair.New(DamageState.Heavy, "damaged-"), - Pair.New(DamageState.Medium, "scratched-"), - Pair.New(DamageState.Light, "scuffed-") - }; - // Remove existing damage prefix - foreach (var s in states) + foreach (var s in DamagePrefixes) { - if (sequence.StartsWith(s.Second)) + if (sequence.StartsWith(s.Second, StringComparison.Ordinal)) { sequence = sequence.Substring(s.Second.Length); break; } } - foreach (var s in states) + return sequence; + } + + public static string NormalizeSequence(Animation anim, DamageState state, string sequence) + { + // Remove any existing damage prefix + sequence = UnnormalizeSequence(sequence); + + foreach (var s in DamagePrefixes) if (state >= s.First && anim.HasSequence(s.Second + sequence)) return s.Second + sequence; 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/OpenRA.Mods.Common/Traits/RepairsBridges.cs b/OpenRA.Mods.Common/Traits/RepairsBridges.cs index d7c730e288..ddf8c0011b 100644 --- a/OpenRA.Mods.Common/Traits/RepairsBridges.cs +++ b/OpenRA.Mods.Common/Traits/RepairsBridges.cs @@ -17,7 +17,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - [Desc("Can enter a BridgeHut to trigger a repair.")] + [Desc("Can enter a BridgeHut or LegacyBridgeHut to trigger a repair.")] class RepairsBridgesInfo : ITraitInfo { [VoiceReference] public readonly string Voice = "Action"; @@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits "Possible values are Exit, Suicide, Dispose.")] public readonly EnterBehaviour EnterBehaviour = EnterBehaviour.Dispose; - [Desc("Cursor to use when targeting a BridgeHut of an unrepaired bridge.")] + [Desc("Cursor to use when targeting an unrepaired bridge.")] public readonly string TargetCursor = "goldwrench"; [Desc("Cursor to use when repairing is denied.")] @@ -65,22 +65,34 @@ namespace OpenRA.Mods.Common.Traits if (order.OrderString != "RepairBridge") return null; - var hut = order.TargetActor.TraitOrDefault(); - if (hut == null) - return null; + var legacyHut = order.TargetActor.TraitOrDefault(); + if (legacyHut != null) + return legacyHut.BridgeDamageState == DamageState.Undamaged || legacyHut.Repairing || legacyHut.Bridge.IsDangling ? null : info.Voice; - return hut.BridgeDamageState == DamageState.Undamaged || hut.Repairing || hut.Bridge.IsDangling ? null : info.Voice; + var hut = order.TargetActor.TraitOrDefault(); + if (hut != null) + return hut.BridgeDamageState == DamageState.Undamaged || hut.Repairing ? null : info.Voice; + + return null; } public void ResolveOrder(Actor self, Order order) { if (order.OrderString == "RepairBridge") { + var legacyHut = order.TargetActor.TraitOrDefault(); var hut = order.TargetActor.TraitOrDefault(); - if (hut == null) - return; - - if (hut.BridgeDamageState == DamageState.Undamaged || hut.Repairing || hut.Bridge.IsDangling) + if (legacyHut != null) + { + if (legacyHut.BridgeDamageState == DamageState.Undamaged || legacyHut.Repairing || legacyHut.Bridge.IsDangling) + return; + } + else if (hut != null) + { + if (hut.BridgeDamageState == DamageState.Undamaged || hut.Repairing) + return; + } + else return; self.SetTargetLine(Target.FromOrder(self.World, order), Color.Yellow); @@ -102,22 +114,36 @@ namespace OpenRA.Mods.Common.Traits public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor) { - var hut = target.TraitOrDefault(); - if (hut == null) - return false; - - // Require force attack to heal partially damaged bridges to avoid unnecessary cursor noise - var damage = hut.BridgeDamageState; - if (!modifiers.HasModifier(TargetModifiers.ForceAttack) && damage != DamageState.Dead) - return false; - // Obey force moving onto bridges if (modifiers.HasModifier(TargetModifiers.ForceMove)) return false; - // Can't repair a bridge that is undamaged, already under repair, or dangling - if (damage == DamageState.Undamaged || hut.Repairing || hut.Bridge.IsDangling) - cursor = info.TargetBlockedCursor; + var legacyHut = target.TraitOrDefault(); + var hut = target.TraitOrDefault(); + if (legacyHut != null) + { + // Require force attack to heal partially damaged bridges to avoid unnecessary cursor noise + var damage = legacyHut.BridgeDamageState; + if (!modifiers.HasModifier(TargetModifiers.ForceAttack) && damage != DamageState.Dead) + return false; + + // Can't repair a bridge that is undamaged, already under repair, or dangling + if (damage == DamageState.Undamaged || legacyHut.Repairing || legacyHut.Bridge.IsDangling) + cursor = info.TargetBlockedCursor; + } + else if (hut != null) + { + // Require force attack to heal partially damaged bridges to avoid unnecessary cursor noise + var damage = hut.BridgeDamageState; + if (hut.Info.RequireForceAttackForHeal && !modifiers.HasModifier(TargetModifiers.ForceAttack) && damage != DamageState.Dead) + return false; + + // Can't repair a bridge that is undamaged, already under repair, or dangling + if (damage == DamageState.Undamaged || hut.Repairing) + cursor = info.TargetBlockedCursor; + } + else + return false; return true; } diff --git a/OpenRA.Mods.Common/Traits/World/BridgeLayer.cs b/OpenRA.Mods.Common/Traits/World/BridgeLayer.cs index dfcac3caaf..a25b5ad3b8 100644 --- a/OpenRA.Mods.Common/Traits/World/BridgeLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/BridgeLayer.cs @@ -9,103 +9,51 @@ */ #endregion -using System.Collections.Generic; -using System.Linq; -using OpenRA.Graphics; -using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - class BridgeLayerInfo : ITraitInfo + interface IBridgeSegment { - [ActorReference] - public readonly string[] Bridges = { "bridge1", "bridge2" }; + void Repair(Actor repairer); + void Demolish(Actor saboteur); - public object Create(ActorInitializer init) { return new BridgeLayer(init.Self, this); } + string Type { get; } + DamageState DamageState { get; } + CVec[] NeighbourOffsets { get; } + bool Valid { get; } + CPos Location { get; } } - class BridgeLayer : IWorldLoaded + class BridgeLayerInfo : ITraitInfo { - readonly BridgeLayerInfo info; - readonly Dictionary> bridgeTypes = new Dictionary>(); + public object Create(ActorInitializer init) { return new BridgeLayer(init.World); } + } - CellLayer bridges; + class BridgeLayer + { + readonly CellLayer bridges; - public BridgeLayer(Actor self, BridgeLayerInfo info) + public BridgeLayer(World world) { - this.info = info; + bridges = new CellLayer(world.Map); } - public void WorldLoaded(World w, WorldRenderer wr) + public Actor this[CPos cell] { get { return bridges[cell]; } } + + public void Add(Actor b) { - bridges = new CellLayer(w.Map); - - // Build a list of templates that should be overlayed with bridges - foreach (var bridge in info.Bridges) - { - var bi = w.Map.Rules.Actors[bridge].TraitInfo(); - foreach (var template in bi.Templates) - bridgeTypes.Add(template.First, Pair.New(bridge, template.Second)); - } - - // Take all templates to overlay from the map - foreach (var cell in w.Map.AllCells.Where(cell => bridgeTypes.ContainsKey(w.Map.Tiles[cell].Type))) - ConvertBridgeToActor(w, cell); - - // Link adjacent (long)-bridges so that artwork is updated correctly - foreach (var p in w.ActorsWithTrait()) - p.Trait.LinkNeighbouringBridges(w, this); + var buildingInfo = b.Info.TraitInfo(); + foreach (var c in FootprintUtils.PathableTiles(b.Info.Name, buildingInfo, b.Location)) + bridges[c] = b; } - void ConvertBridgeToActor(World w, CPos cell) + public void Remove(Actor b) { - // This cell already has a bridge overlaying it from a previous iteration - if (bridges[cell] != null) - return; - - // Correlate the tile "image" aka subtile with its position to find the template origin - var tile = w.Map.Tiles[cell].Type; - var index = w.Map.Tiles[cell].Index; - var template = w.Map.Rules.TileSet.Templates[tile]; - var ni = cell.X - index % template.Size.X; - var nj = cell.Y - index / template.Size.X; - - // Create a new actor for this bridge and keep track of which subtiles this bridge includes - var bridge = w.CreateActor(bridgeTypes[tile].First, new TypeDictionary - { - new LocationInit(new CPos(ni, nj)), - new OwnerInit(w.WorldActor.Owner), - new HealthInit(bridgeTypes[tile].Second, true), - }).Trait(); - - var subTiles = new Dictionary(); - var mapTiles = w.Map.Tiles; - - // For each subtile in the template - for (byte ind = 0; ind < template.Size.X * template.Size.Y; ind++) - { - // Where do we expect to find the subtile - var subtile = new CPos(ni + ind % template.Size.X, nj + ind / template.Size.X); - - // This isn't the bridge you're looking for - if (!mapTiles.Contains(subtile) || mapTiles[subtile].Type != tile || mapTiles[subtile].Index != ind) - continue; - - subTiles.Add(subtile, ind); - bridges[subtile] = bridge; - } - - bridge.Create(tile, subTiles); - } - - // Used to check for neighbouring bridges - public Bridge GetBridge(CPos cell) - { - if (!bridges.Contains(cell)) - return null; - - return bridges[cell]; + var buildingInfo = b.Info.TraitInfo(); + foreach (var c in FootprintUtils.PathableTiles(b.Info.Name, buildingInfo, b.Location)) + if (bridges[c] == b) + bridges[c] = null; } } } diff --git a/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs b/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs new file mode 100644 index 0000000000..e65742de0b --- /dev/null +++ b/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs @@ -0,0 +1,111 @@ +#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.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + class LegacyBridgeLayerInfo : ITraitInfo + { + [ActorReference] + public readonly string[] Bridges = { "bridge1", "bridge2" }; + + public object Create(ActorInitializer init) { return new LegacyBridgeLayer(init.Self, this); } + } + + class LegacyBridgeLayer : IWorldLoaded + { + readonly LegacyBridgeLayerInfo info; + readonly Dictionary> bridgeTypes = new Dictionary>(); + + CellLayer bridges; + + public LegacyBridgeLayer(Actor self, LegacyBridgeLayerInfo info) + { + this.info = info; + } + + public void WorldLoaded(World w, WorldRenderer wr) + { + bridges = new CellLayer(w.Map); + + // Build a list of templates that should be overlayed with bridges + foreach (var bridge in info.Bridges) + { + var bi = w.Map.Rules.Actors[bridge].TraitInfo(); + foreach (var template in bi.Templates) + bridgeTypes.Add(template.First, Pair.New(bridge, template.Second)); + } + + // Take all templates to overlay from the map + foreach (var cell in w.Map.AllCells.Where(cell => bridgeTypes.ContainsKey(w.Map.Tiles[cell].Type))) + ConvertBridgeToActor(w, cell); + + // Link adjacent (long)-bridges so that artwork is updated correctly + foreach (var p in w.ActorsWithTrait()) + p.Trait.LinkNeighbouringBridges(w, this); + } + + void ConvertBridgeToActor(World w, CPos cell) + { + // This cell already has a bridge overlaying it from a previous iteration + if (bridges[cell] != null) + return; + + // Correlate the tile "image" aka subtile with its position to find the template origin + var tile = w.Map.Tiles[cell].Type; + var index = w.Map.Tiles[cell].Index; + var template = w.Map.Rules.TileSet.Templates[tile]; + var ni = cell.X - index % template.Size.X; + var nj = cell.Y - index / template.Size.X; + + // Create a new actor for this bridge and keep track of which subtiles this bridge includes + var bridge = w.CreateActor(bridgeTypes[tile].First, new TypeDictionary + { + new LocationInit(new CPos(ni, nj)), + new OwnerInit(w.WorldActor.Owner), + new HealthInit(bridgeTypes[tile].Second, true), + }).Trait(); + + var subTiles = new Dictionary(); + var mapTiles = w.Map.Tiles; + + // For each subtile in the template + for (byte ind = 0; ind < template.Size.X * template.Size.Y; ind++) + { + // Where do we expect to find the subtile + var subtile = new CPos(ni + ind % template.Size.X, nj + ind / template.Size.X); + + // This isn't the bridge you're looking for + if (!mapTiles.Contains(subtile) || mapTiles[subtile].Type != tile || mapTiles[subtile].Index != ind) + continue; + + subTiles.Add(subtile, ind); + bridges[subtile] = bridge; + } + + bridge.Create(tile, subTiles); + } + + // Used to check for neighbouring bridges + public Bridge GetBridge(CPos cell) + { + if (!bridges.Contains(cell)) + return null; + + return bridges[cell]; + } + } +} diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index d0bac4db7c..35d2495410 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -532,6 +532,16 @@ namespace OpenRA.Mods.Common.UtilityCommands } } + // Reworking bridge logic + if (engineVersion < 20161210) + { + if (node.Key == "BridgeHut") + RenameNodeKey(node, "LegacyBridgeHut"); + + if (node.Key == "BridgeLayer") + RenameNodeKey(node, "LegacyBridgeLayer"); + } + UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1); } diff --git a/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj b/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj index b7c6e58abe..7bb2cea30b 100644 --- a/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj +++ b/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj @@ -56,7 +56,6 @@ - diff --git a/OpenRA.Mods.TS/Traits/Buildings/LowBridge.cs b/OpenRA.Mods.TS/Traits/Buildings/LowBridge.cs deleted file mode 100644 index be90d6cacd..0000000000 --- a/OpenRA.Mods.TS/Traits/Buildings/LowBridge.cs +++ /dev/null @@ -1,72 +0,0 @@ -#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 OpenRA.Graphics; -using OpenRA.Mods.Common.Traits; -using OpenRA.Primitives; -using OpenRA.Traits; - -namespace OpenRA.Mods.TS.Traits -{ - [Desc("Bridge actor that can't be passed underneath.")] - class LowBridgeInfo : ITraitInfo, Requires - { - public readonly string TerrainType = "Bridge"; - - public object Create(ActorInitializer init) { return new LowBridge(init.Self, this); } - } - - class LowBridge : INotifyAddedToWorld, INotifyRemovedFromWorld - { - readonly LowBridgeInfo info; - readonly IEnumerable cells; - - public LowBridge(Actor self, LowBridgeInfo info) - { - this.info = info; - - var buildingInfo = self.Info.TraitInfo(); - cells = FootprintUtils.PathableTiles(self.Info.Name, buildingInfo, self.Location); - } - - void UpdateTerrain(Actor self, byte terrainIndex) - { - foreach (var cell in cells) - self.World.Map.CustomTerrain[cell] = terrainIndex; - - var domainIndex = self.World.WorldActor.TraitOrDefault(); - if (domainIndex != null) - domainIndex.UpdateCells(self.World, cells); - } - - void INotifyAddedToWorld.AddedToWorld(Actor self) - { - var tileSet = self.World.Map.Rules.TileSet; - var terrainIndex = tileSet.GetTerrainIndex(info.TerrainType); - UpdateTerrain(self, terrainIndex); - } - - void KillUnitsOnBridge(Actor self) - { - foreach (var c in cells) - foreach (var a in self.World.ActorMap.GetActorsAt(c)) - if (a.Info.HasTraitInfo() && !a.Trait().CanEnterCell(c)) - a.Kill(self); - } - - void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) - { - UpdateTerrain(self, byte.MaxValue); - KillUnitsOnBridge(self); - } - } -} diff --git a/mods/cnc/rules/civilian.yaml b/mods/cnc/rules/civilian.yaml index 7dd62fbb2f..4c8e909a46 100644 --- a/mods/cnc/rules/civilian.yaml +++ b/mods/cnc/rules/civilian.yaml @@ -448,7 +448,7 @@ BRIDGEHUT: Dimensions: 2,2 CustomSelectionSize: CustomBounds: 48,48 - BridgeHut: + LegacyBridgeHut: Targetable: TargetTypes: BridgeHut, C4 diff --git a/mods/cnc/rules/world.yaml b/mods/cnc/rules/world.yaml index 85a0516be6..c9767972a4 100644 --- a/mods/cnc/rules/world.yaml +++ b/mods/cnc/rules/world.yaml @@ -58,7 +58,7 @@ World: HelpCommand: ScreenShaker: BuildingInfluence: - BridgeLayer: + LegacyBridgeLayer: Bridges: bridge1, bridge2, bridge3, bridge4 ProductionQueueFromSelection: ProductionTabsWidget: PRODUCTION_TABS diff --git a/mods/ra/rules/civilian.yaml b/mods/ra/rules/civilian.yaml index a9152de423..cb8f2da866 100644 --- a/mods/ra/rules/civilian.yaml +++ b/mods/ra/rules/civilian.yaml @@ -522,7 +522,7 @@ BRIDGEHUT: Selectable: Bounds: 48,48 Priority: 2 - BridgeHut: + LegacyBridgeHut: Targetable: TargetTypes: BridgeHut, C4 @@ -534,7 +534,7 @@ BRIDGEHUT.small: Selectable: Bounds: 24,24 Priority: 2 - BridgeHut: + LegacyBridgeHut: Targetable: TargetTypes: BridgeHut, C4 diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index b8b4f374eb..8f6e232f0b 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -99,7 +99,7 @@ World: BuildingInfluence: ProductionQueueFromSelection: ProductionPaletteWidget: PRODUCTION_PALETTE - BridgeLayer: + LegacyBridgeLayer: Bridges: bridge1, bridge2, br1, br2, br3, sbridge1, sbridge2, sbridge3, sbridge4 CustomTerrainDebugOverlay: CrateSpawner: diff --git a/mods/ts/cursors.yaml b/mods/ts/cursors.yaml index 3bd577ab17..01ad0ad6c3 100644 --- a/mods/ts/cursors.yaml +++ b/mods/ts/cursors.yaml @@ -141,10 +141,10 @@ Cursors: Start: 120 Length: 9 goldwrench: - Start: 170 - Length: 24 + Start: 150 + Length: 20 goldwrench-blocked: #TODO - Start: 213 + Start: 190 Length: 1 ioncannon: Start: 279 diff --git a/mods/ts/maps/arivruns/map.yaml b/mods/ts/maps/arivruns/map.yaml index 7019cd5b07..60a7200aff 100644 --- a/mods/ts/maps/arivruns/map.yaml +++ b/mods/ts/maps/arivruns/map.yaml @@ -14,7 +14,7 @@ Bounds: 2,4,130,242 Visibility: Lobby -Categories: Conquest, Original TS Map +Categories: Conquest, Original TS Map, Playable Players: PlayerReference@Neutral: diff --git a/mods/ts/maps/tread_l/map.yaml b/mods/ts/maps/tread_l/map.yaml index f4339d5323..b2d32eb01b 100644 --- a/mods/ts/maps/tread_l/map.yaml +++ b/mods/ts/maps/tread_l/map.yaml @@ -1247,11 +1247,3 @@ Rules: World: GlobalLightingPaletteEffect: Ambient: 0.75 - LOBRDG_A: - Targetable: - TargetTypes: Ground, Building - RequiresForceFire: true - Health: - HP: 1000 - Armor: - Type: Concrete diff --git a/mods/ts/rules/bridges.yaml b/mods/ts/rules/bridges.yaml index 2408665e73..98cc31f891 100644 --- a/mods/ts/rules/bridges.yaml +++ b/mods/ts/rules/bridges.yaml @@ -1,49 +1,153 @@ +CABHUT: + Inherits: ^BasicBuilding + Tooltip: + Name: Bridge repair hut + Building: + Adjacent: 0 + Footprint: x + Dimensions: 1, 1 + BridgeHut: + NeighbourOffsets: -1,-1, -1,0, -1,1, 0,-1, 0,1, 1,-1, 1,0, 1,1 + RenderSprites: + Palette: player + Targetable: + TargetTypes: C4 + -SelectionDecorations: + -Demolishable: + +^LowBridgeRamp: + AlwaysVisible: + RenderSprites: + Palette: terraindecoration + WithSpriteBody: + AutoSelectionSize: + AppearsOnRadar: + RadarColorFromTerrain: + Terrain: Bridge + BodyOrientation: + UseClassicPerspectiveFudge: false + QuantizedFacings: 1 + Tooltip: + Name: Bridge + +^LowBridge: + Inherits: ^LowBridgeRamp + Targetable: + TargetTypes: Ground, Building + RequiresForceFire: true + Health: + HP: 500 + Armor: + Type: Concrete + LOBRDG_A: - Inherits: ^LowBridge_A + Inherits: ^LowBridge + Building: + Footprint: ___ + Dimensions: 3, 1 + GroundLevelBridge: + 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: ^LowBridge_A - -RenderSprites: - RenderSpritesEditorOnly: - Palette: terrainalpha + Inherits: LOBRDG_A EditorOnlyTooltip: Name: Dead Bridge - -LowBridge: + -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_B + Inherits: ^LowBridge + Building: + Footprint: _ _ _ + Dimensions: 1, 3 + GroundLevelBridge: + 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: ^LowBridge_B - -RenderSprites: - RenderSpritesEditorOnly: - Palette: terrainalpha + Inherits: LOBRDG_B EditorOnlyTooltip: Name: Dead Bridge - -LowBridge: + -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: ^LowBridge_B + Inherits: ^LowBridgeRamp + Building: + Footprint: _ _ _ + Dimensions: 1, 3 + BridgePlaceholder: + NeighbourOffsets: -1,1 EditorOnlyTooltip: Name: Bridge Ramp - Description: South East LOBRDG_R_NW: - Inherits: ^LowBridge_B + Inherits: ^LowBridgeRamp + Building: + Footprint: _ _ _ + Dimensions: 1, 3 + BridgePlaceholder: + NeighbourOffsets: 1,1 EditorOnlyTooltip: Name: Bridge Ramp - Description: North West LOBRDG_R_NE: - Inherits: ^LowBridge_A + Inherits: ^LowBridgeRamp + Building: + Footprint: ___ + Dimensions: 3, 1 + BridgePlaceholder: + NeighbourOffsets: 1,1 EditorOnlyTooltip: Name: Bridge Ramp - Description: North East LOBRDG_R_SW: - Inherits: ^LowBridge_A + Inherits: ^LowBridgeRamp + Building: + Footprint: ___ + Dimensions: 3, 1 + BridgePlaceholder: + NeighbourOffsets: 1,-1 EditorOnlyTooltip: Name: Bridge Ramp - Description: South West diff --git a/mods/ts/rules/civilian-structures.yaml b/mods/ts/rules/civilian-structures.yaml index 5e73693476..e6ece37203 100644 --- a/mods/ts/rules/civilian-structures.yaml +++ b/mods/ts/rules/civilian-structures.yaml @@ -645,19 +645,6 @@ CAARMR: Prerequisite: barracks.upgraded Capturable: -CABHUT: - Inherits: ^CivBuilding - Tooltip: - Name: Bridge repair hut - Building: - Adjacent: 0 - Footprint: x - Dimensions: 1, 1 - Health: - HP: 2000 - RenderSprites: - Palette: player - CACRSH01: Inherits: ^Decoration Tooltip: diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index c3d01b1bc5..29ac068747 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -901,36 +901,6 @@ LineBuildNode: Connections: 0,-1, 0,1 -^LowBridge: - AlwaysVisible: - RenderSprites: - Palette: terraindecoration - WithSpriteBody: - AutoSelectionSize: - AppearsOnRadar: - RadarColorFromTerrain: - Terrain: Bridge - BodyOrientation: - UseClassicPerspectiveFudge: false - QuantizedFacings: 1 - Tooltip: - Name: Bridge - Health: - -^LowBridge_A: - Inherits: ^LowBridge - Building: - Footprint: ___ - Dimensions: 3, 1 - LowBridge: - -^LowBridge_B: - Inherits: ^LowBridge - Building: - Footprint: _ _ _ - Dimensions: 1, 3 - LowBridge: - ^HealsOnTiberium: DamagedByTerrain: Damage: -2 diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index 1da114f2ac..f17fcb7957 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -99,6 +99,7 @@ World: SmokeType: largesmoke Sequence: largecraters ResourceLayer: + BridgeLayer: CustomTerrainDebugOverlay: ResourceClaimLayer: WarheadDebugOverlay: 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 diff --git a/mods/ts/weapons/explosions.yaml b/mods/ts/weapons/explosions.yaml index e419809df1..3b8c89ee11 100644 --- a/mods/ts/weapons/explosions.yaml +++ b/mods/ts/weapons/explosions.yaml @@ -78,3 +78,11 @@ LargeDebris: Image: dbrislg Sequences: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Shadow: true + +Demolish: + Warhead@1Dam: SpreadDamage + DamageTypes: DefaultDeath + Warhead@2Eff: CreateEffect + Explosions: large_twlt + ExplosionPalette: effectalpha75 + ImpactSounds: expnew09.aud \ No newline at end of file