diff --git a/OpenRA.Mods.RA/Armament.cs b/OpenRA.Mods.RA/Armament.cs index 4ad7000b14..d01f9fa5fc 100755 --- a/OpenRA.Mods.RA/Armament.cs +++ b/OpenRA.Mods.RA/Armament.cs @@ -18,11 +18,18 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA { + public enum CoordinateModel { Legacy, World }; + public class Barrel { + // Legacy coordinates public PVecInt TurretSpaceOffset; // position in turret space public PVecInt ScreenSpaceOffset; // screen-space hack to make things line up good. public int Facing; // deviation from turret facing + + // World coordinates + public WVec Offset; + public WAngle Yaw; } [Desc("Allows you to attach weapons to the unit (use @IdentifierSuffix for > 1)")] @@ -40,6 +47,16 @@ namespace OpenRA.Mods.RA public readonly float LegacyRecoilRecovery = 0.2f; public readonly int[] LegacyLocalOffset = { }; + public readonly CoordinateModel OffsetModel = CoordinateModel.Legacy; + [Desc("Muzzle position relative to turret or body. (forward, right, up) triples")] + public readonly WRange[] LocalOffset = {}; + [Desc("Muzzle yaw relative to turret or body.")] + public readonly WAngle[] LocalYaw = {}; + [Desc("Move the turret backwards when firing.")] + public readonly WRange Recoil = WRange.Zero; + [Desc("Recoil recovery per-frame")] + public readonly WRange RecoilRecovery = new WRange(9); + public object Create(ActorInitializer init) { return new Armament(init.self, this); } } @@ -49,8 +66,10 @@ namespace OpenRA.Mods.RA public readonly WeaponInfo Weapon; public readonly Barrel[] Barrels; Lazy Turret; + Lazy Coords; - public float Recoil { get; private set; } + public WRange Recoil; + public float LegacyRecoil { get; private set; } public int FireDelay { get; private set; } public int Burst { get; private set; } @@ -58,26 +77,45 @@ namespace OpenRA.Mods.RA { Info = info; - // We can't soft-depend on TraitInfo, so we have to wait - // until runtime to cache this - Turret = Lazy.New(() => self.TraitsImplementing().FirstOrDefault(t => t.info.Turret == info.Turret)); + // We can't resolve these until runtime + Turret = Lazy.New(() => self.TraitsImplementing().FirstOrDefault(t => t.Name == info.Turret)); + Coords = Lazy.New(() => self.Trait()); Weapon = Rules.Weapons[info.Weapon.ToLowerInvariant()]; Burst = Weapon.Burst; var barrels = new List(); - for (var i = 0; i < info.LegacyLocalOffset.Length / 5; i++) - barrels.Add(new Barrel + + if (Info.OffsetModel == CoordinateModel.Legacy) + { + for (var i = 0; i < info.LocalOffset.Length / 5; i++) + barrels.Add(new Barrel + { + TurretSpaceOffset = new PVecInt(info.LegacyLocalOffset[5 * i], info.LegacyLocalOffset[5 * i + 1]), + ScreenSpaceOffset = new PVecInt(info.LegacyLocalOffset[5 * i + 2], info.LegacyLocalOffset[5 * i + 3]), + Facing = info.LegacyLocalOffset[5 * i + 4], + }); + + // if no barrels specified, the default is "turret position; turret facing". + if (barrels.Count == 0) + barrels.Add(new Barrel { TurretSpaceOffset = PVecInt.Zero, ScreenSpaceOffset = PVecInt.Zero, Facing = 0 }); + } + else + { + if (info.LocalOffset.Length % 3 != 0) + throw new InvalidOperationException("Invalid LocalOffset array length"); + + for (var i = 0; i < info.LocalOffset.Length / 3; i++) { - TurretSpaceOffset = new PVecInt(info.LegacyLocalOffset[5 * i], info.LegacyLocalOffset[5 * i + 1]), - ScreenSpaceOffset = new PVecInt(info.LegacyLocalOffset[5 * i + 2], info.LegacyLocalOffset[5 * i + 3]), - Facing = info.LegacyLocalOffset[5 * i + 4], - }); - - // if no barrels specified, the default is "turret position; turret facing". - if (barrels.Count == 0) - barrels.Add(new Barrel { TurretSpaceOffset = PVecInt.Zero, ScreenSpaceOffset = PVecInt.Zero, Facing = 0 }); - + barrels.Add(new Barrel + { + Offset = new WVec(info.LocalOffset[3*i], info.LocalOffset[3*i + 1], info.LocalOffset[3*i + 2]), + Yaw = info.LocalYaw.Length > i ? info.LocalYaw[i] : WAngle.Zero + }); + } + if (barrels.Count == 0) + barrels.Add(new Barrel { Offset = WVec.Zero, Yaw = WAngle.Zero }); + } Barrels = barrels.ToArray(); } @@ -85,9 +123,12 @@ namespace OpenRA.Mods.RA { if (FireDelay > 0) --FireDelay; - Recoil = Math.Max(0f, Recoil - Info.LegacyRecoilRecovery); + LegacyRecoil = Math.Max(0f, LegacyRecoil - Info.LegacyRecoilRecovery); + Recoil = new WRange(Math.Max(0, Recoil.Range - Info.RecoilRecovery.Range)); } + // Note: facing is only used by the legacy positioning code + // The world coordinate model uses Actor.Orientation public void CheckFire(Actor self, AttackBase attack, IMove move, IFacing facing, Target target) { if (FireDelay > 0) return; @@ -103,20 +144,32 @@ namespace OpenRA.Mods.RA var barrel = Barrels[Burst % Barrels.Length]; var destMove = target.IsActor ? target.Actor.TraitOrDefault() : null; + var legacyMuzzlePosition = self.CenterLocation + (PVecInt)MuzzlePxOffset(self, facing, barrel).ToInt2(); + var legacyMuzzleAltitude = move != null ? move.Altitude : 0; + var legacyFacing = barrel.Facing + (Turret.Value != null ? Turret.Value.turretFacing : + facing != null ? facing.Facing : Util.GetFacing(target.CenterLocation - self.CenterLocation, 0)); + + if (Info.OffsetModel == CoordinateModel.World) + { + var muzzlePosition = self.CenterPosition + MuzzleOffset(self, barrel); + legacyMuzzlePosition = PPos.FromWPos(muzzlePosition); + legacyMuzzleAltitude = Game.CellSize*muzzlePosition.Z/1024; + + legacyFacing = MuzzleOrientation(self, barrel).Yaw.Angle / 4; + } + var args = new ProjectileArgs { weapon = Weapon, firedBy = self, target = target, + src = legacyMuzzlePosition, + srcAltitude = legacyMuzzleAltitude, - src = (self.CenterLocation + (PVecInt)MuzzlePxPosition(self, facing, barrel).ToInt2()), - srcAltitude = move != null ? move.Altitude : 0, dest = target.CenterLocation, destAltitude = destMove != null ? destMove.Altitude : 0, - facing = barrel.Facing + - (Turret.Value != null ? Turret.Value.turretFacing : - facing != null ? facing.Facing : Util.GetFacing(target.CenterLocation - self.CenterLocation, 0)), + facing = legacyFacing, firepowerModifier = self.TraitsImplementing() .Select(a => a.GetFirepowerModifier()) @@ -139,7 +192,8 @@ namespace OpenRA.Mods.RA foreach (var na in self.TraitsImplementing()) na.Attacking(self, target); - Recoil = Info.LegacyRecoil; + LegacyRecoil = Info.LegacyRecoil; + Recoil = Info.Recoil; if (--Burst > 0) FireDelay = Weapon.BurstDelay; @@ -169,8 +223,13 @@ namespace OpenRA.Mods.RA return (PVecFloat)Util.RotateVectorByFacing(b.TurretSpaceOffset.ToFloat2(), turretFacing, .7f); } - public PVecFloat MuzzlePxPosition(Actor self, IFacing facing, Barrel b) + // Note: facing is only used by the legacy positioning code + public PVecFloat MuzzlePxOffset(Actor self, IFacing facing, Barrel b) { + // Hack for external code unaware of world coordinates + if (Info.OffsetModel == CoordinateModel.World) + return (PVecFloat)PPos.FromWPosHackZ(WPos.Zero + MuzzleOffset(self, b)).ToFloat2(); + PVecFloat pos = b.ScreenSpaceOffset; // local facing offset doesn't make sense for actors that don't rotate @@ -192,10 +251,29 @@ namespace OpenRA.Mods.RA return pos; } - public PVecFloat RecoilPxOffset(Actor self, int facing) + public WVec MuzzleOffset(Actor self, Barrel b) { - var localRecoil = new float2(0, Recoil); - return (PVecFloat)Util.RotateVectorByFacing(localRecoil, facing, .7f); + if (Info.OffsetModel != CoordinateModel.World) + throw new InvalidOperationException("Armament.MuzzlePosition requires a world coordinate offset"); + + var bodyOrientation = Coords.Value.QuantizeOrientation(self, self.Orientation); + var localOffset = b.Offset + new WVec(-Recoil, WRange.Zero, WRange.Zero); + if (Turret.Value != null) + { + var turretOrientation = Coords.Value.QuantizeOrientation(self, Turret.Value.LocalOrientation(self)); + localOffset = localOffset.Rotate(turretOrientation); + localOffset += Turret.Value.Offset; + } + + return Coords.Value.LocalToWorld(localOffset.Rotate(bodyOrientation)); + } + + public WRot MuzzleOrientation(Actor self, Barrel b) + { + var orientation = self.Orientation + WRot.FromYaw(b.Yaw); + if (Turret.Value != null) + orientation += Turret.Value.LocalOrientation(self); + return orientation; } } } diff --git a/OpenRA.Mods.RA/Render/RenderUnitTurreted.cs b/OpenRA.Mods.RA/Render/RenderUnitTurreted.cs index 683b539246..710ba55528 100755 --- a/OpenRA.Mods.RA/Render/RenderUnitTurreted.cs +++ b/OpenRA.Mods.RA/Render/RenderUnitTurreted.cs @@ -37,19 +37,32 @@ namespace OpenRA.Mods.RA.Render anim.Play("turret"); anims.Add("turret_{0}".F(i++), new AnimationWithOffset(anim, - () => turret.PxPosition(self, facing).ToFloat2() + RecoilOffset(self, turret), null)); + () => TurretPosition(self, turret, facing), null)); } } - float2 RecoilOffset(Actor self, Turreted t) + float2 TurretPosition(Actor self, Turreted t, IFacing facing) { - var a = self.TraitsImplementing() - .OrderByDescending(w => w.Recoil) - .FirstOrDefault(w => w.Info.Turret == t.info.Turret); - if (a == null) - return float2.Zero; + if (t.CoordinateModel == CoordinateModel.Legacy) + { + var recoil = self.TraitsImplementing() + .Where(w => w.Info.Turret == t.Name) + .Sum(w => w.LegacyRecoil); + return t.PxPosition(self, facing).ToFloat2() + Traits.Util.RotateVectorByFacing(new float2(0, recoil), t.turretFacing, .7f); + } + else + { + var recoil = self.TraitsImplementing() + .Where(w => w.Info.Turret == t.Name) + .Aggregate(WRange.Zero, (a,b) => a + b.Recoil); - return a.RecoilPxOffset(self, t.turretFacing).ToFloat2(); + var localOffset = new WVec(-recoil, WRange.Zero, WRange.Zero); + var bodyOrientation = QuantizeOrientation(self, self.Orientation); + var turretOrientation = QuantizeOrientation(self, t.LocalOrientation(self)); + var worldPos = WPos.Zero + t.Position(self) + LocalToWorld(localOffset.Rotate(turretOrientation).Rotate(bodyOrientation)); + + return PPos.FromWPosHackZ(worldPos).ToFloat2(); + } } } } diff --git a/OpenRA.Mods.RA/Render/WithMuzzleFlash.cs b/OpenRA.Mods.RA/Render/WithMuzzleFlash.cs index 69933b27a4..55261a1f70 100644 --- a/OpenRA.Mods.RA/Render/WithMuzzleFlash.cs +++ b/OpenRA.Mods.RA/Render/WithMuzzleFlash.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.RA.Render { var barrel = b; var turreted = self.TraitsImplementing() - .FirstOrDefault(t => t.info.Turret == a.Info.Turret); + .FirstOrDefault(t => t.Name == a.Info.Turret); var getFacing = turreted != null ? () => turreted.turretFacing : facing != null ? (Func)(() => facing.Facing) : () => 0; @@ -47,7 +47,7 @@ namespace OpenRA.Mods.RA.Render muzzleFlashes.Add("muzzle{0}".F(muzzleFlashes.Count), new AnimationWithOffset( muzzleFlash, - () => a.MuzzlePxPosition(self, facing, barrel).ToFloat2(), + () => a.MuzzlePxOffset(self, facing, barrel).ToFloat2(), () => !isShowing)); } } diff --git a/OpenRA.Mods.RA/Turreted.cs b/OpenRA.Mods.RA/Turreted.cs index 8937945b67..e2d1e7f6a1 100755 --- a/OpenRA.Mods.RA/Turreted.cs +++ b/OpenRA.Mods.RA/Turreted.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Collections.Generic; using OpenRA.Mods.RA.Render; using OpenRA.FileFormats; @@ -24,6 +25,10 @@ namespace OpenRA.Mods.RA public readonly int[] LegacyOffset = {0,0}; public readonly bool AlignWhenIdle = false; + public readonly CoordinateModel OffsetModel = CoordinateModel.Legacy; + [Desc("Muzzle position relative to turret or body. (forward, right, up) triples")] + public readonly WVec Offset = WVec.Zero; + public virtual object Create(ActorInitializer init) { return new Turreted(init, this); } } @@ -31,10 +36,17 @@ namespace OpenRA.Mods.RA { [Sync] public int turretFacing = 0; public int? desiredFacing; - public TurretedInfo info; + TurretedInfo info; protected Turret turret; IFacing facing; + // For subclasses that want to move the turret relative to the body + protected WVec LocalOffset = WVec.Zero; + + public WVec Offset { get { return info.Offset + LocalOffset; } } + public string Name { get { return info.Turret; } } + public CoordinateModel CoordinateModel { get { return info.OffsetModel; } } + public static int GetInitialTurretFacing(ActorInitializer init, int def) { if (init.Contains()) @@ -62,7 +74,7 @@ namespace OpenRA.Mods.RA public bool FaceTarget(Actor self, Target target) { - desiredFacing = Util.GetFacing( target.CenterLocation - self.CenterLocation, turretFacing ); + desiredFacing = Util.GetFacing(target.CenterLocation - self.CenterLocation, turretFacing); return turretFacing == desiredFacing; } @@ -74,8 +86,30 @@ namespace OpenRA.Mods.RA public PVecFloat PxPosition(Actor self, IFacing facing) { + // Hack for external code unaware of world coordinates + if (info.OffsetModel == CoordinateModel.World) + return (PVecFloat)PPos.FromWPosHackZ(WPos.Zero + Position(self)).ToFloat2(); + return turret.PxPosition(self, facing); } + + // Turret offset in world-space + public WVec Position(Actor self) + { + if (info.OffsetModel != CoordinateModel.World) + throw new InvalidOperationException("Turreted.Position requires a world coordinate offset"); + + var coords = self.Trait(); + var bodyOrientation = coords.QuantizeOrientation(self, self.Orientation); + return coords.LocalToWorld(Offset.Rotate(bodyOrientation)); + } + + // Orientation in unit-space + public WRot LocalOrientation(Actor self) + { + // Hack: turretFacing is relative to the world, so subtract the body yaw + return WRot.FromYaw(WAngle.FromFacing(turretFacing) - self.Orientation.Yaw); + } } public class Turret