diff --git a/OpenRA.Mods.D2k/Activities/SwallowActor.cs b/OpenRA.Mods.D2k/Activities/SwallowActor.cs index e99b5903f0..969b36393b 100644 --- a/OpenRA.Mods.D2k/Activities/SwallowActor.cs +++ b/OpenRA.Mods.D2k/Activities/SwallowActor.cs @@ -19,22 +19,22 @@ using OpenRA.Traits; namespace OpenRA.Mods.D2k.Activities { - enum AttackState { Burrowed, EmergingAboveGround, ReturningUnderground } + enum AttackState { Uninitialized, Burrowed, Attacking } class SwallowActor : Activity { const int NearEnough = 1; - readonly CPos location; readonly Target target; readonly Sandworm sandworm; + readonly UpgradeManager manager; readonly WeaponInfo weapon; - readonly WithSpriteBody withSpriteBody; readonly RadarPings radarPings; readonly AttackSwallow swallow; readonly IPositionable positionable; int countdown; + CPos burrowLocation; AttackState stance; public SwallowActor(Actor self, Target target, WeaponInfo weapon) @@ -44,128 +44,133 @@ namespace OpenRA.Mods.D2k.Activities sandworm = self.Trait(); positionable = self.Trait(); swallow = self.Trait(); - withSpriteBody = self.Trait(); + manager = self.Trait(); radarPings = self.World.WorldActor.TraitOrDefault(); - countdown = swallow.Info.AttackTime; - - withSpriteBody.DefaultAnimation.ReplaceAnim(sandworm.Info.BurrowedSequence); - stance = AttackState.Burrowed; - location = target.Actor.Location; } - bool WormAttack(Actor worm) + bool AttackTargets(Actor self, IEnumerable targets) { var targetLocation = target.Actor.Location; - - // The target has moved too far away - if ((location - targetLocation).Length > NearEnough) - return false; - - var lunch = worm.World.ActorMap.GetActorsAt(targetLocation) - .Where(t => !t.Equals(worm) && weapon.IsValidAgainst(t, worm)); - - if (!lunch.Any()) - return false; - - stance = AttackState.EmergingAboveGround; - sandworm.IsAttacking = true; - - foreach (var actor in lunch) + foreach (var t in targets) { - var actor1 = actor; // loop variable in closure hazard + var target = t; // loop variable in closure hazard - actor.World.AddFrameEndTask(_ => + self.World.AddFrameEndTask(_ => + { + // Don't use Kill() because we don't want any of its side-effects (husks, etc) + target.Dispose(); + + // Harvester insurance + if (target.Info.HasTraitInfo()) { - actor1.Dispose(); - - // Harvester insurance - if (!actor1.Info.HasTraitInfo()) - return; - - var insurance = actor1.Owner.PlayerActor.TraitOrDefault(); - + var insurance = target.Owner.PlayerActor.TraitOrDefault(); if (insurance != null) - actor1.World.AddFrameEndTask(__ => insurance.TryActivate()); - }); + self.World.AddFrameEndTask(__ => insurance.TryActivate()); + } + }); } - positionable.SetPosition(worm, targetLocation); + positionable.SetPosition(self, targetLocation); - var attackPosition = worm.CenterPosition; - var affectedPlayers = lunch.Select(x => x.Owner).Distinct().ToList(); - - PlayAttack(worm, attackPosition, affectedPlayers); - foreach (var notify in worm.TraitsImplementing()) - notify.Attacking(worm, target, null, null); - - return true; - } - - // List because IEnumerable gets evaluated too late. - void PlayAttack(Actor self, WPos attackPosition, List affectedPlayers) - { - withSpriteBody.PlayCustomAnimation(self, sandworm.Info.MouthSequence); + var attackPosition = self.CenterPosition; + var affectedPlayers = targets.Select(x => x.Owner).Distinct().ToList(); Game.Sound.Play(swallow.Info.WormAttackSound, self.CenterPosition); Game.RunAfterDelay(1000, () => { - if (Game.IsCurrentWorld(self.World)) - foreach (var affectedPlayer in affectedPlayers) - NotifyPlayer(affectedPlayer, attackPosition); + if (!Game.IsCurrentWorld(self.World)) + return; + + foreach (var player in affectedPlayers) + { + Game.Sound.PlayNotification(player.World.Map.Rules, player, "Speech", swallow.Info.WormAttackNotification, player.Faction.InternalName); + + if (player == player.World.RenderPlayer) + radarPings.Add(() => true, attackPosition, Color.Red, 50); + } }); - } - void NotifyPlayer(Player player, WPos location) - { - Game.Sound.PlayNotification(player.World.Map.Rules, player, "Speech", swallow.Info.WormAttackNotification, player.Faction.InternalName); + foreach (var notify in self.TraitsImplementing()) + notify.Attacking(self, target, null, null); - if (player == player.World.RenderPlayer) - radarPings.Add(() => true, location, Color.Red, 50); + return true; } public override Activity Tick(Actor self) { - if (countdown > 0) + switch (stance) { - countdown--; - return this; - } + case AttackState.Uninitialized: + GrantUpgrades(self); + stance = AttackState.Burrowed; + countdown = swallow.Info.AttackDelay; + burrowLocation = self.Location; + break; + case AttackState.Burrowed: + if (--countdown > 0) + return this; - // Wait for the worm to get back underground - if (stance == AttackState.ReturningUnderground) - { - sandworm.IsAttacking = false; + var targetLocation = target.Actor.Location; - // There is a chance that the worm would just go away after attacking - if (self.World.SharedRandom.Next() % 100 <= sandworm.Info.ChanceToDisappear) - { - self.CancelActivity(); - self.World.AddFrameEndTask(w => self.Kill(self)); - } - else - withSpriteBody.DefaultAnimation.ReplaceAnim(sandworm.Info.IdleSequence); + // The target has moved too far away + if ((burrowLocation - targetLocation).Length > NearEnough) + { + RevokeUpgrades(self); + return NextActivity; + } - return NextActivity; - } + // The target reached solid ground + if (!positionable.CanEnterCell(targetLocation, null, false)) + { + RevokeUpgrades(self); + return NextActivity; + } - // Wait for the worm to get in position - if (stance == AttackState.Burrowed) - { - // This is so that the worm cancels an attack against a target that has reached solid rock - if (!positionable.CanEnterCell(target.Actor.Location, null, false)) + var targets = self.World.ActorMap.GetActorsAt(targetLocation) + .Where(t => !t.Equals(self) && weapon.IsValidAgainst(t, self)); + + if (!targets.Any()) + { + RevokeUpgrades(self); + return NextActivity; + } + + stance = AttackState.Attacking; + countdown = swallow.Info.ReturnDelay; + sandworm.IsAttacking = true; + AttackTargets(self, targets); + + break; + case AttackState.Attacking: + if (--countdown > 0) + return this; + + sandworm.IsAttacking = false; + + // There is a chance that the worm would just go away after attacking + if (self.World.SharedRandom.Next(100) <= sandworm.Info.ChanceToDisappear) + { + self.CancelActivity(); + self.World.AddFrameEndTask(w => self.Dispose()); + } + + RevokeUpgrades(self); return NextActivity; - - if (!WormAttack(self)) - { - withSpriteBody.DefaultAnimation.ReplaceAnim(sandworm.Info.IdleSequence); - return NextActivity; - } - - countdown = swallow.Info.ReturnTime; - stance = AttackState.ReturningUnderground; } return this; } + + void GrantUpgrades(Actor self) + { + foreach (var up in swallow.Info.AttackingUpgrades) + manager.GrantUpgrade(self, up, this); + } + + void RevokeUpgrades(Actor self) + { + foreach (var up in swallow.Info.AttackingUpgrades) + manager.RevokeUpgrade(self, up, this); + } } } diff --git a/OpenRA.Mods.D2k/Traits/AttackSwallow.cs b/OpenRA.Mods.D2k/Traits/AttackSwallow.cs index 090996630d..4aad528d11 100644 --- a/OpenRA.Mods.D2k/Traits/AttackSwallow.cs +++ b/OpenRA.Mods.D2k/Traits/AttackSwallow.cs @@ -20,10 +20,14 @@ namespace OpenRA.Mods.D2k.Traits class AttackSwallowInfo : AttackFrontalInfo { [Desc("The number of ticks it takes to return underground.")] - public readonly int ReturnTime = 60; + public readonly int ReturnDelay = 60; [Desc("The number of ticks it takes to get in place under the target to attack.")] - public readonly int AttackTime = 30; + public readonly int AttackDelay = 30; + + [UpgradeGrantedReference] + [Desc("The upgrades to grant while attacking.")] + public readonly string[] AttackingUpgrades = { "attacking" }; public readonly string WormAttackSound = "Worm.wav"; diff --git a/OpenRA.Mods.D2k/Traits/Sandworm.cs b/OpenRA.Mods.D2k/Traits/Sandworm.cs index 2ee042dc1f..91ad133b6d 100644 --- a/OpenRA.Mods.D2k/Traits/Sandworm.cs +++ b/OpenRA.Mods.D2k/Traits/Sandworm.cs @@ -10,12 +10,13 @@ using System; using System.Linq; +using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; namespace OpenRA.Mods.D2k.Traits { - class SandwormInfo : WandersInfo, Requires, Requires, Requires + class SandwormInfo : WandersInfo, Requires, Requires { [Desc("Time between rescanning for targets (in ticks).")] public readonly int TargetRescanInterval = 125; @@ -29,15 +30,6 @@ namespace OpenRA.Mods.D2k.Traits [Desc("The chance this actor has of disappearing after it attacks (in %).")] public readonly int ChanceToDisappear = 100; - [Desc("Name of the sequence that is used when the actor is idle or moving (not attacking).")] - [SequenceReference] public readonly string IdleSequence = "idle"; - - [Desc("Name of the sequence that is used when the actor is attacking.")] - [SequenceReference] public readonly string MouthSequence = "mouth"; - - [Desc("Name of the sequence that is used when the actor is burrowed.")] - [SequenceReference] public readonly string BurrowedSequence = "burrowed"; - public override object Create(ActorInitializer init) { return new Sandworm(init.Self, this); } } @@ -47,7 +39,6 @@ namespace OpenRA.Mods.D2k.Traits readonly WormManager manager; readonly Mobile mobile; - readonly WithSpriteBody withSpriteBody; readonly AttackBase attackTrait; public bool IsMovingTowardTarget { get; private set; } @@ -61,19 +52,10 @@ namespace OpenRA.Mods.D2k.Traits { Info = info; mobile = self.Trait(); - withSpriteBody = self.Trait(); attackTrait = self.Trait(); manager = self.World.WorldActor.Trait(); } - public override void OnBecomingIdle(Actor self) - { - if (withSpriteBody.DefaultAnimation.CurrentSequence.Name != Info.IdleSequence) - withSpriteBody.DefaultAnimation.PlayRepeating(Info.IdleSequence); - - base.OnBecomingIdle(self); - } - public override void DoAction(Actor self, CPos targetCell) { IsMovingTowardTarget = false; diff --git a/mods/d2k/bits/sandtrail.shp b/mods/d2k/bits/sandtrail.shp new file mode 100644 index 0000000000..8601595dc2 Binary files /dev/null and b/mods/d2k/bits/sandtrail.shp differ diff --git a/mods/d2k/rules/arrakis.yaml b/mods/d2k/rules/arrakis.yaml index 557edd6a10..d6c222012f 100644 --- a/mods/d2k/rules/arrakis.yaml +++ b/mods/d2k/rules/arrakis.yaml @@ -69,8 +69,20 @@ sandworm: Spice: 100 Targetable: TargetTypes: Ground - WithFacingSpriteBody: - WithAttackOverlay: + WithSpriteBody: + WithIdleAnimation: + Interval: 160 + Sequences: lightninga, lightningb, lightningc, lightningd, lightninge, lightningf + UpgradeTypes: attacking + UpgradeMaxEnabledLevel: 0 + AmbientSound: + SoundFile: WRMSIGN1.WAV + Interval: 160 + UpgradeTypes: attacking + UpgradeMaxEnabledLevel: 0 + WithAttackOverlay@mouth: + Sequence: mouth + WithAttackOverlay@sand: Sequence: sand HiddenUnderFog: AppearsOnRadar: @@ -88,6 +100,16 @@ sandworm: PingRadar: True RevealsShroud: Range: 5c0 + LeavesTrails: + Image: sandtrail + Sequences: traila, trailb, trailc + Palette: effect + Type: CenterPosition + TerrainTypes: Sand, Dune, SpiceSand, Spice + MovingInterval: 3 + UpgradeTypes: attacking + UpgradeMaxEnabledLevel: 0 + UpgradeManager: sietch: Inherits: ^Building diff --git a/mods/d2k/sequences/infantry.yaml b/mods/d2k/sequences/infantry.yaml index 1f114d484d..22bd0b67ce 100644 --- a/mods/d2k/sequences/infantry.yaml +++ b/mods/d2k/sequences/infantry.yaml @@ -481,10 +481,35 @@ sandworm: Length: 20 Tick: 100 idle: DATA.R8 - Start: 3586 - Length: 35 - Tick: 180 - BlendMode: Additive - burrowed: DATA.R8 Start: 39 + lightninga: DATA.R8 + Start: 3591 + Length: 5 + Tick: 80 + BlendMode: Additive + lightningb: DATA.R8 + Start: 3596 + Length: 5 + Tick: 80 + BlendMode: Additive + lightningc: DATA.R8 + Start: 3601 + Length: 5 + Tick: 80 + BlendMode: Additive + lightningd: DATA.R8 + Start: 3606 + Length: 5 + Tick: 80 + BlendMode: Additive + lightninge: DATA.R8 + Start: 3611 + Length: 5 + Tick: 80 + BlendMode: Additive + lightningf: DATA.R8 + Start: 3616 + Length: 5 + Tick: 80 + BlendMode: Additive icon: wormicon.shp \ No newline at end of file diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml index d9a1b9e1e4..84a8854d1e 100644 --- a/mods/d2k/sequences/misc.yaml +++ b/mods/d2k/sequences/misc.yaml @@ -109,6 +109,17 @@ laserfire: BlendMode: Additive ZOffset: 511 +sandtrail: + Defaults: + Length: 8 + Tick: 200 + ZOffset: -512 + traila: sandtrail.shp + trailb: sandtrail.shp + Frames: 2, 6, 4, 5, 0, 1, 3, 7 + trailc: sandtrail.shp + Frames: 7, 4, 6, 5, 2, 0, 3, 1 + pips: groups: DATA.R8 Start: 17