diff --git a/OpenRA.Game/WRot.cs b/OpenRA.Game/WRot.cs index 05adc891df..d2a79211e0 100644 --- a/OpenRA.Game/WRot.cs +++ b/OpenRA.Game/WRot.cs @@ -36,6 +36,16 @@ namespace OpenRA public static bool operator !=(WRot me, WRot other) { return !(me == other); } + public WRot WithRoll(WAngle roll) + { + return new WRot(roll, Pitch, Yaw); + } + + public WRot WithPitch(WAngle pitch) + { + return new WRot(Roll, pitch, Yaw); + } + public WRot WithYaw(WAngle yaw) { return new WRot(Roll, Pitch, yaw); diff --git a/OpenRA.Mods.Common/Activities/Air/Fly.cs b/OpenRA.Mods.Common/Activities/Air/Fly.cs index a0432189c8..1c18e87761 100644 --- a/OpenRA.Mods.Common/Activities/Air/Fly.cs +++ b/OpenRA.Mods.Common/Activities/Air/Fly.cs @@ -60,16 +60,29 @@ namespace OpenRA.Mods.Common.Activities this.minRange = minRange; } - public static void FlyTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, WVec moveOverride, WAngle? turnSpeedOverride = null) + public static void FlyTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, WVec moveOverride, bool idleTurn = false) { var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); var move = aircraft.Info.CanSlide ? aircraft.FlyStep(desiredFacing) : aircraft.FlyStep(aircraft.Facing); if (moveOverride != WVec.Zero) move = moveOverride; - var turnSpeed = turnSpeedOverride ?? aircraft.TurnSpeed; + var oldFacing = aircraft.Facing; + var turnSpeed = idleTurn ? aircraft.IdleTurnSpeed ?? aircraft.TurnSpeed : aircraft.TurnSpeed; aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, turnSpeed); + var roll = idleTurn ? aircraft.Info.IdleRoll ?? aircraft.Info.Roll : aircraft.Info.Roll; + if (roll != WAngle.Zero) + { + var desiredRoll = aircraft.Facing == desiredFacing ? WAngle.Zero : + new WAngle(roll.Angle * Util.GetTurnDirection(aircraft.Facing, oldFacing)); + + aircraft.Roll = Util.TickFacing(aircraft.Roll, desiredRoll, aircraft.Info.RollSpeed); + } + + if (aircraft.Info.Pitch != WAngle.Zero) + aircraft.Pitch = Util.TickFacing(aircraft.Pitch, aircraft.Info.Pitch, aircraft.Info.PitchSpeed); + // Note: we assume that if move.Z is not zero, it's intentional and we want to move in that vertical direction instead of towards desiredAltitude. // If that is not desired, the place that calls this should make sure moveOverride.Z is zero. if (dat != desiredAltitude || move.Z != 0) @@ -83,18 +96,18 @@ namespace OpenRA.Mods.Common.Activities aircraft.SetPosition(self, aircraft.CenterPosition + move); } - public static void FlyTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, WAngle? turnSpeedOverride = null) + public static void FlyTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, bool idleTurn = false) { - FlyTick(self, aircraft, desiredFacing, desiredAltitude, WVec.Zero, turnSpeedOverride); + FlyTick(self, aircraft, desiredFacing, desiredAltitude, WVec.Zero, idleTurn); } // Should only be used for vertical-only movement, usually VTOL take-off or land. Terrain-induced altitude changes should always be handled by FlyTick. - public static bool VerticalTakeOffOrLandTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, WAngle? turnSpeedOverride = null) + public static bool VerticalTakeOffOrLandTick(Actor self, Aircraft aircraft, WAngle desiredFacing, WDist desiredAltitude, bool idleTurn = false) { var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); var move = WVec.Zero; - var turnSpeed = turnSpeedOverride ?? aircraft.TurnSpeed; + var turnSpeed = idleTurn ? aircraft.IdleTurnSpeed ?? aircraft.TurnSpeed : aircraft.TurnSpeed; aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, turnSpeed); if (dat != desiredAltitude) diff --git a/OpenRA.Mods.Common/Activities/Air/FlyIdle.cs b/OpenRA.Mods.Common/Activities/Air/FlyIdle.cs index bf5a1e0caf..df99b2ce0b 100644 --- a/OpenRA.Mods.Common/Activities/Air/FlyIdle.cs +++ b/OpenRA.Mods.Common/Activities/Air/FlyIdle.cs @@ -20,16 +20,16 @@ namespace OpenRA.Mods.Common.Activities { readonly Aircraft aircraft; readonly INotifyIdle[] tickIdles; - readonly WAngle turnSpeed; + readonly bool idleTurn; int remainingTicks; - public FlyIdle(Actor self, int ticks = -1, bool tickIdle = true) + public FlyIdle(Actor self, int ticks = -1, bool idleTurn = true) { aircraft = self.Trait(); - turnSpeed = aircraft.IdleTurnSpeed ?? aircraft.TurnSpeed; remainingTicks = ticks; + this.idleTurn = idleTurn; - if (tickIdle) + if (idleTurn) tickIdles = self.TraitsImplementing().ToArray(); } @@ -55,7 +55,7 @@ namespace OpenRA.Mods.Common.Activities // We can't possibly turn this fast var desiredFacing = aircraft.Facing + new WAngle(256); - Fly.FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, move, turnSpeed); + Fly.FlyTick(self, aircraft, desiredFacing, aircraft.Info.CruiseAltitude, move, idleTurn); } return false; diff --git a/OpenRA.Mods.Common/Activities/PickupUnit.cs b/OpenRA.Mods.Common/Activities/PickupUnit.cs index ef64cf4d0a..563e3a064a 100644 --- a/OpenRA.Mods.Common/Activities/PickupUnit.cs +++ b/OpenRA.Mods.Common/Activities/PickupUnit.cs @@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.Activities // Fly to the target and wait for it to be locked for pickup // These activities will be cancelled and replaced by Land once the target has been locked QueueChild(new Fly(self, Target.FromActor(cargo))); - QueueChild(new FlyIdle(self, tickIdle: false)); + QueueChild(new FlyIdle(self, idleTurn: false)); } } diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index 1924dbfee7..64a7cd4c87 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -56,6 +56,21 @@ namespace OpenRA.Mods.Common.Traits public readonly int Speed = 1; + [Desc("Body pitch when flying forwards. Only relevant for voxel aircraft.")] + public readonly WAngle Pitch = WAngle.Zero; + + [Desc("Pitch steps to apply each tick when starting/stopping.")] + public readonly WAngle PitchSpeed = WAngle.Zero; + + [Desc("Body roll when turning. Only relevant for voxel aircraft.")] + public readonly WAngle Roll = WAngle.Zero; + + [Desc("Body roll to apply when aircraft flies in circles while idle. Defaults to Roll if undefined. Only relevant for voxel aircraft.")] + public readonly WAngle? IdleRoll = null; + + [Desc("Roll steps to apply each tick when turning.")] + public readonly WAngle RollSpeed = WAngle.Zero; + [Desc("Minimum altitude where this aircraft is considered airborne.")] public readonly int MinAirborneAltitude = 1; @@ -217,6 +232,18 @@ namespace OpenRA.Mods.Common.Traits set { orientation = orientation.WithYaw(value); } } + public WAngle Pitch + { + get { return orientation.Pitch; } + set { orientation = orientation.WithPitch(value); } + } + + public WAngle Roll + { + get { return orientation.Roll; } + set { orientation = orientation.WithRoll(value); } + } + public WRot Orientation { get { return orientation; } } [Sync] @@ -389,6 +416,15 @@ namespace OpenRA.Mods.Common.Traits CurrentMovementTypes = newMovementTypes; + if (!CurrentMovementTypes.HasFlag(MovementType.Horizontal)) + { + if (Info.Roll != WAngle.Zero && Roll != WAngle.Zero) + Roll = Util.TickFacing(Roll, WAngle.Zero, Info.RollSpeed); + + if (Info.Pitch != WAngle.Zero && Pitch != WAngle.Zero) + Pitch = Util.TickFacing(Pitch, WAngle.Zero, Info.PitchSpeed); + } + Repulse(); }