Introduce world offsets for turrets & armaments.
This commit is contained in:
@@ -18,11 +18,18 @@ using OpenRA.Traits;
|
|||||||
|
|
||||||
namespace OpenRA.Mods.RA
|
namespace OpenRA.Mods.RA
|
||||||
{
|
{
|
||||||
|
public enum CoordinateModel { Legacy, World };
|
||||||
|
|
||||||
public class Barrel
|
public class Barrel
|
||||||
{
|
{
|
||||||
|
// Legacy coordinates
|
||||||
public PVecInt TurretSpaceOffset; // position in turret space
|
public PVecInt TurretSpaceOffset; // position in turret space
|
||||||
public PVecInt ScreenSpaceOffset; // screen-space hack to make things line up good.
|
public PVecInt ScreenSpaceOffset; // screen-space hack to make things line up good.
|
||||||
public int Facing; // deviation from turret facing
|
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)")]
|
[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 float LegacyRecoilRecovery = 0.2f;
|
||||||
public readonly int[] LegacyLocalOffset = { };
|
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); }
|
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 WeaponInfo Weapon;
|
||||||
public readonly Barrel[] Barrels;
|
public readonly Barrel[] Barrels;
|
||||||
Lazy<Turreted> Turret;
|
Lazy<Turreted> Turret;
|
||||||
|
Lazy<ILocalCoordinatesModel> Coords;
|
||||||
|
|
||||||
public float Recoil { get; private set; }
|
public WRange Recoil;
|
||||||
|
public float LegacyRecoil { get; private set; }
|
||||||
public int FireDelay { get; private set; }
|
public int FireDelay { get; private set; }
|
||||||
public int Burst { get; private set; }
|
public int Burst { get; private set; }
|
||||||
|
|
||||||
@@ -58,15 +77,18 @@ namespace OpenRA.Mods.RA
|
|||||||
{
|
{
|
||||||
Info = info;
|
Info = info;
|
||||||
|
|
||||||
// We can't soft-depend on TraitInfo, so we have to wait
|
// We can't resolve these until runtime
|
||||||
// until runtime to cache this
|
Turret = Lazy.New(() => self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.Name == info.Turret));
|
||||||
Turret = Lazy.New(() => self.TraitsImplementing<Turreted>().FirstOrDefault(t => t.info.Turret == info.Turret));
|
Coords = Lazy.New(() => self.Trait<ILocalCoordinatesModel>());
|
||||||
|
|
||||||
Weapon = Rules.Weapons[info.Weapon.ToLowerInvariant()];
|
Weapon = Rules.Weapons[info.Weapon.ToLowerInvariant()];
|
||||||
Burst = Weapon.Burst;
|
Burst = Weapon.Burst;
|
||||||
|
|
||||||
var barrels = new List<Barrel>();
|
var barrels = new List<Barrel>();
|
||||||
for (var i = 0; i < info.LegacyLocalOffset.Length / 5; i++)
|
|
||||||
|
if (Info.OffsetModel == CoordinateModel.Legacy)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < info.LocalOffset.Length / 5; i++)
|
||||||
barrels.Add(new Barrel
|
barrels.Add(new Barrel
|
||||||
{
|
{
|
||||||
TurretSpaceOffset = new PVecInt(info.LegacyLocalOffset[5 * i], info.LegacyLocalOffset[5 * i + 1]),
|
TurretSpaceOffset = new PVecInt(info.LegacyLocalOffset[5 * i], info.LegacyLocalOffset[5 * i + 1]),
|
||||||
@@ -77,7 +99,23 @@ namespace OpenRA.Mods.RA
|
|||||||
// if no barrels specified, the default is "turret position; turret facing".
|
// if no barrels specified, the default is "turret position; turret facing".
|
||||||
if (barrels.Count == 0)
|
if (barrels.Count == 0)
|
||||||
barrels.Add(new Barrel { TurretSpaceOffset = PVecInt.Zero, ScreenSpaceOffset = PVecInt.Zero, Facing = 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++)
|
||||||
|
{
|
||||||
|
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();
|
Barrels = barrels.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,9 +123,12 @@ namespace OpenRA.Mods.RA
|
|||||||
{
|
{
|
||||||
if (FireDelay > 0)
|
if (FireDelay > 0)
|
||||||
--FireDelay;
|
--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)
|
public void CheckFire(Actor self, AttackBase attack, IMove move, IFacing facing, Target target)
|
||||||
{
|
{
|
||||||
if (FireDelay > 0) return;
|
if (FireDelay > 0) return;
|
||||||
@@ -103,20 +144,32 @@ namespace OpenRA.Mods.RA
|
|||||||
var barrel = Barrels[Burst % Barrels.Length];
|
var barrel = Barrels[Burst % Barrels.Length];
|
||||||
var destMove = target.IsActor ? target.Actor.TraitOrDefault<IMove>() : null;
|
var destMove = target.IsActor ? target.Actor.TraitOrDefault<IMove>() : 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
|
var args = new ProjectileArgs
|
||||||
{
|
{
|
||||||
weapon = Weapon,
|
weapon = Weapon,
|
||||||
firedBy = self,
|
firedBy = self,
|
||||||
target = target,
|
target = target,
|
||||||
|
src = legacyMuzzlePosition,
|
||||||
|
srcAltitude = legacyMuzzleAltitude,
|
||||||
|
|
||||||
src = (self.CenterLocation + (PVecInt)MuzzlePxPosition(self, facing, barrel).ToInt2()),
|
|
||||||
srcAltitude = move != null ? move.Altitude : 0,
|
|
||||||
dest = target.CenterLocation,
|
dest = target.CenterLocation,
|
||||||
destAltitude = destMove != null ? destMove.Altitude : 0,
|
destAltitude = destMove != null ? destMove.Altitude : 0,
|
||||||
|
|
||||||
facing = barrel.Facing +
|
facing = legacyFacing,
|
||||||
(Turret.Value != null ? Turret.Value.turretFacing :
|
|
||||||
facing != null ? facing.Facing : Util.GetFacing(target.CenterLocation - self.CenterLocation, 0)),
|
|
||||||
|
|
||||||
firepowerModifier = self.TraitsImplementing<IFirepowerModifier>()
|
firepowerModifier = self.TraitsImplementing<IFirepowerModifier>()
|
||||||
.Select(a => a.GetFirepowerModifier())
|
.Select(a => a.GetFirepowerModifier())
|
||||||
@@ -139,7 +192,8 @@ namespace OpenRA.Mods.RA
|
|||||||
foreach (var na in self.TraitsImplementing<INotifyAttack>())
|
foreach (var na in self.TraitsImplementing<INotifyAttack>())
|
||||||
na.Attacking(self, target);
|
na.Attacking(self, target);
|
||||||
|
|
||||||
Recoil = Info.LegacyRecoil;
|
LegacyRecoil = Info.LegacyRecoil;
|
||||||
|
Recoil = Info.Recoil;
|
||||||
|
|
||||||
if (--Burst > 0)
|
if (--Burst > 0)
|
||||||
FireDelay = Weapon.BurstDelay;
|
FireDelay = Weapon.BurstDelay;
|
||||||
@@ -169,8 +223,13 @@ namespace OpenRA.Mods.RA
|
|||||||
return (PVecFloat)Util.RotateVectorByFacing(b.TurretSpaceOffset.ToFloat2(), turretFacing, .7f);
|
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;
|
PVecFloat pos = b.ScreenSpaceOffset;
|
||||||
|
|
||||||
// local facing offset doesn't make sense for actors that don't rotate
|
// local facing offset doesn't make sense for actors that don't rotate
|
||||||
@@ -192,10 +251,29 @@ namespace OpenRA.Mods.RA
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PVecFloat RecoilPxOffset(Actor self, int facing)
|
public WVec MuzzleOffset(Actor self, Barrel b)
|
||||||
{
|
{
|
||||||
var localRecoil = new float2(0, Recoil);
|
if (Info.OffsetModel != CoordinateModel.World)
|
||||||
return (PVecFloat)Util.RotateVectorByFacing(localRecoil, facing, .7f);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,19 +37,32 @@ namespace OpenRA.Mods.RA.Render
|
|||||||
anim.Play("turret");
|
anim.Play("turret");
|
||||||
|
|
||||||
anims.Add("turret_{0}".F(i++), new AnimationWithOffset(anim,
|
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<Armament>()
|
if (t.CoordinateModel == CoordinateModel.Legacy)
|
||||||
.OrderByDescending(w => w.Recoil)
|
{
|
||||||
.FirstOrDefault(w => w.Info.Turret == t.info.Turret);
|
var recoil = self.TraitsImplementing<Armament>()
|
||||||
if (a == null)
|
.Where(w => w.Info.Turret == t.Name)
|
||||||
return float2.Zero;
|
.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<Armament>()
|
||||||
|
.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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace OpenRA.Mods.RA.Render
|
|||||||
{
|
{
|
||||||
var barrel = b;
|
var barrel = b;
|
||||||
var turreted = self.TraitsImplementing<Turreted>()
|
var turreted = self.TraitsImplementing<Turreted>()
|
||||||
.FirstOrDefault(t => t.info.Turret == a.Info.Turret);
|
.FirstOrDefault(t => t.Name == a.Info.Turret);
|
||||||
var getFacing = turreted != null ? () => turreted.turretFacing :
|
var getFacing = turreted != null ? () => turreted.turretFacing :
|
||||||
facing != null ? (Func<int>)(() => facing.Facing) : () => 0;
|
facing != null ? (Func<int>)(() => facing.Facing) : () => 0;
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ namespace OpenRA.Mods.RA.Render
|
|||||||
|
|
||||||
muzzleFlashes.Add("muzzle{0}".F(muzzleFlashes.Count), new AnimationWithOffset(
|
muzzleFlashes.Add("muzzle{0}".F(muzzleFlashes.Count), new AnimationWithOffset(
|
||||||
muzzleFlash,
|
muzzleFlash,
|
||||||
() => a.MuzzlePxPosition(self, facing, barrel).ToFloat2(),
|
() => a.MuzzlePxOffset(self, facing, barrel).ToFloat2(),
|
||||||
() => !isShowing));
|
() => !isShowing));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using OpenRA.Mods.RA.Render;
|
using OpenRA.Mods.RA.Render;
|
||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
@@ -24,6 +25,10 @@ namespace OpenRA.Mods.RA
|
|||||||
public readonly int[] LegacyOffset = {0,0};
|
public readonly int[] LegacyOffset = {0,0};
|
||||||
public readonly bool AlignWhenIdle = false;
|
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); }
|
public virtual object Create(ActorInitializer init) { return new Turreted(init, this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,10 +36,17 @@ namespace OpenRA.Mods.RA
|
|||||||
{
|
{
|
||||||
[Sync] public int turretFacing = 0;
|
[Sync] public int turretFacing = 0;
|
||||||
public int? desiredFacing;
|
public int? desiredFacing;
|
||||||
public TurretedInfo info;
|
TurretedInfo info;
|
||||||
protected Turret turret;
|
protected Turret turret;
|
||||||
IFacing facing;
|
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)
|
public static int GetInitialTurretFacing(ActorInitializer init, int def)
|
||||||
{
|
{
|
||||||
if (init.Contains<TurretFacingInit>())
|
if (init.Contains<TurretFacingInit>())
|
||||||
@@ -74,8 +86,30 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public PVecFloat PxPosition(Actor self, IFacing facing)
|
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);
|
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<ILocalCoordinatesModel>();
|
||||||
|
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
|
public class Turret
|
||||||
|
|||||||
Reference in New Issue
Block a user