diff --git a/OpenRA.Mods.Common/Activities/Parachute.cs b/OpenRA.Mods.Common/Activities/Parachute.cs index 0f1d8cc505..258b8a74c2 100644 --- a/OpenRA.Mods.Common/Activities/Parachute.cs +++ b/OpenRA.Mods.Common/Activities/Parachute.cs @@ -18,72 +18,45 @@ namespace OpenRA.Mods.Common.Activities public class Parachute : Activity { readonly IPositionable pos; - readonly ParachutableInfo para; readonly WVec fallVector; readonly Actor ignore; - WPos dropPosition; - WPos currentPosition; - bool triggered = false; + int groundLevel; - public Parachute(Actor self, WPos dropPosition, Actor ignoreActor = null) + public Parachute(Actor self, Actor ignoreActor = null) { pos = self.TraitOrDefault(); ignore = ignoreActor; - // Parachutable trait is a prerequisite for running this activity - para = self.Info.TraitInfo(); - fallVector = new WVec(0, 0, para.FallRate); - this.dropPosition = dropPosition; + fallVector = new WVec(0, 0, self.Info.TraitInfo().FallRate); IsInterruptible = false; } - Activity FirstTick(Actor self) + protected override void OnFirstRun(Actor self) { - triggered = true; - + groundLevel = self.World.Map.CenterOfCell(self.Location).Z; foreach (var np in self.TraitsImplementing()) np.OnParachute(self); - - // Place the actor and retrieve its visual position (CenterPosition) - pos.SetPosition(self, dropPosition); - currentPosition = self.CenterPosition; - - return this; - } - - Activity LastTick(Actor self) - { - var dat = self.World.Map.DistanceAboveTerrain(currentPosition); - pos.SetPosition(self, currentPosition - new WVec(WDist.Zero, WDist.Zero, dat)); - - foreach (var np in self.TraitsImplementing()) - np.OnLanded(self, ignore); - - return NextActivity; } public override Activity Tick(Actor self) { - // If this is the first tick - if (!triggered) - return FirstTick(self); + var nextPosition = self.CenterPosition - fallVector; + if (nextPosition.Z < groundLevel) + return NextActivity; - currentPosition -= fallVector; - - // If the unit has landed, this will be the last tick - if (self.World.Map.DistanceAboveTerrain(currentPosition).Length <= 0) - return LastTick(self); - - pos.SetVisualPosition(self, currentPosition); + pos.SetVisualPosition(self, nextPosition); return this; } - // Only the last queued activity (given order) is kept - public override void Queue(Actor self, Activity activity) + protected override void OnLastRun(Actor self) { - NextActivity = activity; + var centerPosition = self.CenterPosition; + pos.SetPosition(self, centerPosition - new WVec(0, 0, groundLevel - centerPosition.Z)); + + foreach (var np in self.TraitsImplementing()) + np.OnLanded(self, ignore); } } } diff --git a/OpenRA.Mods.Common/Traits/EjectOnDeath.cs b/OpenRA.Mods.Common/Traits/EjectOnDeath.cs index a350ce68ef..24c162f63d 100644 --- a/OpenRA.Mods.Common/Traits/EjectOnDeath.cs +++ b/OpenRA.Mods.Common/Traits/EjectOnDeath.cs @@ -63,32 +63,46 @@ namespace OpenRA.Mods.Common.Traits var pilot = self.World.CreateActor(false, Info.PilotActor.ToLowerInvariant(), new TypeDictionary { new OwnerInit(self.Owner), new LocationInit(self.Location) }); - if (Info.AllowUnsuitableCell || IsSuitableCell(self, pilot)) + var pilotPositionable = pilot.TraitOrDefault(); + var pilotCell = self.Location; + var pilotSubCell = pilotPositionable.GetAvailableSubCell(pilotCell); + if (pilotSubCell == SubCell.Invalid) { - if (inAir) + if (!Info.AllowUnsuitableCell) { - self.World.AddFrameEndTask(w => - { - w.Add(pilot); - pilot.QueueActivity(new Parachute(pilot, cp)); - }); - Game.Sound.Play(SoundType.World, Info.ChuteSound, cp); + pilot.Dispose(); + return; } - else + + pilotSubCell = SubCell.Any; + } + + if (inAir) + { + self.World.AddFrameEndTask(w => { - self.World.AddFrameEndTask(w => w.Add(pilot)); + pilotPositionable.SetPosition(pilot, pilotCell, pilotSubCell); + w.Add(pilot); + + var dropPosition = pilot.CenterPosition + new WVec(0, 0, self.CenterPosition.Z - pilot.CenterPosition.Z); + pilotPositionable.SetVisualPosition(pilot, dropPosition); + pilot.QueueActivity(new Parachute(pilot)); + }); + + Game.Sound.Play(SoundType.World, Info.ChuteSound, cp); + } + else + { + self.World.AddFrameEndTask(w => + { + w.Add(pilot); + pilotPositionable.SetPosition(pilot, pilotCell, pilotSubCell); + var pilotMobile = pilot.TraitOrDefault(); if (pilotMobile != null) pilotMobile.Nudge(pilot, pilot, true); - } + }); } - else - pilot.Dispose(); - } - - static bool IsSuitableCell(Actor self, Actor actorToDrop) - { - return actorToDrop.Trait().CanEnterCell(self.Location, self, true); } } } diff --git a/OpenRA.Mods.Common/Traits/ParaDrop.cs b/OpenRA.Mods.Common/Traits/ParaDrop.cs index e5414e9cca..f13586c85f 100644 --- a/OpenRA.Mods.Common/Traits/ParaDrop.cs +++ b/OpenRA.Mods.Common/Traits/ParaDrop.cs @@ -22,6 +22,9 @@ namespace OpenRA.Mods.Common.Traits [Desc("Distance around the drop-point to unload troops.")] public readonly WDist DropRange = WDist.FromCells(4); + [Desc("Wait at least this many ticks between each drop.")] + public readonly int DropInterval = 5; + [Desc("Sound to play when dropping.")] public readonly string ChuteSound = null; @@ -33,7 +36,6 @@ namespace OpenRA.Mods.Common.Traits readonly ParaDropInfo info; readonly Actor self; readonly Cargo cargo; - readonly HashSet droppedAt = new HashSet(); public event Action OnRemovedFromWorld = self => { }; public event Action OnEnteredDropRange = self => { }; @@ -45,6 +47,9 @@ namespace OpenRA.Mods.Common.Traits [Sync] Target target; + [Sync] + int dropDelay; + bool checkForSuitableCell; public ParaDrop(Actor self, ParaDropInfo info) @@ -56,13 +61,18 @@ namespace OpenRA.Mods.Common.Traits public void SetLZ(CPos lz, bool checkLandingCell) { - droppedAt.Clear(); target = Target.FromCell(self.World, lz); checkForSuitableCell = checkLandingCell; } void ITick.Tick(Actor self) { + if (dropDelay > 0) + { + dropDelay--; + return; + } + var wasInDropRange = inDropRange; inDropRange = target.IsInRange(self.CenterPosition, info.DropRange); @@ -73,30 +83,38 @@ namespace OpenRA.Mods.Common.Traits OnExitedDropRange(self); // Are we able to drop the next trooper? - if (!inDropRange || cargo.IsEmpty(self)) + if (!inDropRange || cargo.IsEmpty(self) || !self.World.Map.Contains(self.Location)) return; - if (droppedAt.Contains(self.Location) || (checkForSuitableCell && !IsSuitableCell(cargo.Peek(self), self.Location))) - return; + var dropActor = cargo.Peek(self); + var dropPositionable = dropActor.Trait(); + var dropCell = self.Location; + var dropSubCell = dropPositionable.GetAvailableSubCell(dropCell); + if (dropSubCell == SubCell.Invalid) + { + if (checkForSuitableCell) + return; - if (!self.World.Map.Contains(self.Location)) - return; + dropSubCell = SubCell.Any; + } - // unload a dude here - droppedAt.Add(self.Location); + // Unload here + if (cargo.Unload(self) != dropActor) + throw new InvalidOperationException("Peeked cargo was not unloaded!"); - var a = cargo.Unload(self); self.World.AddFrameEndTask(w => { - w.Add(a); - a.QueueActivity(new Parachute(a, self.CenterPosition)); - }); - Game.Sound.Play(SoundType.World, info.ChuteSound, self.CenterPosition); - } + dropPositionable.SetPosition(dropActor, dropCell, dropSubCell); + w.Add(dropActor); - static bool IsSuitableCell(Actor actorToDrop, CPos p) - { - return actorToDrop.Trait().CanEnterCell(p); + var dropPosition = dropActor.CenterPosition + new WVec(0, 0, self.CenterPosition.Z - dropActor.CenterPosition.Z); + dropPositionable.SetVisualPosition(dropActor, dropPosition); + + dropActor.QueueActivity(new Parachute(dropActor)); + }); + + Game.Sound.Play(SoundType.World, info.ChuteSound, self.CenterPosition); + dropDelay = info.DropInterval; } void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) diff --git a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs index 78b2ae8767..c8af2713bd 100644 --- a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs +++ b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs @@ -135,7 +135,7 @@ namespace OpenRA.Mods.Common.Traits { var newUnit = self.World.CreateActor(producee.Name, td); - newUnit.QueueActivity(new Parachute(newUnit, newUnit.CenterPosition, self)); + newUnit.QueueActivity(new Parachute(newUnit, self)); var move = newUnit.TraitOrDefault(); if (move != null) { diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/ParatroopersPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/ParatroopersPower.cs index 6a91befe4b..432ad9ee28 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/ParatroopersPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/ParatroopersPower.cs @@ -175,14 +175,6 @@ namespace OpenRA.Mods.Common.Traits } }; - foreach (var p in info.DropItems) - { - var unit = self.World.CreateActor(false, p.ToLowerInvariant(), - new TypeDictionary { new OwnerInit(self.Owner) }); - - units.Add(unit); - } - self.World.AddFrameEndTask(w => { PlayLaunchSounds(); @@ -216,11 +208,17 @@ namespace OpenRA.Mods.Common.Traits drop.OnRemovedFromWorld += onRemovedFromWorld; var cargo = a.Trait(); - var passengers = units.Skip(added).Take(passengersPerPlane); - added += passengersPerPlane; + foreach (var p in info.DropItems.Skip(added).Take(passengersPerPlane)) + { + var unit = self.World.CreateActor(false, p.ToLowerInvariant(), new TypeDictionary + { + new OwnerInit(self.Owner) + }); - foreach (var p in passengers) - cargo.Load(a, p); + cargo.Load(a, unit); + units.Add(unit); + added++; + } a.QueueActivity(new Fly(a, Target.FromPos(target + spawnOffset))); a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset)));