Missile projectile refactor.
Introduces: Vertical rate of turn Customizable vertical launch angle Delayed activation of the homing mechanism / propulsion Freefall while propulsion deactivated Customizable explosion altitude (for airburst) Customizable cruise altitude (when target out of range) Height checks for terrain impact and shadow rendering Acceleration (instead of constant speed from launch to impact)
This commit is contained in:
@@ -22,18 +22,29 @@ namespace OpenRA.Mods.Common.Effects
|
|||||||
{
|
{
|
||||||
public class MissileInfo : IProjectileInfo
|
public class MissileInfo : IProjectileInfo
|
||||||
{
|
{
|
||||||
|
[Desc("Name of the image containing the projectile sequence.")]
|
||||||
public readonly string Image = null;
|
public readonly string Image = null;
|
||||||
|
|
||||||
|
[Desc("Projectile sequence name.")]
|
||||||
[SequenceReference("Image")] public readonly string Sequence = "idle";
|
[SequenceReference("Image")] public readonly string Sequence = "idle";
|
||||||
|
|
||||||
|
[Desc("Palette used to render the projectile sequence.")]
|
||||||
[PaletteReference] public readonly string Palette = "effect";
|
[PaletteReference] public readonly string Palette = "effect";
|
||||||
|
|
||||||
|
[Desc("Should the projectile's shadow be rendered?")]
|
||||||
public readonly bool Shadow = false;
|
public readonly bool Shadow = false;
|
||||||
|
|
||||||
[Desc("Projectile speed in WDist / tick")]
|
[Desc("Projectile speed in WDist / tick")]
|
||||||
public readonly WDist Speed = new WDist(8);
|
public readonly WDist InitialSpeed = new WDist(8);
|
||||||
|
|
||||||
[Desc("Maximum vertical pitch when changing altitude.")]
|
[Desc("Vertical launch angle (pitch).")]
|
||||||
public readonly WAngle MaximumPitch = WAngle.FromDegrees(30);
|
public readonly WAngle LaunchAngle = WAngle.Zero;
|
||||||
|
|
||||||
|
[Desc("Maximum projectile speed in WDist / tick")]
|
||||||
|
public readonly WDist MaximumSpeed = new WDist(512);
|
||||||
|
|
||||||
|
[Desc("Projectile acceleration when propulsion activated.")]
|
||||||
|
public readonly WDist Acceleration = WDist.Zero;
|
||||||
|
|
||||||
[Desc("How many ticks before this missile is armed and can explode.")]
|
[Desc("How many ticks before this missile is armed and can explode.")]
|
||||||
public readonly int Arm = 0;
|
public readonly int Arm = 0;
|
||||||
@@ -47,29 +58,59 @@ namespace OpenRA.Mods.Common.Effects
|
|||||||
[Desc("Probability of locking onto and following target.")]
|
[Desc("Probability of locking onto and following target.")]
|
||||||
public readonly int LockOnProbability = 100;
|
public readonly int LockOnProbability = 100;
|
||||||
|
|
||||||
[Desc("In n/256 per tick.")]
|
[Desc("Horizontal rate of turn.")]
|
||||||
public readonly int RateOfTurn = 5;
|
public readonly int HorizontalRateOfTurn = 5;
|
||||||
|
|
||||||
[Desc("Explode when following the target longer than this many ticks.")]
|
[Desc("Vertical rate of turn.")]
|
||||||
|
public readonly int VerticalRateOfTurn = 5;
|
||||||
|
|
||||||
|
[Desc("Run out of fuel after being activated this many ticks. Zero for unlimited fuel.")]
|
||||||
public readonly int RangeLimit = 0;
|
public readonly int RangeLimit = 0;
|
||||||
|
|
||||||
[Desc("Trail animation.")]
|
[Desc("Explode when running out of fuel.")]
|
||||||
public readonly string Trail = null;
|
public readonly bool ExplodeWhenEmpty = true;
|
||||||
|
|
||||||
[Desc("Interval in ticks between each spawned Trail animation.")]
|
[Desc("Altitude above terrain below which to explode. Zero effectively deactivates airburst.")]
|
||||||
public readonly int TrailInterval = 2;
|
public readonly WDist AirburstAltitude = WDist.Zero;
|
||||||
|
|
||||||
|
[Desc("Cruise altitude. Zero means no cruise altitude used.")]
|
||||||
|
public readonly WDist CruiseAltitude = new WDist(512);
|
||||||
|
|
||||||
|
[Desc("Activate homing mechanism after this many ticks.")]
|
||||||
|
public readonly int HomingActivationDelay = 0;
|
||||||
|
|
||||||
|
[Desc("Image that contains the trail animation.")]
|
||||||
|
public readonly string TrailImage = null;
|
||||||
|
|
||||||
|
[Desc("Smoke sequence name.")]
|
||||||
|
[SequenceReference("Trail")] public readonly string TrailSequence = "idle";
|
||||||
|
|
||||||
|
[Desc("Palette used to render the smoke sequence.")]
|
||||||
[PaletteReference("TrailUsePlayerPalette")] public readonly string TrailPalette = "effect";
|
[PaletteReference("TrailUsePlayerPalette")] public readonly string TrailPalette = "effect";
|
||||||
|
|
||||||
|
[Desc("Use the Player Palette to render the smoke sequence.")]
|
||||||
public readonly bool TrailUsePlayerPalette = false;
|
public readonly bool TrailUsePlayerPalette = false;
|
||||||
|
|
||||||
|
[Desc("Interval in ticks between spawning smoke animation.")]
|
||||||
|
public readonly int TrailInterval = 2;
|
||||||
|
|
||||||
|
[Desc("Should smoke animation be spawned when the propulsion is not activated.")]
|
||||||
|
public readonly bool TrailWhenDeactivated = false;
|
||||||
|
|
||||||
public readonly int ContrailLength = 0;
|
public readonly int ContrailLength = 0;
|
||||||
|
|
||||||
public readonly Color ContrailColor = Color.White;
|
public readonly Color ContrailColor = Color.White;
|
||||||
|
|
||||||
public readonly bool ContrailUsePlayerColor = false;
|
public readonly bool ContrailUsePlayerColor = false;
|
||||||
|
|
||||||
public readonly int ContrailDelay = 1;
|
public readonly int ContrailDelay = 1;
|
||||||
|
|
||||||
[Desc("Should missile targeting be thrown off by nearby actors with JamsMissiles.")]
|
[Desc("Should missile targeting be thrown off by nearby actors with JamsMissiles.")]
|
||||||
public readonly bool Jammable = true;
|
public readonly bool Jammable = true;
|
||||||
|
|
||||||
|
[Desc("Range of facings by which jammed missiles can stray from current path.")]
|
||||||
|
public readonly int JammedDiversionRange = 20;
|
||||||
|
|
||||||
[Desc("Explodes when leaving the following terrain type, e.g., Water for torpedoes.")]
|
[Desc("Explodes when leaving the following terrain type, e.g., Water for torpedoes.")]
|
||||||
public readonly string BoundToTerrainType = "";
|
public readonly string BoundToTerrainType = "";
|
||||||
|
|
||||||
@@ -87,12 +128,19 @@ namespace OpenRA.Mods.Common.Effects
|
|||||||
readonly ProjectileArgs args;
|
readonly ProjectileArgs args;
|
||||||
readonly Animation anim;
|
readonly Animation anim;
|
||||||
|
|
||||||
|
readonly WVec gravity = new WVec(0, 0, -10);
|
||||||
|
|
||||||
int ticksToNextSmoke;
|
int ticksToNextSmoke;
|
||||||
ContrailRenderable contrail;
|
ContrailRenderable contrail;
|
||||||
string trailPalette;
|
string trailPalette;
|
||||||
|
int terrainHeight;
|
||||||
|
|
||||||
[Sync] WPos pos;
|
[Sync] WPos pos;
|
||||||
[Sync] int facing;
|
[Sync] WVec velocity;
|
||||||
|
[Sync] int hFacing;
|
||||||
|
[Sync] int vFacing;
|
||||||
|
[Sync] bool activated;
|
||||||
|
[Sync] int speed;
|
||||||
|
|
||||||
[Sync] WPos targetPosition;
|
[Sync] WPos targetPosition;
|
||||||
[Sync] WVec offset;
|
[Sync] WVec offset;
|
||||||
@@ -109,7 +157,13 @@ namespace OpenRA.Mods.Common.Effects
|
|||||||
this.args = args;
|
this.args = args;
|
||||||
|
|
||||||
pos = args.Source;
|
pos = args.Source;
|
||||||
facing = args.Facing;
|
hFacing = args.Facing;
|
||||||
|
vFacing = info.LaunchAngle.Angle / 4;
|
||||||
|
|
||||||
|
speed = info.InitialSpeed.Length;
|
||||||
|
velocity = new WVec(WDist.Zero, -info.InitialSpeed, WDist.Zero)
|
||||||
|
.Rotate(new WRot(WAngle.FromFacing(vFacing), WAngle.Zero, WAngle.Zero))
|
||||||
|
.Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing)));
|
||||||
|
|
||||||
targetPosition = args.PassiveTarget;
|
targetPosition = args.PassiveTarget;
|
||||||
|
|
||||||
@@ -126,8 +180,8 @@ namespace OpenRA.Mods.Common.Effects
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.Image))
|
if (!string.IsNullOrEmpty(info.Image))
|
||||||
{
|
{
|
||||||
anim = new Animation(world, info.Image, () => facing);
|
anim = new Animation(world, info.Image, () => hFacing);
|
||||||
anim.PlayRepeating("idle");
|
anim.PlayRepeating(info.Sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.ContrailLength > 0)
|
if (info.ContrailLength > 0)
|
||||||
@@ -158,52 +212,144 @@ namespace OpenRA.Mods.Common.Effects
|
|||||||
if (anim != null)
|
if (anim != null)
|
||||||
anim.Tick();
|
anim.Tick();
|
||||||
|
|
||||||
// Missile tracks target
|
var cell = world.Map.CellContaining(pos);
|
||||||
|
terrainHeight = world.Map.MapHeight.Value[cell] * 512;
|
||||||
|
|
||||||
|
// Switch from freefall mode to homing mode
|
||||||
|
if (ticks == info.HomingActivationDelay + 1)
|
||||||
|
{
|
||||||
|
activated = true;
|
||||||
|
hFacing = OpenRA.Traits.Util.GetFacing(velocity, hFacing);
|
||||||
|
speed = velocity.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch from homing mode to freefall mode
|
||||||
|
if (info.RangeLimit != 0 && ticks == info.RangeLimit + 1)
|
||||||
|
{
|
||||||
|
activated = false;
|
||||||
|
velocity = new WVec(0, -speed, 0)
|
||||||
|
.Rotate(new WRot(WAngle.FromFacing(vFacing), WAngle.Zero, WAngle.Zero))
|
||||||
|
.Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if target position should be updated (actor visible & locked on)
|
||||||
if (args.GuidedTarget.IsValidFor(args.SourceActor) && lockOn)
|
if (args.GuidedTarget.IsValidFor(args.SourceActor) && lockOn)
|
||||||
targetPosition = args.GuidedTarget.CenterPosition;
|
targetPosition = args.GuidedTarget.CenterPosition + new WVec(WDist.Zero, WDist.Zero, info.AirburstAltitude);
|
||||||
|
|
||||||
|
// Compute current distance from target position
|
||||||
var dist = targetPosition + offset - pos;
|
var dist = targetPosition + offset - pos;
|
||||||
var desiredFacing = OpenRA.Traits.Util.GetFacing(dist, facing);
|
var len = dist.Length;
|
||||||
var desiredAltitude = targetPosition.Z;
|
var hLenCurr = dist.HorizontalLength;
|
||||||
var jammed = info.Jammable && world.ActorsWithTrait<JamsMissiles>().Any(JammedBy);
|
|
||||||
|
|
||||||
|
WVec move;
|
||||||
|
if (activated)
|
||||||
|
{
|
||||||
|
// If target is within range, keep speed constant and aim for the target.
|
||||||
|
// The speed needs to be kept constant to keep range computation relatively simple.
|
||||||
|
|
||||||
|
// If target is not within range, accelerate the projectile. If cruise altitudes
|
||||||
|
// are not used, aim for the target. If the cruise altitudes are used, aim for the
|
||||||
|
// target horizontally and for cruise altitude vertically.
|
||||||
|
|
||||||
|
// Target is considered in range if after an additional tick of accelerated motion
|
||||||
|
// the horizontal distance from the target would be less than
|
||||||
|
// the diameter of the circle that the missile travels along when
|
||||||
|
// turning vertically at the maximum possible rate.
|
||||||
|
// This should work because in the worst case, the missile will have to make
|
||||||
|
// a semi-loop before hitting the target.
|
||||||
|
|
||||||
|
// Get underestimate of distance from target in next tick, so that inRange would
|
||||||
|
// become true a little sooner than the theoretical "in range" condition is met.
|
||||||
|
var hLenNext = (long)(hLenCurr - speed - info.Acceleration.Length).Clamp(0, hLenCurr);
|
||||||
|
|
||||||
|
// Check if target in range
|
||||||
|
bool inRange = hLenNext * hLenNext * info.VerticalRateOfTurn * info.VerticalRateOfTurn * 314 * 314
|
||||||
|
<= 2L * 2 * speed * speed * 128 * 128 * 100 * 100;
|
||||||
|
|
||||||
|
// Basically vDist is the representation in the x-y plane
|
||||||
|
// of the projection of dist in the z-hDist plane,
|
||||||
|
// where hDist is the projection of dist in the x-y plane.
|
||||||
|
|
||||||
|
// This allows applying vertical rate of turn in the same way as the
|
||||||
|
// horizontal rate of turn is applied.
|
||||||
|
WVec vDist;
|
||||||
|
if (inRange || info.CruiseAltitude.Length == 0)
|
||||||
|
vDist = new WVec(-dist.Z, -hLenCurr, 0);
|
||||||
|
else
|
||||||
|
vDist = new WVec(-(dist.Z - targetPosition.Z + info.CruiseAltitude.Length + terrainHeight), -speed, 0);
|
||||||
|
|
||||||
|
// Accelerate if out of range
|
||||||
|
if (!inRange)
|
||||||
|
speed = (speed + info.Acceleration.Length).Clamp(0, info.MaximumSpeed.Length);
|
||||||
|
|
||||||
|
// Compute which direction the projectile should be facing
|
||||||
|
var desiredHFacing = OpenRA.Traits.Util.GetFacing(dist, hFacing);
|
||||||
|
var desiredVFacing = OpenRA.Traits.Util.GetFacing(vDist, vFacing);
|
||||||
|
|
||||||
|
// Check whether the homing mechanism is jammed
|
||||||
|
var jammed = info.Jammable && world.ActorsWithTrait<JamsMissiles>().Any(JammedBy);
|
||||||
if (jammed)
|
if (jammed)
|
||||||
{
|
{
|
||||||
desiredFacing = facing + world.SharedRandom.Next(-20, 21);
|
desiredHFacing = hFacing + world.SharedRandom.Next(-info.JammedDiversionRange, info.JammedDiversionRange + 1);
|
||||||
desiredAltitude = world.SharedRandom.Next(-43, 86);
|
desiredVFacing = vFacing + world.SharedRandom.Next(-info.JammedDiversionRange, info.JammedDiversionRange + 1);
|
||||||
}
|
}
|
||||||
else if (!args.GuidedTarget.IsValidFor(args.SourceActor))
|
else if (!args.GuidedTarget.IsValidFor(args.SourceActor))
|
||||||
desiredFacing = facing;
|
desiredHFacing = hFacing;
|
||||||
|
|
||||||
facing = OpenRA.Traits.Util.TickFacing(facing, desiredFacing, info.RateOfTurn);
|
// Compute new direction the projectile will be facing
|
||||||
var move = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)) * info.Speed.Length / 1024;
|
hFacing = OpenRA.Traits.Util.TickFacing(hFacing, desiredHFacing, info.HorizontalRateOfTurn);
|
||||||
|
vFacing = OpenRA.Traits.Util.TickFacing(vFacing, desiredVFacing, info.VerticalRateOfTurn);
|
||||||
|
|
||||||
if (pos.Z != desiredAltitude)
|
// Compute the projectile's guided displacement
|
||||||
|
move = new WVec(0, -1024 * speed, 0)
|
||||||
|
.Rotate(new WRot(WAngle.FromFacing(vFacing), WAngle.Zero, WAngle.Zero))
|
||||||
|
.Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing)))
|
||||||
|
/ 1024;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var delta = move.HorizontalLength * info.MaximumPitch.Tan() / 1024;
|
// Compute the projectile's freefall displacement
|
||||||
var dz = (targetPosition.Z - pos.Z).Clamp(-delta, delta);
|
move = velocity + gravity / 2;
|
||||||
move += new WVec(0, 0, dz);
|
velocity += gravity;
|
||||||
|
var velRatio = info.MaximumSpeed.Length * 1024 / velocity.Length;
|
||||||
|
if (velRatio < 1024)
|
||||||
|
velocity = velocity * velRatio / 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When move (speed) is large, check for impact during the following next tick
|
||||||
|
// Shorten the move to have its length match the distance from the target
|
||||||
|
// and check for impact with the shortened move
|
||||||
|
var movLen = move.Length;
|
||||||
|
if (len < movLen)
|
||||||
|
{
|
||||||
|
var npos = pos + move * 1024 * len / movLen / 1024;
|
||||||
|
if (world.Map.DistanceAboveTerrain(npos).Length <= 0 // Hit the ground
|
||||||
|
|| (targetPosition + offset - npos).LengthSquared < info.CloseEnough.LengthSquared) // Within range
|
||||||
|
pos = npos;
|
||||||
|
else
|
||||||
|
pos += move;
|
||||||
|
}
|
||||||
|
else
|
||||||
pos += move;
|
pos += move;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.Trail) && --ticksToNextSmoke < 0)
|
// Create the smoke trail effect
|
||||||
|
if (!string.IsNullOrEmpty(info.TrailImage) && --ticksToNextSmoke < 0 && (activated || info.TrailWhenDeactivated))
|
||||||
{
|
{
|
||||||
world.AddFrameEndTask(w => w.Add(new Smoke(w, pos - 3 * move / 2, info.Trail, trailPalette, info.Sequence)));
|
world.AddFrameEndTask(w => w.Add(new Smoke(w, pos - 3 * move / 2, info.TrailImage, trailPalette, info.TrailSequence)));
|
||||||
ticksToNextSmoke = info.TrailInterval;
|
ticksToNextSmoke = info.TrailInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.ContrailLength > 0)
|
if (info.ContrailLength > 0)
|
||||||
contrail.Update(pos);
|
contrail.Update(pos);
|
||||||
|
|
||||||
var cell = world.Map.CellContaining(pos);
|
var height = world.Map.DistanceAboveTerrain(pos);
|
||||||
|
var shouldExplode = (height.Length <= 0) // Hit the ground
|
||||||
var shouldExplode = (pos.Z < 0) // Hit the ground
|
|| (len < info.CloseEnough.Length) // Within range
|
||||||
|| (dist.LengthSquared < info.CloseEnough.LengthSquared) // Within range
|
|| (info.ExplodeWhenEmpty && info.RangeLimit != 0 && ticks > info.RangeLimit) // Ran out of fuel
|
||||||
|| (info.RangeLimit != 0 && ticks > info.RangeLimit) // Ran out of fuel
|
|
||||||
|| (info.Blockable && BlocksProjectiles.AnyBlockingActorAt(world, pos)) // Hit a wall or other blocking obstacle
|
|| (info.Blockable && BlocksProjectiles.AnyBlockingActorAt(world, pos)) // Hit a wall or other blocking obstacle
|
||||||
|| !world.Map.Contains(cell) // This also avoids an IndexOutOfRangeException in GetTerrainInfo below.
|
|| !world.Map.Contains(cell) // This also avoids an IndexOutOfRangeException in GetTerrainInfo below.
|
||||||
|| (!string.IsNullOrEmpty(info.BoundToTerrainType) && world.Map.GetTerrainInfo(cell).Type != info.BoundToTerrainType); // Hit incompatible terrain
|
|| (!string.IsNullOrEmpty(info.BoundToTerrainType) && world.Map.GetTerrainInfo(cell).Type != info.BoundToTerrainType) // Hit incompatible terrain
|
||||||
|
|| (height.Length < info.AirburstAltitude.Length && hLenCurr < info.CloseEnough.Length); // Airburst
|
||||||
|
|
||||||
if (shouldExplode)
|
if (shouldExplode)
|
||||||
Explode(world);
|
Explode(world);
|
||||||
|
|||||||
@@ -2718,6 +2718,18 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (engineVersion < 20150912)
|
||||||
|
{
|
||||||
|
if (depth == 2 && parentKey == "Projectile" && parent.Value.Value == "Missile" && node.Key == "Speed")
|
||||||
|
node.Key = "InitialSpeed";
|
||||||
|
|
||||||
|
if (depth == 2 && parentKey == "Projectile" && parent.Value.Value == "Missile" && node.Key == "RateOfTurn")
|
||||||
|
node.Key = "HorizontalRateOfTurn";
|
||||||
|
|
||||||
|
if (depth == 2 && parentKey == "Projectile" && parent.Value.Value == "Missile" && node.Key == "Trail")
|
||||||
|
node.Key = "TrailImage";
|
||||||
|
}
|
||||||
|
|
||||||
UpgradeWeaponRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
|
UpgradeWeaponRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user