diff --git a/OpenRA.Mods.Common/Traits/Cloak.cs b/OpenRA.Mods.Common/Traits/Cloak.cs index 3980b91022..c1f2a2a8b5 100644 --- a/OpenRA.Mods.Common/Traits/Cloak.cs +++ b/OpenRA.Mods.Common/Traits/Cloak.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; +using OpenRA.Mods.Common.Effects; using OpenRA.Primitives; using OpenRA.Traits; @@ -33,8 +34,8 @@ namespace OpenRA.Mods.Common.Traits Dock = 256 } - // Type tag for cloaktypes - public class CloakType { } + // Type tag for DetectionTypes + public class DetectionType { } [Desc("This unit can cloak and uncloak in specific situations.")] public class CloakInfo : PausableConditionalTraitInfo @@ -57,12 +58,36 @@ namespace OpenRA.Mods.Common.Traits public readonly string Palette = "cloak"; public readonly bool IsPlayerPalette = false; - public readonly BitSet CloakTypes = new BitSet("Cloak"); + public readonly BitSet DetectionTypes = new BitSet("Cloak"); [GrantedConditionReference] [Desc("The condition to grant to self while cloaked.")] public readonly string CloakedCondition = null; + [Desc("The type of cloak. Same type of cloaks won't trigger cloaking and uncloaking sound and effect.")] + public readonly string CloakType = null; + + [Desc("Which image to use for the effect played when cloaking or uncloaking.")] + public readonly string EffectImage = null; + + [Desc("Which effect sequence to play when cloaking.")] + [SequenceReference(nameof(EffectImage), allowNullImage: true)] + public readonly string CloakEffectSequence = null; + + [Desc("Which effect sequence to play when uncloaking.")] + [SequenceReference(nameof(EffectImage), allowNullImage: true)] + public readonly string UncloakEffectSequence = null; + + [PaletteReference(nameof(EffectPaletteIsPlayerPalette))] + public readonly string EffectPalette = "effect"; + public readonly bool EffectPaletteIsPlayerPalette = false; + + [Desc("Offset for the effect played when cloaking or uncloaking.")] + public readonly WVec EffectOffset = WVec.Zero; + + [Desc("Should the effect track the actor.")] + public readonly bool EffectTracksActor = true; + public override object Create(ActorInitializer init) { return new Cloak(this); } } @@ -88,9 +113,12 @@ namespace OpenRA.Mods.Common.Traits protected override void Created(Actor self) { - otherCloaks = self.TraitsImplementing() - .Where(c => c != this) - .ToArray(); + if (Info.CloakType != null) + { + otherCloaks = self.TraitsImplementing() + .Where(c => c != this && c.Info.CloakType == Info.CloakType) + .ToArray(); + } if (Cloaked) { @@ -167,16 +195,50 @@ namespace OpenRA.Mods.Common.Traits cloakedToken = self.GrantCondition(Info.CloakedCondition); // Sounds shouldn't play if the actor starts cloaked - if (!(firstTick && Info.InitialDelay == 0) && !otherCloaks.Any(a => a.Cloaked)) + if (!(firstTick && Info.InitialDelay == 0) && (otherCloaks == null || !otherCloaks.Any(a => a.Cloaked))) + { + var pos = self.CenterPosition; Game.Sound.Play(SoundType.World, Info.CloakSound, self.CenterPosition); + + Func posfunc = () => self.CenterPosition + Info.EffectOffset; + if (!Info.EffectTracksActor) + posfunc = () => pos + Info.EffectOffset; + + if (Info.EffectImage != null && Info.CloakEffectSequence != null) + { + var palette = Info.EffectPalette; + if (Info.EffectPaletteIsPlayerPalette) + palette += self.Owner.InternalName; + + self.World.AddFrameEndTask(w => w.Add(new SpriteEffect( + posfunc, () => WAngle.Zero, w, Info.EffectImage, Info.CloakEffectSequence, palette))); + } + } } else if (!isCloaked && wasCloaked) { if (cloakedToken != Actor.InvalidConditionToken) cloakedToken = self.RevokeCondition(cloakedToken); - if (!(firstTick && Info.InitialDelay == 0) && !otherCloaks.Any(a => a.Cloaked)) - Game.Sound.Play(SoundType.World, Info.UncloakSound, self.CenterPosition); + if (!(firstTick && Info.InitialDelay == 0) && (otherCloaks == null || !otherCloaks.Any(a => a.Cloaked))) + { + var pos = self.CenterPosition; + Game.Sound.Play(SoundType.World, Info.CloakSound, pos); + + Func posfunc = () => self.CenterPosition + Info.EffectOffset; + if (!Info.EffectTracksActor) + posfunc = () => pos + Info.EffectOffset; + + if (Info.EffectImage != null && Info.UncloakEffectSequence != null) + { + var palette = Info.EffectPalette; + if (Info.EffectPaletteIsPlayerPalette) + palette += self.Owner.InternalName; + + self.World.AddFrameEndTask(w => w.Add(new SpriteEffect( + posfunc, () => WAngle.Zero, w, Info.EffectImage, Info.UncloakEffectSequence, palette))); + } + } } wasCloaked = isCloaked; @@ -196,7 +258,7 @@ namespace OpenRA.Mods.Common.Traits return true; return self.World.ActorsWithTrait().Any(a => a.Actor.Owner.IsAlliedWith(viewer) - && Info.CloakTypes.Overlaps(a.Trait.Info.CloakTypes) + && Info.DetectionTypes.Overlaps(a.Trait.Info.DetectionTypes) && (self.CenterPosition - a.Actor.CenterPosition).LengthSquared <= a.Trait.Range.LengthSquared); } diff --git a/OpenRA.Mods.Common/Traits/DetectCloaked.cs b/OpenRA.Mods.Common/Traits/DetectCloaked.cs index 8cc64075ed..98c07dffc4 100644 --- a/OpenRA.Mods.Common/Traits/DetectCloaked.cs +++ b/OpenRA.Mods.Common/Traits/DetectCloaked.cs @@ -18,7 +18,7 @@ namespace OpenRA.Mods.Common.Traits public class DetectCloakedInfo : ConditionalTraitInfo { [Desc("Specific cloak classifications I can reveal.")] - public readonly BitSet CloakTypes = new BitSet("Cloak"); + public readonly BitSet DetectionTypes = new BitSet("Cloak"); public readonly WDist Range = WDist.FromCells(5); diff --git a/mods/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index b58f3e34af..39207b3b7e 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -208,7 +208,7 @@ C17: Cloak: InitialDelay: 0 CloakDelay: 0 - CloakTypes: C17 + DetectionTypes: C17 RequiresCondition: global-C17-stealth Contrail@1: Offset: -261,-650,0 diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index c45c7f8815..46b7da6117 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -433,7 +433,7 @@ Categories: Infantry EdibleByLeap: DetectCloaked: - CloakTypes: Cloak + DetectionTypes: Cloak Range: 1c0 ^Soldier: @@ -1190,7 +1190,7 @@ CloakSound: UncloakSound: Palette: - CloakTypes: Mine + DetectionTypes: Mine InitialDelay: 0 Tooltip: Name: Mine diff --git a/mods/ra/rules/infantry.yaml b/mods/ra/rules/infantry.yaml index 0e841501a9..fec952d6d2 100644 --- a/mods/ra/rules/infantry.yaml +++ b/mods/ra/rules/infantry.yaml @@ -641,7 +641,7 @@ THF: InitialDelay: 250 CloakDelay: 120 UncloakOn: Attack, Unload, Infiltrate, Demolish, Move - CloakTypes: Cloak + DetectionTypes: Cloak IsPlayerPalette: true PauseOnCondition: cloak-force-disabled GrantConditionOnDamageState@UNCLOAK: diff --git a/mods/ra/rules/misc.yaml b/mods/ra/rules/misc.yaml index 3a912bd0c0..418b3be86c 100644 --- a/mods/ra/rules/misc.yaml +++ b/mods/ra/rules/misc.yaml @@ -213,7 +213,7 @@ SONAR: Name: (support power proxy camera) -RevealsShroud: DetectCloaked: - CloakTypes: Underwater + DetectionTypes: Underwater Range: 10c0 FLARE: diff --git a/mods/ra/rules/ships.yaml b/mods/ra/rules/ships.yaml index a28cfe4931..7c5d73d848 100644 --- a/mods/ra/rules/ships.yaml +++ b/mods/ra/rules/ships.yaml @@ -33,7 +33,7 @@ SS: TargetTypes: Underwater, Submarine RequiresCondition: underwater Cloak: - CloakTypes: Underwater + DetectionTypes: Underwater InitialDelay: 0 CloakDelay: 50 CloakSound: subshow1.aud @@ -55,7 +55,7 @@ SS: AutoTargetPriority@ATTACKANYTHING: ValidTargets: WaterActor, Underwater DetectCloaked: - CloakTypes: Underwater + DetectionTypes: Underwater Range: 4c0 RenderDetectionCircle: Explodes: @@ -100,7 +100,7 @@ MSUB: TargetTypes: Underwater, Submarine RequiresCondition: underwater Cloak: - CloakTypes: Underwater + DetectionTypes: Underwater InitialDelay: 0 CloakDelay: 100 CloakSound: subshow1.aud @@ -126,7 +126,7 @@ MSUB: InitialStance: HoldFire InitialStanceAI: ReturnFire DetectCloaked: - CloakTypes: Underwater + DetectionTypes: Underwater Range: 4c0 RenderDetectionCircle: Explodes: @@ -182,7 +182,7 @@ DD: AttackTurreted: WithSpriteTurret: DetectCloaked: - CloakTypes: Underwater + DetectionTypes: Underwater Range: 4c0 RenderDetectionCircle: Selectable: @@ -333,7 +333,7 @@ PT: WithMuzzleOverlay: WithSpriteTurret: DetectCloaked: - CloakTypes: Underwater + DetectionTypes: Underwater Range: 4c0 RenderDetectionCircle: Selectable: diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml index 03681ef0a5..3728b7c8ec 100644 --- a/mods/ra/rules/structures.yaml +++ b/mods/ra/rules/structures.yaml @@ -214,7 +214,7 @@ SPEN: Power: Amount: -30 DetectCloaked: - CloakTypes: Underwater + DetectionTypes: Underwater Range: 10c0 RenderDetectionCircle: ProvidesPrerequisite@soviet: @@ -329,7 +329,7 @@ SYRD: Power: Amount: -30 DetectCloaked: - CloakTypes: Underwater + DetectionTypes: Underwater Range: 10c0 RenderDetectionCircle: ProvidesPrerequisite@allies: diff --git a/mods/ra/rules/vehicles.yaml b/mods/ra/rules/vehicles.yaml index 62fe911fc7..4eea0d7f4f 100644 --- a/mods/ra/rules/vehicles.yaml +++ b/mods/ra/rules/vehicles.yaml @@ -508,7 +508,7 @@ MNLY: RearmSound: minelay1.aud DetectCloaked: Range: 5c0 - CloakTypes: Mine + DetectionTypes: Mine RenderDetectionCircle: Explodes: Weapon: ATMine diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index b140079ce2..f6b837f5ed 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -130,6 +130,7 @@ CloakSound: cloak5.aud UncloakSound: cloak5.aud UncloakOn: Attack, Unload, Infiltrate, Demolish, Damage, Heal + CloakType: nod-stealth ExternalCondition@CLOAKGENERATOR: Condition: cloakgenerator ExternalCondition@CRATE-CLOAK: diff --git a/mods/ts/rules/nod-vehicles.yaml b/mods/ts/rules/nod-vehicles.yaml index 92f819d569..c35333f375 100644 --- a/mods/ts/rules/nod-vehicles.yaml +++ b/mods/ts/rules/nod-vehicles.yaml @@ -520,6 +520,7 @@ STNK: IsPlayerPalette: true UncloakOn: Attack, Unload, Infiltrate, Demolish, Damage, Heal PauseOnCondition: cloak-force-disabled || empdisable + CloakType: nod-stealth GrantConditionOnDamageState@UNCLOAK: Condition: cloak-force-disabled ValidDamageStates: Critical