diff --git a/OpenRA.Mods.Common/Activities/Air/Fly.cs b/OpenRA.Mods.Common/Activities/Air/Fly.cs index acd9b8b297..91b20c1fc3 100644 --- a/OpenRA.Mods.Common/Activities/Air/Fly.cs +++ b/OpenRA.Mods.Common/Activities/Air/Fly.cs @@ -105,6 +105,7 @@ namespace OpenRA.Mods.Common.Activities soundPlayed = true; } + // We are taking off, so remove influence in ground cells. if (self.IsAtGroundLevel()) aircraft.RemoveInfluence(); diff --git a/OpenRA.Mods.Common/Activities/Air/HeliFly.cs b/OpenRA.Mods.Common/Activities/Air/HeliFly.cs index c515027db0..7e0d484846 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliFly.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliFly.cs @@ -98,6 +98,7 @@ namespace OpenRA.Mods.Common.Activities soundPlayed = true; } + // We are taking off, so remove influence in ground cells. if (self.IsAtGroundLevel()) aircraft.RemoveInfluence(); diff --git a/OpenRA.Mods.Common/Activities/Air/HeliLand.cs b/OpenRA.Mods.Common/Activities/Air/HeliLand.cs index c1eda44c41..dc4ab0ee04 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliLand.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliLand.cs @@ -11,6 +11,7 @@ using OpenRA.Activities; using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { @@ -37,6 +38,13 @@ namespace OpenRA.Mods.Common.Activities public override Activity Tick(Actor self) { + if (ChildActivity != null) + { + ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); + if (ChildActivity != null) + return this; + } + if (IsCanceling) { aircraft.RemoveInfluence(); @@ -48,11 +56,13 @@ namespace OpenRA.Mods.Common.Activities var landingCell = self.Location; if (!aircraft.CanLand(landingCell, ignoreActor)) { + QueueChild(self, new Wait(25), true); self.NotifyBlocker(landingCell); return this; } aircraft.AddInfluence(landingCell); + aircraft.EnteringCell(self); landingInitiated = true; } diff --git a/OpenRA.Mods.Common/Activities/Air/Land.cs b/OpenRA.Mods.Common/Activities/Air/Land.cs index a3769ae3ca..21634666a4 100644 --- a/OpenRA.Mods.Common/Activities/Air/Land.cs +++ b/OpenRA.Mods.Common/Activities/Air/Land.cs @@ -63,6 +63,7 @@ namespace OpenRA.Mods.Common.Activities } aircraft.AddInfluence(landingCell); + aircraft.EnteringCell(self); landingInitiated = true; } diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index 2f25f8851d..27fb8b166b 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -52,6 +52,12 @@ namespace OpenRA.Mods.Common.Traits [Desc("Can the actor be ordered to move in to shroud?")] public readonly bool MoveIntoShroud = true; + [Desc("e.g. crate, wall, infantry")] + public readonly BitSet Crushes = default(BitSet); + + [Desc("Types of damage that are caused while crushing. Leave empty for no damage types.")] + public readonly BitSet CrushDamageTypes = default(BitSet); + [VoiceReference] public readonly string Voice = "Action"; [GrantedConditionReference] @@ -166,6 +172,8 @@ namespace OpenRA.Mods.Common.Traits INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyActorDisposing, INotifyBecomingIdle, IActorPreviewInitModifier, IIssueDeployOrder, IObservesVariables { + static readonly Pair[] NoCells = { }; + public readonly AircraftInfo Info; readonly Actor self; @@ -183,7 +191,7 @@ namespace OpenRA.Mods.Common.Traits public Actor ReservedActor { get; private set; } public bool MayYieldReservation { get; private set; } public bool ForceLanding { get; private set; } - CPos landingCell; + CPos? landingCell; bool airborne; bool cruising; @@ -495,7 +503,12 @@ namespace OpenRA.Mods.Common.Traits public Pair[] OccupiedCells() { if (!self.IsAtGroundLevel()) - return new[] { Pair.New(landingCell, SubCell.FullCell) }; + { + if (landingCell.HasValue) + return new[] { Pair.New(landingCell.Value, SubCell.FullCell) }; + + return NoCells; + } return new[] { Pair.New(TopLeft, SubCell.FullCell) }; } @@ -517,16 +530,12 @@ namespace OpenRA.Mods.Common.Traits return false; foreach (var otherActor in self.World.ActorMap.GetActorsAt(cell)) - { - if (otherActor != ignoreActor) + if (IsBlockedBy(self, otherActor, ignoreActor)) return false; - } foreach (var otherActor in self.World.ActorMap.GetActorsAt(cell)) - { if (AircraftCanEnter(otherActor)) return true; - } var type = self.World.Map.GetTerrainInfo(cell).Type; return Info.LandableTerrainTypes.Contains(type); @@ -542,6 +551,35 @@ namespace OpenRA.Mods.Common.Traits return repairable != null && repairable.Info.RepairActors.Contains(host.Info.Name) && self.GetDamageState() != DamageState.Undamaged; } + bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor) + { + // We are not blocked by the actor we are ignoring. + if (otherActor == self || otherActor == ignoreActor) + return false; + + // PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers + if (self.World.RulesContainTemporaryBlocker) + { + // If there is a temporary blocker in our path, but we can remove it, we are not blocked. + var temporaryBlocker = otherActor.TraitOrDefault(); + if (temporaryBlocker != null && temporaryBlocker.CanRemoveBlockage(otherActor, self)) + return false; + } + + // If we cannot crush the other actor in our way, we are blocked. + if (Info.Crushes.IsEmpty) + return true; + + // If the other actor in our way cannot be crushed, we are blocked. + // PERF: Avoid LINQ. + var crushables = otherActor.TraitsImplementing(); + foreach (var crushable in crushables) + if (crushable.CrushableBy(otherActor, self, Info.Crushes)) + return false; + + return true; + } + public virtual IEnumerable GetResupplyActivities(Actor a) { // The ResupplyAircraft activity guarantees that we're on the helipad/repair depot @@ -589,7 +627,7 @@ namespace OpenRA.Mods.Common.Traits } else if (!Info.CanHover && !atLandAltitude) self.QueueActivity(new FlyCircle(self, -1, Info.IdleTurnSpeed > -1 ? Info.IdleTurnSpeed : TurnSpeed)); - else if (atLandAltitude && !CanLand(self.Location, self) && ReservedActor == null) + else if (atLandAltitude && !CanLand(self.Location) && ReservedActor == null) self.QueueActivity(new TakeOff(self)); else if (Info.CanHover && self.Info.HasTraitInfo() && Info.IdleTurnSpeed > -1) { @@ -629,16 +667,59 @@ namespace OpenRA.Mods.Common.Traits self.World.UpdateMaps(self, this); var altitude = self.World.Map.DistanceAboveTerrain(CenterPosition); + var isAirborne = altitude.Length >= Info.MinAirborneAltitude; if (isAirborne && !airborne) OnAirborneAltitudeReached(); else if (!isAirborne && airborne) OnAirborneAltitudeLeft(); + var isCruising = altitude == Info.CruiseAltitude; if (isCruising && !cruising) OnCruisingAltitudeReached(); else if (!isCruising && cruising) OnCruisingAltitudeLeft(); + + FinishedMoving(self); + } + + public void FinishedMoving(Actor self) + { + // Only make actor crush if it is on the ground + if (!self.IsAtGroundLevel()) + return; + + var actors = self.World.ActorMap.GetActorsAt(TopLeft).Where(a => a != self).ToList(); + if (!AnyCrushables(actors)) + return; + + var notifiers = actors.SelectMany(a => a.TraitsImplementing().Select(t => new TraitPair(a, t))); + foreach (var notifyCrushed in notifiers) + notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.Crushes); + } + + bool AnyCrushables(List actors) + { + var crushables = actors.SelectMany(a => a.TraitsImplementing().Select(t => new TraitPair(a, t))).ToList(); + if (crushables.Count == 0) + return false; + + foreach (var crushes in crushables) + if (crushes.Trait.CrushableBy(crushes.Actor, self, Info.Crushes)) + return true; + + return false; + } + + public void EnteringCell(Actor self) + { + var actors = self.World.ActorMap.GetActorsAt(TopLeft).Where(a => a != self).ToList(); + if (!AnyCrushables(actors)) + return; + + var notifiers = actors.SelectMany(a => a.TraitsImplementing().Select(t => new TraitPair(a, t))); + foreach (var notifyCrushed in notifiers) + notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.Crushes); } public void AddInfluence(CPos landingCell) @@ -652,6 +733,8 @@ namespace OpenRA.Mods.Common.Traits { if (self.IsInWorld) self.World.ActorMap.RemoveInfluence(self, this); + + landingCell = null; } #endregion diff --git a/OpenRA.Mods.Common/Traits/Cargo.cs b/OpenRA.Mods.Common/Traits/Cargo.cs index 5a656a41ac..ffe477f135 100644 --- a/OpenRA.Mods.Common/Traits/Cargo.cs +++ b/OpenRA.Mods.Common/Traits/Cargo.cs @@ -213,7 +213,7 @@ namespace OpenRA.Mods.Common.Traits return false; } - return !IsEmpty(self) && (aircraft == null || aircraft.CanLand(self.Location, self)) + return !IsEmpty(self) && (aircraft == null || aircraft.CanLand(self.Location)) && CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => p.Trait().CanEnterCell(c, null, immediate))); } diff --git a/mods/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index 5fea7baa8c..28c6a30691 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -16,6 +16,7 @@ TRAN: TurnSpeed: 5 Speed: 150 LandableTerrainTypes: Clear,Rough,Road,Ore,Beach,Tiberium,BlueTiberium + Crushes: crate, infantry AltitudeVelocity: 0c100 Health: HP: 9000 diff --git a/mods/ra/rules/aircraft.yaml b/mods/ra/rules/aircraft.yaml index 98d7841826..c76a43a724 100644 --- a/mods/ra/rules/aircraft.yaml +++ b/mods/ra/rules/aircraft.yaml @@ -239,6 +239,7 @@ TRAN: TurnSpeed: 5 Speed: 128 LandableTerrainTypes: Clear,Rough,Road,Ore,Beach,Gems + Crushes: crate, mine, infantry AltitudeVelocity: 0c58 WithIdleOverlay@ROTOR1AIR: Offset: 597,0,213 diff --git a/mods/ts/rules/aircraft.yaml b/mods/ts/rules/aircraft.yaml index 2904d96962..7d3ecbd1de 100644 --- a/mods/ts/rules/aircraft.yaml +++ b/mods/ts/rules/aircraft.yaml @@ -11,6 +11,7 @@ DPOD: Speed: 149 InitialFacing: 0 LandableTerrainTypes: Clear,Road,Rail,DirtRoad,Rough,Tiberium,BlueTiberium,Veins + Crushes: crate, infantry Health: HP: 6000 Armor: @@ -46,6 +47,7 @@ DSHP: Speed: 168 InitialFacing: 0 LandableTerrainTypes: Clear,Road,Rail,DirtRoad,Rough,Tiberium,BlueTiberium,Veins + Crushes: crate, infantry TakeoffSounds: dropup1.aud LandingSounds: dropdwn1.aud IdealSeparation: 1275 @@ -192,6 +194,7 @@ ORCATRAN: Speed: 84 InitialFacing: 0 LandableTerrainTypes: Clear,Road,Rail,DirtRoad,Rough,Tiberium,BlueTiberium,Veins + Crushes: crate, infantry TakeoffSounds: orcaup1.aud LandingSounds: orcadwn1.aud IdealSeparation: 1275 @@ -230,6 +233,7 @@ TRNSPORT: Speed: 149 InitialFacing: 0 LandableTerrainTypes: Clear,Road,Rail,DirtRoad,Rough,Tiberium,BlueTiberium,Veins + Crushes: crate, infantry TakeoffSounds: dropup1.aud LandingSounds: dropdwn1.aud AltitudeVelocity: 64