diff --git a/OpenRA.Mods.Common/Activities/Demolish.cs b/OpenRA.Mods.Common/Activities/Demolish.cs index 07e77db0ec..67ab078410 100644 --- a/OpenRA.Mods.Common/Activities/Demolish.cs +++ b/OpenRA.Mods.Common/Activities/Demolish.cs @@ -52,23 +52,13 @@ namespace OpenRA.Mods.Common.Activities if (target.IsDead) return; + w.Add(new FlashTarget(target, count: flashes, delay: flashesDelay, interval: flashInterval)); + foreach (var ind in notifiers) ind.Demolishing(self); - w.Add(new FlashTarget(target, count: flashes, delay: flashesDelay, interval: flashInterval)); - - w.Add(new DelayedAction(delay, () => - { - if (target.IsDead) - return; - - var modifiers = target.TraitsImplementing() - .Concat(self.Owner.PlayerActor.TraitsImplementing()) - .Select(t => t.GetDamageModifier(self, null)); - - if (Util.ApplyPercentageModifiers(100, modifiers) > 0) - demolishables.Do(d => d.Demolish(target, self)); - })); + foreach (var d in demolishables) + d.Demolish(target, self, delay); }); } } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 3ba740ad9a..ffcd625892 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -589,6 +589,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/Buildings/BridgeHut.cs b/OpenRA.Mods.Common/Traits/Buildings/BridgeHut.cs index 1c4340405b..982737756f 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BridgeHut.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BridgeHut.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Linq; +using OpenRA.Effects; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -164,17 +165,36 @@ namespace OpenRA.Mods.Common.Traits repairDelay = Info.RepairPropagationDelay; } - public void Demolish(Actor self, Actor saboteur) + bool IDemolishable.IsValidTarget(Actor self, Actor saboteur) { - if (Info.DemolishPropagationDelay > 0) + return true; + } + + void IDemolishable.Demolish(Actor self, Actor saboteur, int delay) + { + // TODO: Handle using ITick + self.World.Add(new DelayedAction(delay, () => { - demolishStep = 0; - demolishSaboteur = saboteur; - DemolishStep(); - } - else - foreach (var s in segments.Values) - s.Demolish(saboteur); + if (self.IsDead) + return; + + var modifiers = self.TraitsImplementing() + .Concat(self.Owner.PlayerActor.TraitsImplementing()) + .Select(t => t.GetDamageModifier(self, null)); + + if (Util.ApplyPercentageModifiers(100, modifiers) > 0) + { + if (Info.DemolishPropagationDelay > 0) + { + demolishStep = 0; + demolishSaboteur = saboteur; + DemolishStep(); + } + else + foreach (var s in segments.Values) + s.Demolish(saboteur); + } + })); } public void DemolishStep() @@ -202,11 +222,6 @@ namespace OpenRA.Mods.Common.Traits demolishStep++; } - public bool IsValidTarget(Actor self, Actor saboteur) - { - return true; - } - public DamageState BridgeDamageState { get diff --git a/OpenRA.Mods.Common/Traits/Buildings/Building.cs b/OpenRA.Mods.Common/Traits/Buildings/Building.cs index 8534d10570..3863518280 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Building.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Building.cs @@ -244,7 +244,7 @@ namespace OpenRA.Mods.Common.Traits } public class Building : IOccupySpace, ITargetableCells, INotifySold, INotifyTransform, ISync, INotifyCreated, - INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyDemolition + INotifyAddedToWorld, INotifyRemovedFromWorld { public readonly bool SkipMakeAnimation; public readonly BuildingInfo Info; @@ -333,11 +333,6 @@ namespace OpenRA.Mods.Common.Traits notify.BuildingComplete(self); } - void INotifyDemolition.Demolishing(Actor self) - { - Lock(); - } - void INotifySold.Selling(Actor self) { if (Info.RemoveSmudgesOnSell) diff --git a/OpenRA.Mods.Common/Traits/Buildings/LegacyBridgeHut.cs b/OpenRA.Mods.Common/Traits/Buildings/LegacyBridgeHut.cs index bb68998f26..06195b4ab6 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/LegacyBridgeHut.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/LegacyBridgeHut.cs @@ -10,6 +10,7 @@ #endregion using System.Linq; +using OpenRA.Effects; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -43,14 +44,26 @@ namespace OpenRA.Mods.Common.Traits 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) + bool IDemolishable.IsValidTarget(Actor self, Actor saboteur) { return BridgeDamageState != DamageState.Dead; } + + void IDemolishable.Demolish(Actor self, Actor saboteur, int delay) + { + // TODO: Handle using ITick + self.World.Add(new DelayedAction(delay, () => + { + if (self.IsDead) + return; + + var modifiers = self.TraitsImplementing() + .Concat(self.Owner.PlayerActor.TraitsImplementing()) + .Select(t => t.GetDamageModifier(self, null)); + + if (Util.ApplyPercentageModifiers(100, modifiers) > 0) + Bridge.Do((b, d) => b.Demolish(saboteur, d)); + })); + } } } diff --git a/OpenRA.Mods.Common/Traits/Demolishable.cs b/OpenRA.Mods.Common/Traits/Demolishable.cs index 78839d96c3..67a6f90283 100644 --- a/OpenRA.Mods.Common/Traits/Demolishable.cs +++ b/OpenRA.Mods.Common/Traits/Demolishable.cs @@ -9,43 +9,91 @@ */ #endregion +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Effects; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Handle demolitions from C4 explosives.")] - public class DemolishableInfo : IDemolishableInfo, ITraitInfo + public class DemolishableInfo : ConditionalTraitInfo, IDemolishableInfo, ITraitInfo { public bool IsValidTarget(ActorInfo actorInfo, Actor saboteur) { return true; } - [Desc("If true and this actor has EjectOnDeath, no actor will be spawned.")] - public readonly bool PreventsEjectOnDeath = false; + [GrantedConditionReference] + [Desc("Condition to grant during demolition countdown.")] + public readonly string Condition = null; - public object Create(ActorInitializer init) { return new Demolishable(init.Self, this); } + public override object Create(ActorInitializer init) { return new Demolishable(this); } } - public class Demolishable : IDemolishable, IPreventsEjectOnDeath + public class Demolishable : ConditionalTrait, IDemolishable, ITick { - readonly DemolishableInfo info; - - public Demolishable(Actor self, DemolishableInfo info) + class DemolishAction { - this.info = info; + public readonly Actor Saboteur; + public readonly int Token; + public int Delay; + + public DemolishAction(Actor saboteur, int delay, int token) + { + Saboteur = saboteur; + Delay = delay; + Token = token; + } } - public bool PreventsEjectOnDeath(Actor self) + ConditionManager conditionManager; + List actions = new List(); + + public Demolishable(DemolishableInfo info) + : base(info) { } + + protected override void Created(Actor self) { - return info.PreventsEjectOnDeath; + base.Created(self); + conditionManager = self.TraitOrDefault(); } - public void Demolish(Actor self, Actor saboteur) + bool IDemolishable.IsValidTarget(Actor self, Actor saboteur) { - self.Kill(saboteur); + return !IsTraitDisabled; } - public bool IsValidTarget(Actor self, Actor saboteur) + void IDemolishable.Demolish(Actor self, Actor saboteur, int delay) { - return true; + if (IsTraitDisabled) + return; + + var token = ConditionManager.InvalidConditionToken; + if (conditionManager != null && !string.IsNullOrEmpty(Info.Condition)) + token = conditionManager.GrantCondition(self, Info.Condition); + + actions.Add(new DemolishAction(saboteur, delay, token)); + } + + void ITick.Tick(Actor self) + { + if (IsTraitDisabled) + return; + + foreach (var a in actions) + { + if (a.Delay-- <= 0) + { + var modifiers = self.TraitsImplementing() + .Concat(self.Owner.PlayerActor.TraitsImplementing()) + .Select(t => t.GetDamageModifier(self, null)); + + if (Util.ApplyPercentageModifiers(100, modifiers) > 0) + self.Kill(a.Saboteur); + else if (a.Token != ConditionManager.InvalidConditionToken) + conditionManager.RevokeCondition(self, a.Token); + } + } } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/EjectOnDeath.cs b/OpenRA.Mods.Common/Traits/EjectOnDeath.cs index ee2827e9b3..832b5e381c 100644 --- a/OpenRA.Mods.Common/Traits/EjectOnDeath.cs +++ b/OpenRA.Mods.Common/Traits/EjectOnDeath.cs @@ -40,8 +40,6 @@ namespace OpenRA.Mods.Common.Traits public override object Create(ActorInitializer init) { return new EjectOnDeath(init.Self, this); } } - public interface IPreventsEjectOnDeath { bool PreventsEjectOnDeath(Actor self); } - public class EjectOnDeath : ConditionalTrait, INotifyKilled { public EjectOnDeath(Actor self, EjectOnDeathInfo info) @@ -52,10 +50,6 @@ namespace OpenRA.Mods.Common.Traits if (IsTraitDisabled || self.Owner.WinState == WinState.Lost || !self.World.Map.Contains(self.Location)) return; - foreach (var condition in self.TraitsImplementing()) - if (condition.PreventsEjectOnDeath(self)) - return; - var r = self.World.SharedRandom.Next(1, 100); if (r <= 100 - Info.SuccessRate) diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index bdb557f16a..eb74e7583e 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -71,8 +71,8 @@ namespace OpenRA.Mods.Common.Traits public interface IDemolishableInfo : ITraitInfoInterface { bool IsValidTarget(ActorInfo actorInfo, Actor saboteur); } public interface IDemolishable { - void Demolish(Actor self, Actor saboteur); bool IsValidTarget(Actor self, Actor saboteur); + void Demolish(Actor self, Actor saboteur, int delay); } // Type tag for crush class bits diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20180923/RemovedDemolishLocking.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/RemovedDemolishLocking.cs new file mode 100644 index 0000000000..811927e399 --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/RemovedDemolishLocking.cs @@ -0,0 +1,71 @@ +#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; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class RemovedDemolishLocking : UpdateRule + { + public override string Name { get { return "Traits are no longer automatically disabled during the Demolition countdown"; } } + public override string Description + { + get + { + return "Traits are no longer force-disabled during the Demolishing trait destruction countdown.\n" + + "This affects the Production*, Transforms, Sellable, EjectOnDeath and ToggleConditionOnOrder traits.\n" + + "Affected actors are listed so that conditions may be manually defined."; + } + } + + static readonly string[] Traits = + { + "Production", + "ProductionAirdrop", + "ProductionFromMapEdge", + "ProductionParadrop", + "Transforms", + "Sellable", + "ToggleConditionOnOrder", + "EjectOnDeath" + }; + + readonly Dictionary> locations = new Dictionary>(); + + public override IEnumerable AfterUpdate(ModData modData) + { + if (locations.Any()) + yield return "Review the following definitions and, if the actor is Demolishable,\n" + + "define Demolishable.Condition and use this condition to disable them:\n" + + UpdateUtils.FormatMessageList(locations.Select( + kv => kv.Key + ":\n" + UpdateUtils.FormatMessageList(kv.Value))); + + locations.Clear(); + } + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + { + var used = new List(); + foreach (var t in Traits) + if (actorNode.LastChildMatching(t, includeRemovals: false) != null) + used.Add(t); + + if (used.Any()) + { + var location = "{0} ({1})".F(actorNode.Key, actorNode.Location.Filename); + locations[location] = used; + } + + yield break; + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs index cac2311d15..f4fcdf9287 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs @@ -102,6 +102,7 @@ namespace OpenRA.Mods.Common.UpdateRules new RemoveRepairBuildingsFromAircraft(), new AddRearmable(), new MergeAttackPlaneAndHeli(), + new RemovedDemolishLocking(), }) }; diff --git a/mods/cnc/maps/gdi01/rules.yaml b/mods/cnc/maps/gdi01/rules.yaml index 22cc66dcea..11da4d6d8a 100644 --- a/mods/cnc/maps/gdi01/rules.yaml +++ b/mods/cnc/maps/gdi01/rules.yaml @@ -13,16 +13,22 @@ World: NUKE: -Sellable: + Demolishable: + -Condition: Buildable: BuildLimit: 1 PYLE: -Sellable: + Demolishable: + -Condition: Buildable: BuildLimit: 1 FACT: -Sellable: + Demolishable: + -Condition: PROC: Buildable: diff --git a/mods/cnc/maps/nod07c/rules.yaml b/mods/cnc/maps/nod07c/rules.yaml index b8e7af1a25..9f7fc38afe 100644 --- a/mods/cnc/maps/nod07c/rules.yaml +++ b/mods/cnc/maps/nod07c/rules.yaml @@ -144,6 +144,8 @@ HPAD.IN: BuildSounds: placbldg.aud, build5.aud UndeploySounds: cashturn.aud -Sellable: + Demolishable: + -Condition: -Power: ORCA.IN: diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index d68063424d..4fd2e3db62 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -744,7 +744,10 @@ SpawnActorsOnSell: ActorTypes: e6,e1,e1,e1 EngineerRepairable: + Demolishable: + Condition: being-demolished Sellable: + RequiresCondition: !being-demolished SellSounds: cashturn.aud CaptureManager: Capturable: diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml index 93a1706f1d..d574639c71 100644 --- a/mods/cnc/rules/structures.yaml +++ b/mods/cnc/rules/structures.yaml @@ -20,10 +20,11 @@ FACT: Production: Produces: Building.GDI, Building.Nod, Defence.GDI, Defence.Nod Transforms: + RequiresCondition: factundeploy + PauseOnCondition: being-demolished IntoActor: mcv Offset: 1,1 Facing: 108 - RequiresCondition: factundeploy GrantConditionOnPrerequisite@GLOBALFACTUNDEPLOY: Condition: factundeploy Prerequisites: global-factundeploy diff --git a/mods/d2k/rules/arrakis.yaml b/mods/d2k/rules/arrakis.yaml index ffc920f949..51f8436486 100644 --- a/mods/d2k/rules/arrakis.yaml +++ b/mods/d2k/rules/arrakis.yaml @@ -144,6 +144,8 @@ sietch: -Sellable: -Capturable: -RepairableBuilding: + Demolishable: + -Condition: ProvidesPrerequisite@buildingname: -WithMakeAnimation: -WithCrumbleOverlay: diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index 5dec38d5fb..b071110ff3 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -409,11 +409,13 @@ ActorLostNotification: Notification: BuildingLost ShakeOnDeath: + Demolishable: + Condition: being-demolished Sellable: + RequiresCondition: !being-demolished SellSounds: BUILD1.WAV Guardable: Range: 3c0 - Demolishable: DamagedByTerrain: Damage: 500 DamageInterval: 100 diff --git a/mods/ra/maps/allies-08a/rules.yaml b/mods/ra/maps/allies-08a/rules.yaml index 39d5d3b2b0..0b92a97a99 100644 --- a/mods/ra/maps/allies-08a/rules.yaml +++ b/mods/ra/maps/allies-08a/rules.yaml @@ -75,6 +75,8 @@ ATEK: CaptureManager: -BeingCapturedCondition: -Sellable: + Demolishable: + -Condition: PDOX: Inherits@IDISABLE: ^DisableOnLowPower @@ -85,6 +87,8 @@ PDOX: CaptureManager: -BeingCapturedCondition: -Sellable: + Demolishable: + -Condition: IRON: Buildable: diff --git a/mods/ra/maps/fort-lonestar/rules.yaml b/mods/ra/maps/fort-lonestar/rules.yaml index 91c2e6c275..e9e96e9e12 100644 --- a/mods/ra/maps/fort-lonestar/rules.yaml +++ b/mods/ra/maps/fort-lonestar/rules.yaml @@ -170,6 +170,8 @@ TENT: Production: Produces: Infantry, Soldier, Dog, Defense -Sellable: + Demolishable: + -Condition: BaseProvider: Range: 12c0 Power: diff --git a/mods/ra/maps/soviet-01/rules.yaml b/mods/ra/maps/soviet-01/rules.yaml index 2e273e721f..73e305ae21 100644 --- a/mods/ra/maps/soviet-01/rules.yaml +++ b/mods/ra/maps/soviet-01/rules.yaml @@ -39,13 +39,19 @@ AFLD: DropItems: E1,E1,E1,E2,E2 -RallyPoint: -Sellable: + Demolishable: + -Condition: DOME: CaptureManager: -BeingCapturedCondition: -Sellable: + Demolishable: + -Condition: POWR: CaptureManager: -BeingCapturedCondition: -Sellable: + Demolishable: + -Condition: diff --git a/mods/ra/maps/soviet-07/rules.yaml b/mods/ra/maps/soviet-07/rules.yaml index dffb275830..823adfcf3a 100644 --- a/mods/ra/maps/soviet-07/rules.yaml +++ b/mods/ra/maps/soviet-07/rules.yaml @@ -33,6 +33,8 @@ FTUR: CaptureManager: -BeingCapturedCondition: -Sellable: + Demolishable: + -Condition: PBOX: -AutoTarget: diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index be805249a2..3ea9453238 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -663,8 +663,10 @@ RequiredForShortGame: true GpsDot: String: Structure + Demolishable: + Condition: being-demolished Sellable: - RequiresCondition: !being-captured + RequiresCondition: !being-captured && !being-demolished SellSounds: cashturn.aud WithBuildingRepairDecoration: Image: allyrepair diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml index d134fd3a17..9e4a6c6688 100644 --- a/mods/ra/rules/structures.yaml +++ b/mods/ra/rules/structures.yaml @@ -1127,13 +1127,13 @@ FACT: ActorTypes: e1,e1,e1,tecn,tecn,e6 BaseBuilding: Transforms: - PauseOnCondition: chrono-vortex || being-captured + RequiresCondition: factundeploy + PauseOnCondition: chrono-vortex || being-captured || being-demolished IntoActor: mcv Offset: 1,1 Facing: 96 - RequiresCondition: factundeploy Sellable: - RequiresCondition: !chrono-vortex && !being-captured + RequiresCondition: !chrono-vortex && !being-captured && !being-demolished GrantConditionOnPrerequisite@GLOBALFACTUNDEPLOY: Condition: factundeploy Prerequisites: global-factundeploy diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index ceb383c2ab..0bc827dba7 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -369,7 +369,10 @@ MustBeDestroyed: RequiredForShortGame: true CaptureNotification: + Demolishable: + Condition: being-demolished Sellable: + RequiresCondition: !being-demolished SellSounds: cashturn.aud WithMakeAnimation: ThrowsShrapnel@SMALL: @@ -478,9 +481,11 @@ TargetTypes: Ground, Wall, C4 WithWallSpriteBody: Type: wall - Sellable: - SellSounds: cashturn.aud Demolishable: + Condition: being-demolished + Sellable: + RequiresCondition: !being-demolished + SellSounds: cashturn.aud ScriptTriggers: ConditionManager: Health: diff --git a/mods/ts/rules/nod-support.yaml b/mods/ts/rules/nod-support.yaml index f54fe0f2ed..ecbdc2f626 100644 --- a/mods/ts/rules/nod-support.yaml +++ b/mods/ts/rules/nod-support.yaml @@ -93,6 +93,8 @@ NAFNCE: Types: laserfence -Crushable: -Sellable: + Demolishable: + -Condition: -Targetable: -Building: EnergyWall: diff --git a/mods/ts/rules/shared-structures.yaml b/mods/ts/rules/shared-structures.yaml index c5c271858f..ad4b37e076 100644 --- a/mods/ts/rules/shared-structures.yaml +++ b/mods/ts/rules/shared-structures.yaml @@ -28,11 +28,12 @@ GACNST: Value: 2500 BaseBuilding: Transforms: + RequiresCondition: factundeploy + PauseOnCondition: being-demolished IntoActor: mcv Offset: 1,1 Facing: 96 DeployCursor: undeploy - RequiresCondition: factundeploy GrantConditionOnPrerequisite@GLOBALFACTUNDEPLOY: Condition: factundeploy Prerequisites: global-factundeploy