diff --git a/CHANGELOG b/CHANGELOG index 52c4cc9bd8..0fdffab12a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,10 @@ NEW: + Dune 2000: + Added the Atreides grenadier from the 1.06 patch. + Red Alert: + Tanya can now plant C4 on bridges. + Tiberian Dawn: + Commando can now plant C4 on bridges. Engine: Converted Aircraft CruiseAltitude to world coordinates. Converted Health Radius to world coordinates. @@ -9,8 +15,7 @@ NEW: Altitude is no longer parsed from actor templates in maps. Specify CenterPosition instead. Run `OpenRA.Utility.exe --upgrade-mod 20131223` to automatically upgrade mod rules. Run `OpenRA.Utility.exe --upgrade-map 20131223` to automatically upgrade custom map rules. - Dune 2000: - Added the Atreides grenadier from the 1.06 patch. + Added a new trait Demolishable for buildings to handle the C4 demolition. 20131223: All mods: diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index c95ef77b3f..528720ad72 100755 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -77,6 +77,11 @@ namespace OpenRA.Traits public interface INotifyHarvest { void Harvested(Actor self, ResourceType resource); } public interface IAcceptInfiltrator { void OnInfiltrate(Actor self, Actor infiltrator); } + public interface IDemolishable + { + void Demolish(Actor self, Actor saboteur); + bool IsValidTarget(Actor self, Actor saboteur); + } public interface IStoreOre { int Capacity { get; } } public interface IToolTip { diff --git a/OpenRA.Mods.RA/Activities/Demolish.cs b/OpenRA.Mods.RA/Activities/Demolish.cs index 4371a2c723..cba9b993f7 100644 --- a/OpenRA.Mods.RA/Activities/Demolish.cs +++ b/OpenRA.Mods.RA/Activities/Demolish.cs @@ -41,8 +41,12 @@ namespace OpenRA.Mods.RA.Activities .Concat(self.Owner.PlayerActor.TraitsImplementing()) .Select(t => t.GetDamageModifier(self, null)).Product(); + var demolishable = target.Actor.TraitOrDefault(); + if (demolishable == null || !demolishable.IsValidTarget(target.Actor, self)) + return; + if (modifier > 0) - target.Actor.Kill(self); + demolishable.Demolish(target.Actor, self); }))); return NextActivity; diff --git a/OpenRA.Mods.RA/Bridge.cs b/OpenRA.Mods.RA/Bridge.cs index 6f3f6d9ec7..0c32fbb3b2 100644 --- a/OpenRA.Mods.RA/Bridge.cs +++ b/OpenRA.Mods.RA/Bridge.cs @@ -288,5 +288,35 @@ namespace OpenRA.Mods.RA return damage; } + + public void Demolish(Actor saboteur, bool continueNorth, bool continueSouth) + { + var initialDamage = Health.DamageState; + self.World.AddFrameEndTask(w => + { + Combat.DoExplosion(saboteur, "Demolish", self.CenterPosition); + self.World.WorldActor.Trait().AddEffect(15, self.CenterPosition, 6); + self.Kill(saboteur); + }); + + // Destroy adjacent spans (long bridges) + if (continueNorth && northNeighbour != null) + { + var delay = initialDamage == DamageState.Dead || NeighbourIsDeadShore(northNeighbour) ? + 0 : Info.RepairPropagationDelay; + + self.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, () => + northNeighbour.Demolish(saboteur, true, false)))); + } + + if (continueSouth && southNeighbour != null) + { + var delay = initialDamage == DamageState.Dead || NeighbourIsDeadShore(southNeighbour) ? + 0 : Info.RepairPropagationDelay; + + self.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, () => + southNeighbour.Demolish(saboteur, false, true)))); + } + } } } diff --git a/OpenRA.Mods.RA/BridgeHut.cs b/OpenRA.Mods.RA/BridgeHut.cs index c80605a763..51cd186196 100644 --- a/OpenRA.Mods.RA/BridgeHut.cs +++ b/OpenRA.Mods.RA/BridgeHut.cs @@ -17,7 +17,7 @@ namespace OpenRA.Mods.RA public object Create(ActorInitializer init) { return new BridgeHut(init); } } - class BridgeHut + class BridgeHut : IDemolishable { public Bridge bridge; @@ -31,6 +31,16 @@ namespace OpenRA.Mods.RA bridge.Repair(repairer, true, true); } + public void Demolish(Actor self, Actor saboteur) + { + bridge.Demolish(saboteur, true, true); + } + + public bool IsValidTarget(Actor self, Actor saboteur) + { + return BridgeDamageState == DamageState.Undamaged; + } + public DamageState BridgeDamageState { get { return bridge.AggregateDamageState(); } } } } diff --git a/OpenRA.Mods.RA/Buildings/Demolishable.cs b/OpenRA.Mods.RA/Buildings/Demolishable.cs new file mode 100644 index 0000000000..194298a755 --- /dev/null +++ b/OpenRA.Mods.RA/Buildings/Demolishable.cs @@ -0,0 +1,34 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 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 System; +using OpenRA.Traits; +using OpenRA.FileFormats; + +namespace OpenRA.Mods.RA +{ + [Desc("Handle demolitions from C4 explosives.")] + public class DemolishableInfo : TraitInfo { } + + public class Demolishable : IDemolishable + { + public Demolishable() { } + + public void Demolish(Actor self, Actor saboteur) + { + self.Kill(saboteur); + } + + public bool IsValidTarget(Actor self, Actor saboteur) + { + return true; + } + } +} + diff --git a/OpenRA.Mods.RA/C4Demolition.cs b/OpenRA.Mods.RA/C4Demolition.cs index efe655ce03..a233a4514a 100644 --- a/OpenRA.Mods.RA/C4Demolition.cs +++ b/OpenRA.Mods.RA/C4Demolition.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.RA public IEnumerable Orders { - get { yield return new TargetTypeOrderTargeter("C4", "C4", 6, "c4", true, false); } + get { yield return new C4DemolitionOrderTargeter(); } } public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) @@ -56,6 +56,10 @@ namespace OpenRA.Mods.RA if (target.Type != TargetType.Actor) return; + var demolishable = order.TargetActor.TraitOrDefault(); + if (demolishable == null || !demolishable.IsValidTarget(target.Actor, self)) + return; + if (!order.Queued) self.CancelActivity(); @@ -67,5 +71,33 @@ namespace OpenRA.Mods.RA { return order.OrderString == "C4" ? "Attack" : null; } + + class C4DemolitionOrderTargeter : UnitOrderTargeter + { + public C4DemolitionOrderTargeter() + : base("C4", 6, "c4", true, false) { } + + public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor) + { + // Obey force moving onto bridges + if (modifiers.HasModifier(TargetModifiers.ForceMove)) + return false; + + var demolishable = target.TraitOrDefault(); + if (demolishable == null || !demolishable.IsValidTarget(target, self)) + return false; + + return true; + } + + public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor) + { + // TODO: Bridges don't yet support FrozenUnderFog. + if (target.Actor.HasTrait()) + return false; + + return true; + } + } } } diff --git a/OpenRA.Mods.RA/Missions/Soviet01ClassicScript.cs b/OpenRA.Mods.RA/Missions/Soviet01ClassicScript.cs index 0b567afa2c..775165c0dc 100644 --- a/OpenRA.Mods.RA/Missions/Soviet01ClassicScript.cs +++ b/OpenRA.Mods.RA/Missions/Soviet01ClassicScript.cs @@ -125,12 +125,8 @@ namespace OpenRA.Mods.RA.Missions startJeep.QueueActivity(new Turn(128)); startJeep.QueueActivity(new CallFunc(() => { - var bridge = world.Actors - .Where(a => a.HasTrait() && !a.IsDead()) - .ClosestTo(startJeep); - Combat.DoExplosion(bridge, "Demolish", bridge.CenterPosition); - world.WorldActor.Trait().AddEffect(15, bridge.CenterPosition, 6); - bridge.Kill(bridge); + var bridge = world.Actors.Where(a => a.HasTrait()).ClosestTo(startJeep); + bridge.Trait().Demolish(bridge, startJeep); })); } diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 0e28d6d8a2..94c65c9e66 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -484,6 +484,7 @@ + diff --git a/OpenRA.Mods.RA/RepairsBridges.cs b/OpenRA.Mods.RA/RepairsBridges.cs index bc1bdcbd47..798da2f1aa 100644 --- a/OpenRA.Mods.RA/RepairsBridges.cs +++ b/OpenRA.Mods.RA/RepairsBridges.cs @@ -79,6 +79,10 @@ namespace OpenRA.Mods.RA 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 an undamaged bridge if (damage == DamageState.Undamaged) cursor = "goldwrench-blocked"; diff --git a/mods/cnc/rules/civilian.yaml b/mods/cnc/rules/civilian.yaml index 99aa5725d2..456fa5e6c9 100644 --- a/mods/cnc/rules/civilian.yaml +++ b/mods/cnc/rules/civilian.yaml @@ -363,7 +363,7 @@ BRIDGEHUT: Bounds: 48,48 BridgeHut: TargetableBuilding: - TargetTypes: BridgeHut + TargetTypes: BridgeHut, C4 C1: Inherits: ^CivInfantry @@ -455,4 +455,3 @@ VICE: QuantizedFacings: 8 PoisonedByTiberium: Weapon: Heal - diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index bbd885aeed..1350e10b9e 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -353,6 +353,7 @@ EngineerRepairable: Huntable: LuaScriptEvents: + Demolishable: ^CivBuilding: Inherits: ^Building @@ -404,6 +405,8 @@ Tooltip: Name: Field -WithBuildingExplosion: + -TargetableBuilding: + -Demolishable: RenderBuilding: Palette: terrain EditorAppearance: @@ -456,6 +459,7 @@ BodyOrientation: FrozenUnderFog: LuaScriptEvents: + Demolishable: ^Tree: Tooltip: diff --git a/mods/cnc/weapons.yaml b/mods/cnc/weapons.yaml index 73900210d7..bb8e997b2f 100644 --- a/mods/cnc/weapons.yaml +++ b/mods/cnc/weapons.yaml @@ -1027,3 +1027,7 @@ Claw: InfDeath: 1 Damage: 60 +Demolish: + Warhead: + ImpactSound: xplobig6.aud + Explosion: building \ No newline at end of file diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index 59cf1f916c..c3748d0cf6 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -249,4 +249,4 @@ WithCrumbleOverlay: Huntable: LuaScriptEvents: - + Demolishable: diff --git a/mods/d2k/rules/structures.yaml b/mods/d2k/rules/structures.yaml index ce94979cb5..65fd2f16db 100644 --- a/mods/d2k/rules/structures.yaml +++ b/mods/d2k/rules/structures.yaml @@ -384,6 +384,7 @@ Sellable: Guardable: BodyOrientation: + Demolishable: WALL: Inherits: ^WALL diff --git a/mods/ra/rules/civilian.yaml b/mods/ra/rules/civilian.yaml index 5877c509f1..5f3308755c 100644 --- a/mods/ra/rules/civilian.yaml +++ b/mods/ra/rules/civilian.yaml @@ -488,7 +488,7 @@ BRIDGEHUT: Bounds: 48,48 BridgeHut: TargetableBuilding: - TargetTypes: BridgeHut + TargetTypes: BridgeHut, C4 BRIDGEHUT.small: Building: @@ -499,7 +499,7 @@ BRIDGEHUT.small: Bounds: 24,24 BridgeHut: TargetableBuilding: - TargetTypes: BridgeHut + TargetTypes: BridgeHut, C4 V20: Inherits: ^DesertCivBuilding diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 116b28977d..69cdf925c9 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -264,6 +264,7 @@ String: Structure Huntable: LuaScriptEvents: + Demolishable: ^Wall: AppearsOnRadar: @@ -300,6 +301,7 @@ BodyOrientation: FrozenUnderFog: LuaScriptEvents: + Demolishable: ^TechBuilding: Inherits: ^Building @@ -375,6 +377,8 @@ -Selectable: Tooltip: Name: Field + -TargetableBuilding: + -Demolishable: -ProximityCaptor: ProximityCaptor: Types: CivilianField diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index fa3a5bac6a..0965cb5697 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -36,6 +36,7 @@ BodyOrientation: Huntable: LuaScriptEvents: + Demolishable: ^Infantry: AppearsOnRadar: @@ -176,6 +177,7 @@ UpdatesPlayerStatistics: BodyOrientation: LuaScriptEvents: + Demolishable: ^Helicopter: AppearsOnRadar: