diff --git a/OpenRA.Mods.Common/Effects/Missile.cs b/OpenRA.Mods.Common/Effects/Missile.cs index c82d959a6b..743989add0 100644 --- a/OpenRA.Mods.Common/Effects/Missile.cs +++ b/OpenRA.Mods.Common/Effects/Missile.cs @@ -22,18 +22,35 @@ namespace OpenRA.Mods.Common.Effects { public class MissileInfo : IProjectileInfo { + [Desc("Name of the image containing the projectile sequence.")] public readonly string Image = null; + + [Desc("Projectile sequence name.")] [SequenceReference("Image")] public readonly string Sequence = "idle"; + [Desc("Palette used to render the projectile sequence.")] [PaletteReference] public readonly string Palette = "effect"; + [Desc("Should the projectile's shadow be rendered?")] public readonly bool Shadow = false; - [Desc("Projectile speed in WDist / tick")] - public readonly WDist Speed = new WDist(8); + [Desc("Minimum vertical launch angle (pitch).")] + public readonly WAngle MinimumLaunchAngle = WAngle.Zero; - [Desc("Maximum vertical pitch when changing altitude.")] - public readonly WAngle MaximumPitch = WAngle.FromDegrees(30); + [Desc("Maximum vertical launch angle (pitch).")] + public readonly WAngle MaximumLaunchAngle = new WAngle(64); + + [Desc("Minimum launch speed in WDist / tick")] + public readonly WDist MinimumLaunchSpeed = new WDist(75); + + [Desc("Maximum launch speed in WDist / tick")] + public readonly WDist MaximumLaunchSpeed = new WDist(200); + + [Desc("Maximum projectile speed in WDist / tick")] + public readonly WDist MaximumSpeed = new WDist(384); + + [Desc("Projectile acceleration when propulsion activated.")] + public readonly WDist Acceleration = new WDist(5); [Desc("How many ticks before this missile is armed and can explode.")] public readonly int Arm = 0; @@ -47,29 +64,59 @@ namespace OpenRA.Mods.Common.Effects [Desc("Probability of locking onto and following target.")] public readonly int LockOnProbability = 100; - [Desc("In n/256 per tick.")] - public readonly int RateOfTurn = 5; + [Desc("Horizontal rate of turn.")] + 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 = 6; + + [Desc("Run out of fuel after being activated this many ticks. Zero for unlimited fuel.")] public readonly int RangeLimit = 0; - [Desc("Trail animation.")] - public readonly string Trail = null; + [Desc("Explode when running out of fuel.")] + public readonly bool ExplodeWhenEmpty = true; - [Desc("Interval in ticks between each spawned Trail animation.")] - public readonly int TrailInterval = 2; + [Desc("Altitude above terrain below which to explode. Zero effectively deactivates airburst.")] + 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("TrailImage")] public readonly string TrailSequence = "idle"; + + [Desc("Palette used to render the smoke sequence.")] [PaletteReference("TrailUsePlayerPalette")] public readonly string TrailPalette = "effect"; + + [Desc("Use the Player Palette to render the smoke sequence.")] 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 Color ContrailColor = Color.White; + public readonly bool ContrailUsePlayerColor = false; + public readonly int ContrailDelay = 1; [Desc("Should missile targeting be thrown off by nearby actors with JamsMissiles.")] 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.")] public readonly string BoundToTerrainType = ""; @@ -81,27 +128,51 @@ namespace OpenRA.Mods.Common.Effects public IEffect Create(ProjectileArgs args) { return new Missile(this, args); } } + // TODO: double check square roots!!! public class Missile : IEffect, ISync { + enum States + { + Freefall, + Homing, + Hitting + } + readonly MissileInfo info; readonly ProjectileArgs args; readonly Animation anim; + // NOTE: Might be desirable to unhardcode the number -10 + readonly WVec gravity = new WVec(0, 0, -10); + + int ticks; + int ticksToNextSmoke; ContrailRenderable contrail; string trailPalette; + States state; + bool targetPassedBy; + bool lockOn = false; + bool allowPassBy; // TODO: use this also with high minimum launch angle settings + + WPos targetPosition; + WVec offset; + + WVec tarVel; + WVec predVel; + [Sync] WPos pos; - [Sync] int facing; + WVec velocity; + int speed; + int loopRadius; - [Sync] WPos targetPosition; - [Sync] WVec offset; - [Sync] int ticks; + int renderFacing; + [Sync] int hFacing; + [Sync] int vFacing; - [Sync] bool lockOn = false; - - [Sync] public Actor SourceActor { get { return args.SourceActor; } } - [Sync] public Target GuidedTarget { get { return args.GuidedTarget; } } + public Actor SourceActor { get { return args.SourceActor; } } + public Target GuidedTarget { get { return args.GuidedTarget; } } public Missile(MissileInfo info, ProjectileArgs args) { @@ -109,25 +180,30 @@ namespace OpenRA.Mods.Common.Effects this.args = args; pos = args.Source; - facing = args.Facing; - + hFacing = args.Facing; targetPosition = args.PassiveTarget; var world = args.SourceActor.World; - if (world.SharedRandom.Next(100) <= info.LockOnProbability) - lockOn = true; - if (info.Inaccuracy.Length > 0) { var inaccuracy = OpenRA.Traits.Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers); offset = WVec.FromPDF(world.SharedRandom, 2) * inaccuracy / 1024; } + DetermineLaunchSpeedAndAngle(world, out speed, out vFacing); + + 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))); + + if (world.SharedRandom.Next(100) <= info.LockOnProbability) + lockOn = true; + if (!string.IsNullOrEmpty(info.Image)) { - anim = new Animation(world, info.Image, () => facing); - anim.PlayRepeating("idle"); + anim = new Animation(world, info.Image, () => renderFacing); + anim.PlayRepeating(info.Sequence); } if (info.ContrailLength > 0) @@ -141,6 +217,159 @@ namespace OpenRA.Mods.Common.Effects trailPalette += args.SourceActor.Owner.InternalName; } + static int LoopRadius(int speed, int rot) + { + // loopRadius in w-units = speed in w-units per tick / angular speed in radians per tick + // angular speed in radians per tick = rot in facing units per tick * (pi radians / 128 facing units) + // pi = 314 / 100 + // ==> loopRadius = (speed * 128 * 100) / (314 * rot) + return (speed * 6400) / (157 * rot); + } + + void DetermineLaunchSpeedAndAngleForIncline(int predClfDist, int diffClfMslHgt, int relTarHorDist, + out int speed, out int vFacing) + { + speed = info.MaximumLaunchSpeed.Length; + + // Find smallest vertical facing, for which the missile will be able to climb terrAltDiff w-units + // within hHeightChange w-units all the while ending the ascent with vertical facing 0 + vFacing = info.MaximumLaunchAngle.Angle >> 2; + + // Compute minimum speed necessary to both be able to face directly upwards and have enough space + // to hit the target without passing it by (and thus having to do horizontal loops) + var minSpeed = ((System.Math.Min(predClfDist * 1024 / (1024 - WAngle.FromFacing(vFacing).Sin()), + (relTarHorDist + predClfDist) * 1024 / (2 * (2048 - WAngle.FromFacing(vFacing).Sin()))) + * info.VerticalRateOfTurn * 157) / 6400).Clamp(info.MinimumLaunchSpeed.Length, info.MaximumLaunchSpeed.Length); + + if ((sbyte)vFacing < 0) + speed = minSpeed; + else if (!WillClimbWithinDistance(vFacing, loopRadius, predClfDist, diffClfMslHgt) + && !WillClimbAroundInclineTop(vFacing, loopRadius, predClfDist, diffClfMslHgt, speed)) + { + // Find highest speed greater than the above minimum that allows the missile + // to surmount the incline + var vFac = vFacing; + speed = BisectionSearch(minSpeed, info.MaximumLaunchSpeed.Length, spd => + { + var lpRds = LoopRadius(spd, info.VerticalRateOfTurn); + return WillClimbWithinDistance(vFac, lpRds, predClfDist, diffClfMslHgt) + || WillClimbAroundInclineTop(vFac, lpRds, predClfDist, diffClfMslHgt, spd); + }); + } + else + { + // Find least vertical facing that will allow the missile to climb + // terrAltDiff w-units within hHeightChange w-units + // all the while ending the ascent with vertical facing 0 + vFacing = BisectionSearch(System.Math.Max((sbyte)(info.MinimumLaunchAngle.Angle >> 2), (sbyte)0), + (sbyte)(info.MaximumLaunchAngle.Angle >> 2), + vFac => !WillClimbWithinDistance(vFac, loopRadius, predClfDist, diffClfMslHgt)) + 1; + } + } + + // TODO: Double check Launch parameter determination + void DetermineLaunchSpeedAndAngle(World world, out int speed, out int vFacing) + { + speed = info.MaximumLaunchSpeed.Length; + loopRadius = LoopRadius(speed, info.VerticalRateOfTurn); + + // Compute current distance from target position + var tarDistVec = targetPosition + offset - pos; + var relTarHorDist = tarDistVec.HorizontalLength; + + int predClfHgt, predClfDist, lastHtChg, lastHt; + InclineLookahead(world, relTarHorDist, out predClfHgt, out predClfDist, out lastHtChg, out lastHt); + + // Height difference between the incline height and missile height + var diffClfMslHgt = predClfHgt - pos.Z; + + // Incline coming up + if (diffClfMslHgt >= 0) + DetermineLaunchSpeedAndAngleForIncline(predClfDist, diffClfMslHgt, relTarHorDist, out speed, out vFacing); + else if (lastHt != 0) + { + vFacing = System.Math.Max((sbyte)(info.MinimumLaunchAngle.Angle >> 2), (sbyte)0); + speed = info.MaximumLaunchSpeed.Length; + } + else + { + // Set vertical facing so that the missile faces its target + var vDist = new WVec(-tarDistVec.Z, -relTarHorDist, 0); + vFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, 0); + + // Do not accept -1 as valid vertical facing since it is usually a numerical error + // and will lead to premature descent and crashing into the ground + if (vFacing == -1) + vFacing = 0; + + // Make sure the chosen vertical facing adheres to prescribed bounds + vFacing = vFacing.Clamp((sbyte)(info.MinimumLaunchAngle.Angle >> 2), + (sbyte)(info.MaximumLaunchAngle.Angle >> 2)); + } + } + + // Will missile be able to climb terrAltDiff w-units within hHeightChange w-units + // all the while ending the ascent with vertical facing 0 + // Calling this function only makes sense when vFacing is nonnegative + static bool WillClimbWithinDistance(int vFacing, int loopRadius, int predClfDist, int diffClfMslHgt) + { + // Missile's horizontal distance from loop's center + var missDist = loopRadius * WAngle.FromFacing(vFacing).Sin() / 1024; + + // Missile's height below loop's top + var missHgt = loopRadius * (1024 - WAngle.FromFacing(vFacing).Cos()) / 1024; + + // Height that would be climbed without changing vertical facing + // for a horizontal distance hHeightChange - missDist + var hgtChg = (predClfDist - missDist) * WAngle.FromFacing(vFacing).Tan() / 1024; + + // Check if total manoeuvre height enough to overcome the incline's height + return hgtChg + missHgt >= diffClfMslHgt; + } + + // This function checks if the missile's vertical facing is + // nonnegative, and the incline top's horizontal distance from the missile is + // less than loopRadius * (1024 - WAngle.FromFacing(vFacing).Sin()) / 1024 + static bool IsNearInclineTop(int vFacing, int loopRadius, int predClfDist) + { + return vFacing >= 0 && predClfDist <= loopRadius * (1024 - WAngle.FromFacing(vFacing).Sin()) / 1024; + } + + // Will missile climb around incline top if bringing vertical facing + // down to zero on an arc of radius loopRadius + // Calling this function only makes sense when IsNearInclineTop returns true + static bool WillClimbAroundInclineTop(int vFacing, int loopRadius, int predClfDist, int diffClfMslHgt, int speed) + { + // Vector from missile's current position pointing to the loop's center + var radius = new WVec(loopRadius, 0, 0) + .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(System.Math.Max(0, 64 - vFacing)))); + + // Vector from loop's center to incline top + 64 hardcoded in height buffer zone + var topVector = new WVec(predClfDist, diffClfMslHgt + 64, 0) - radius; + + // Check if incline top inside of the vertical loop + return topVector.Length <= loopRadius; + } + + static int BisectionSearch(int lowerBound, int upperBound, System.Func testCriterion) + { + // Assuming that there exists an integer N between lowerBound and upperBound + // for which testCriterion returns true as well as all integers less than N, + // and for which testCriterion returns false for all integers greater than N, + // this function finds N. + while (upperBound - lowerBound > 1) + { + var middle = (upperBound + lowerBound) / 2; + + if (testCriterion(middle)) + lowerBound = middle; + else + upperBound = middle; + } + + return lowerBound; + } + bool JammedBy(TraitPair tp) { if ((tp.Actor.CenterPosition - pos).HorizontalLengthSquared > tp.Trait.Range.LengthSquared) @@ -152,44 +381,402 @@ namespace OpenRA.Mods.Common.Effects return tp.Actor.World.SharedRandom.Next(100 / tp.Trait.Chance) == 0; } + void ChangeSpeed(int sign = 1) + { + speed = (speed + sign * info.Acceleration.Length).Clamp(0, info.MaximumSpeed.Length); + + // Compute the vertical loop radius + loopRadius = LoopRadius(speed, info.VerticalRateOfTurn); + } + + WVec FreefallTick() + { + // Compute the projectile's freefall displacement + var move = velocity + gravity / 2; + velocity += gravity; + var velRatio = info.MaximumSpeed.Length * 1024 / velocity.Length; + if (velRatio < 1024) + velocity = velocity * velRatio / 1024; + + return move; + } + + // NOTE: It might be desirable to make lookahead more intelligent by outputting more information + // than just the highest point in the lookahead distance + void InclineLookahead(World world, int distCheck, out int predClfHgt, out int predClfDist, out int lastHtChg, out int lastHt) + { + predClfHgt = 0; // Highest probed terrain height + predClfDist = 0; // Distance from highest point + lastHtChg = 0; // Distance from last time the height changes + lastHt = 0; // Height just before the last height change + + // NOTE: Might be desired to unhardcode the lookahead step size + var stepSize = 32; + var step = new WVec(0, -stepSize, 0) + .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(hFacing))); // Step vector of length 128 + + // Probe terrain ahead of the missile + // NOTE: Might be desired to unhardcode maximum lookahead distance + var maxLookaheadDistance = loopRadius * 4; + var posProbe = pos; + var curDist = 0; + var tickLimit = System.Math.Min(maxLookaheadDistance, distCheck) / stepSize; + var prevHt = 0; + + // TODO: Make sure cell on map!!! + for (var tick = 0; tick <= tickLimit; tick++) + { + posProbe += step; + if (!world.Map.Contains(world.Map.CellContaining(posProbe))) + break; + + var ht = world.Map.MapHeight.Value[world.Map.CellContaining(posProbe)] * 512; + + curDist += stepSize; + if (ht > predClfHgt) + { + predClfHgt = ht; + predClfDist = curDist; + } + + if (prevHt != ht) + { + lastHtChg = curDist; + lastHt = prevHt; + prevHt = ht; + } + } + } + + int IncreaseAltitude(int predClfDist, int diffClfMslHgt, int relTarHorDist, int vFacing) + { + var desiredVFacing = vFacing; + + // If missile is below incline top height and facing downwards, bring back + // its vertical facing above zero as soon as possible + if ((sbyte)vFacing < 0) + desiredVFacing = info.VerticalRateOfTurn; + + // Missile will climb around incline top if bringing vertical facing + // down to zero on an arc of radius loopRadius + else if (IsNearInclineTop(vFacing, loopRadius, predClfDist) + && WillClimbAroundInclineTop(vFacing, loopRadius, predClfDist, diffClfMslHgt, speed)) + desiredVFacing = 0; + + // Missile will not climb terrAltDiff w-units within hHeightChange w-units + // all the while ending the ascent with vertical facing 0 + else if (!WillClimbWithinDistance(vFacing, loopRadius, predClfDist, diffClfMslHgt)) + + // Find smallest vertical facing, attainable in the next tick, + // for which the missile will be able to climb terrAltDiff w-units + // within hHeightChange w-units all the while ending the ascent + // with vertical facing 0 + for (var vFac = System.Math.Min(vFacing + info.VerticalRateOfTurn - 1, 63); vFac >= vFacing; vFac--) + if (!WillClimbWithinDistance(vFac, loopRadius, predClfDist, diffClfMslHgt) + && !(predClfDist <= loopRadius * (1024 - WAngle.FromFacing(vFac).Sin()) / 1024 + && WillClimbAroundInclineTop(vFac, loopRadius, predClfDist, diffClfMslHgt, speed))) + { + desiredVFacing = vFac + 1; + break; + } + + // Attained height after ascent as predicted from upper part of incline surmounting manoeuvre + var predAttHght = loopRadius * (1024 - WAngle.FromFacing(vFacing).Cos()) / 1024 - diffClfMslHgt; + + // Should the missile be slowed down in order to make it more manoeuverable + var slowDown = info.Acceleration.Length != 0 // Possible to decelerate + && ((desiredVFacing != 0 // Lower part of incline surmounting manoeuvre + + // Incline will be hit before vertical facing attains 64 + && (predClfDist <= loopRadius * (1024 - WAngle.FromFacing(vFacing).Sin()) / 1024 + + // When evaluating this the incline will be *not* be hit before vertical facing attains 64 + // At current speed target too close to hit without passing it by + || relTarHorDist <= 2 * loopRadius * (2048 - WAngle.FromFacing(vFacing).Sin()) / 1024 - predClfDist)) + + || (desiredVFacing == 0 // Upper part of incline surmounting manoeuvre + && relTarHorDist <= loopRadius * WAngle.FromFacing(vFacing).Sin() / 1024 + + Exts.ISqrt(predAttHght * (2 * loopRadius - predAttHght)))); // Target too close to hit at current speed + + if (slowDown) + ChangeSpeed(-1); + + return desiredVFacing; + } + + int HomingInnerTick(int predClfDist, int diffClfMslHgt, int relTarHorDist, int lastHtChg, int lastHt, + int nxtRelTarHorDist, int relTarHgt, int vFacing, bool targetPassedBy) + { + int desiredVFacing = vFacing; + + // Incline coming up -> attempt to reach the incline so that after predClfDist + // the height above the terrain is positive but as close to 0 as possible + // Also, never change horizontal facing and never travel backwards + // Possible techniques to avoid close cliffs are deceleration, turning + // as sharply as possible to travel directly upwards and then returning + // to zero vertical facing as low as possible while still not hittin the + // high terrain. A last technique (and the preferred one, normally used when + // the missile hasn't been fired near a cliff) is simply finding the smallest + // vertical facing that allows for a smooth climb to the new terrain's height + // and coming in at predClfDist at exactly zero vertical facing + if (diffClfMslHgt >= 0 && !allowPassBy) + desiredVFacing = IncreaseAltitude(predClfDist, diffClfMslHgt, relTarHorDist, vFacing); + else if (relTarHorDist <= 3 * loopRadius || state == States.Hitting) + { + // No longer travel at cruise altitude + state = States.Hitting; + + if (lastHt >= targetPosition.Z) + allowPassBy = true; + + if (!allowPassBy && (lastHt < targetPosition.Z || targetPassedBy)) + { + // Aim for the target + var vDist = new WVec(-relTarHgt, -relTarHorDist, 0); + desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing); + + // Do not accept -1 as valid vertical facing since it is usually a numerical error + // and will lead to premature descent and crashing into the ground + if (desiredVFacing == -1) + desiredVFacing = 0; + + // If the target has been passed by, limit the absolute value of + // vertical facing by the maximum vertical rate of turn + // Do this because the missile will be looping horizontally + // and thus needs smaller vertical facings so as not + // to hit the ground prematurely + if (targetPassedBy) + desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn, info.VerticalRateOfTurn); + else if (lastHt == 0) + { // Before the target is passed by, missile speed should be changed + // Target's height above loop's center + var tarHgt = (loopRadius * WAngle.FromFacing(vFacing).Cos() / 1024 - System.Math.Abs(relTarHgt)).Clamp(0, loopRadius); + + // Target's horizontal distance from loop's center + var tarDist = Exts.ISqrt(loopRadius * loopRadius - tarHgt * tarHgt); + + // Missile's horizontal distance from loop's center + var missDist = loopRadius * WAngle.FromFacing(vFacing).Sin() / 1024; + + // If the current height does not permit the missile + // to hit the target before passing it by, lower speed + // Otherwise, increase speed + if (relTarHorDist <= tarDist - System.Math.Sign(relTarHgt) * missDist) + ChangeSpeed(-1); + else + ChangeSpeed(); + } + } + else if (allowPassBy || (lastHt != 0 && relTarHorDist - lastHtChg < loopRadius)) + { + // Only activate this part if target too close to cliff + allowPassBy = true; + + // Vector from missile's current position pointing to the loop's center + var radius = new WVec(loopRadius, 0, 0) + .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(64 - vFacing))); + + // Vector from loop's center to incline top hardcoded in height buffer zone + var edgeVector = new WVec(lastHtChg, lastHt - pos.Z, 0) - radius; + + if (!targetPassedBy) + { + // Climb to critical height + if (relTarHorDist > 2 * loopRadius) + { + // Target's distance from cliff + var d1 = relTarHorDist - lastHtChg; + if (d1 < 0) + d1 = 0; + if (d1 > 2 * loopRadius) + return 0; + + // Find critical height at which the missile must be once it is at one loopRadius + // away from the target + var h1 = loopRadius - Exts.ISqrt(d1 * (2 * loopRadius - d1)) - (pos.Z - lastHt); + + if (h1 > loopRadius * (1024 - WAngle.FromFacing(vFacing).Cos()) / 1024) + desiredVFacing = WAngle.ArcTan(Exts.ISqrt(h1 * (2 * loopRadius - h1)), loopRadius - h1).Angle >> 2; + else + desiredVFacing = 0; + + // TODO: deceleration checks!!! + } + else + { + // Avoid the cliff edge + if (edgeVector.Length > loopRadius && lastHt > targetPosition.Z) + { + int vFac; + for (vFac = vFacing + 1; vFac <= vFacing + info.VerticalRateOfTurn - 1; vFac++) + { + // Vector from missile's current position pointing to the loop's center + radius = new WVec(loopRadius, 0, 0) + .Rotate(new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(64 - vFac))); + + // Vector from loop's center to incline top + 64 hardcoded in height buffer zone + edgeVector = new WVec(lastHtChg, lastHt - pos.Z, 0) - radius; + if (edgeVector.Length <= loopRadius) + break; + } + + desiredVFacing = vFac; + } + else + { + // Aim for the target + var vDist = new WVec(-relTarHgt, -relTarHorDist * (targetPassedBy ? -1 : 1), 0); + desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing); + if (desiredVFacing < 0 && info.VerticalRateOfTurn < (sbyte)vFacing) + desiredVFacing = 0; + } + } + } + else + { + // Aim for the target + var vDist = new WVec(-relTarHgt, -relTarHorDist * (targetPassedBy ? -1 : 1), 0); + desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing); + if (desiredVFacing < 0 && info.VerticalRateOfTurn < (sbyte)vFacing) + desiredVFacing = 0; + } + } + else + { + // Aim to attain cruise altitude as soon as possible while having the absolute value + // of vertical facing bound by the maximum vertical rate of turn + var vDist = new WVec(-diffClfMslHgt - info.CruiseAltitude.Length, -speed, 0); + desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing); + desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn, info.VerticalRateOfTurn); + + ChangeSpeed(); + } + } + else + { + // Aim to attain cruise altitude as soon as possible while having the absolute value + // of vertical facing bound by the maximum vertical rate of turn + var vDist = new WVec(-diffClfMslHgt - info.CruiseAltitude.Length, -speed, 0); + desiredVFacing = (sbyte)OpenRA.Traits.Util.GetFacing(vDist, vFacing); + desiredVFacing = desiredVFacing.Clamp(-info.VerticalRateOfTurn, info.VerticalRateOfTurn); + + ChangeSpeed(); + } + + return desiredVFacing; + } + + WVec HomingTick(World world, WVec tarDistVec, int relTarHorDist) + { + int predClfHgt, predClfDist, lastHtChg, lastHt; + InclineLookahead(world, relTarHorDist, out predClfHgt, out predClfDist, out lastHtChg, out lastHt); + + // Height difference between the incline height and missile height + var diffClfMslHgt = predClfHgt - pos.Z; + + // Get underestimate of distance from target in next tick + var nxtRelTarHorDist = (relTarHorDist - speed - info.Acceleration.Length).Clamp(0, relTarHorDist); + + // Target height relative to the missile + var relTarHgt = tarDistVec.Z; + + // Compute which direction the projectile should be facing + var desiredHFacing = OpenRA.Traits.Util.GetFacing(tarDistVec + predVel, hFacing); + + if (allowPassBy && System.Math.Abs(desiredHFacing - hFacing) >= System.Math.Abs(desiredHFacing + 128 - hFacing)) + { + desiredHFacing += 128; + targetPassedBy = true; + } + else + targetPassedBy = false; + + var desiredVFacing = HomingInnerTick(predClfDist, diffClfMslHgt, relTarHorDist, lastHtChg, lastHt, + nxtRelTarHorDist, relTarHgt, vFacing, targetPassedBy); + + // The target has been passed by + if (tarDistVec.HorizontalLength < speed * WAngle.FromFacing(vFacing).Cos() / 1024) + targetPassedBy = true; + + // Check whether the homing mechanism is jammed + var jammed = info.Jammable && world.ActorsWithTrait().Any(JammedBy); + if (jammed) + { + desiredHFacing = hFacing + world.SharedRandom.Next(-info.JammedDiversionRange, info.JammedDiversionRange + 1); + desiredVFacing = vFacing + world.SharedRandom.Next(-info.JammedDiversionRange, info.JammedDiversionRange + 1); + } + else if (!args.GuidedTarget.IsValidFor(args.SourceActor)) + desiredHFacing = hFacing; + + // Compute new direction the projectile will be facing + hFacing = OpenRA.Traits.Util.TickFacing(hFacing, desiredHFacing, info.HorizontalRateOfTurn); + vFacing = OpenRA.Traits.Util.TickFacing(vFacing, desiredVFacing, info.VerticalRateOfTurn); + + // Compute the projectile's guided displacement + return 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; + } + public void Tick(World world) { ticks++; if (anim != null) anim.Tick(); - // Missile tracks target + // Switch from freefall mode to homing mode + if (ticks == info.HomingActivationDelay + 1) + { + state = States.Homing; + speed = velocity.Length; + + // Compute the vertical loop radius + loopRadius = LoopRadius(speed, info.VerticalRateOfTurn); + } + + // Switch from homing mode to freefall mode + if (info.RangeLimit != 0 && ticks == info.RangeLimit + 1) + { + state = States.Freefall; + 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) + var newTarPos = targetPosition; if (args.GuidedTarget.IsValidFor(args.SourceActor) && lockOn) - targetPosition = args.GuidedTarget.CenterPosition; + newTarPos = args.GuidedTarget.CenterPosition + + new WVec(WDist.Zero, WDist.Zero, info.AirburstAltitude); - var dist = targetPosition + offset - pos; - var desiredFacing = OpenRA.Traits.Util.GetFacing(dist, facing); - var desiredAltitude = targetPosition.Z; - var jammed = info.Jammable && world.ActorsWithTrait().Any(JammedBy); + // Compute target's predicted velocity vector (assuming uniform circular motion) + var fac1 = OpenRA.Traits.Util.GetFacing(tarVel, hFacing); + tarVel = newTarPos - targetPosition; + var fac2 = OpenRA.Traits.Util.GetFacing(tarVel, hFacing); + predVel = tarVel.Rotate(WRot.FromFacing(fac2 - fac1)); + targetPosition = newTarPos; - if (jammed) - { - desiredFacing = facing + world.SharedRandom.Next(-20, 21); - desiredAltitude = world.SharedRandom.Next(-43, 86); - } - else if (!args.GuidedTarget.IsValidFor(args.SourceActor)) - desiredFacing = facing; + // Compute current distance from target position + var tarDistVec = targetPosition + offset - pos; + var relTarDist = tarDistVec.Length; + var relTarHorDist = tarDistVec.HorizontalLength; - facing = OpenRA.Traits.Util.TickFacing(facing, desiredFacing, info.RateOfTurn); - var move = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)) * info.Speed.Length / 1024; + WVec move; + if (state == States.Freefall) + move = FreefallTick(); + else + move = HomingTick(world, tarDistVec, relTarHorDist); - if (pos.Z != desiredAltitude) - { - var delta = move.HorizontalLength * info.MaximumPitch.Tan() / 1024; - var dz = (targetPosition.Z - pos.Z).Clamp(-delta, delta); - move += new WVec(0, 0, dz); - } + renderFacing = WAngle.ArcTan(move.Z - move.Y, move.X).Angle / 4 - 64; + // Move the missile pos += move; - if (!string.IsNullOrEmpty(info.Trail) && --ticksToNextSmoke < 0) + // Create the smoke trail effect + if (!string.IsNullOrEmpty(info.TrailImage) && --ticksToNextSmoke < 0 && (state != States.Freefall || 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; } @@ -198,12 +785,16 @@ namespace OpenRA.Mods.Common.Effects var cell = world.Map.CellContaining(pos); - var shouldExplode = (pos.Z < 0) // Hit the ground - || (dist.LengthSquared < info.CloseEnough.LengthSquared) // Within range - || (info.RangeLimit != 0 && ticks > info.RangeLimit) // Ran out of fuel + // NOTE: High speeds might cause the missile to miss the target or fly through obstacles + // In that case, big moves should probably be decomposed into multiple smaller ones with hit checks + var height = world.Map.DistanceAboveTerrain(pos); + var shouldExplode = (height.Length <= 0) // Hit the ground + || (relTarDist < info.CloseEnough.Length) // Within range + || (info.ExplodeWhenEmpty && info.RangeLimit != 0 && ticks > info.RangeLimit) // Ran out of fuel || (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. - || (!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 && relTarHorDist < info.CloseEnough.Length); // Airburst if (shouldExplode) Explode(world); diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index b022e125c8..5e6e53879d 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -2718,6 +2718,18 @@ namespace OpenRA.Mods.Common.UtilityCommands } } + if (engineVersion < 20151009) + { + if (depth == 2 && parentKey == "Projectile" && parent.Value.Value == "Missile" && node.Key == "Speed") + node.Key = "MaximumLaunchSpeed"; + + 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); } } diff --git a/mods/cnc/weapons/missiles.yaml b/mods/cnc/weapons/missiles.yaml index f6d97efbd4..eccf061eea 100644 --- a/mods/cnc/weapons/missiles.yaml +++ b/mods/cnc/weapons/missiles.yaml @@ -9,10 +9,10 @@ Rockets: Blockable: false Inaccuracy: 128 Image: DRAGON - RateOfTurn: 15 - Trail: smokey + HorizontalRateOfTurn: 15 + TrailImage: smokey ContrailLength: 8 - Speed: 298 + MaximumLaunchSpeed: 298 RangeLimit: 20 Warhead@1Dam: SpreadDamage Spread: 128 @@ -43,10 +43,10 @@ BikeRockets: Blockable: false Inaccuracy: 128 Image: DRAGON - RateOfTurn: 10 - Trail: smokey + HorizontalRateOfTurn: 10 + TrailImage: smokey ContrailLength: 8 - Speed: 213 + MaximumLaunchSpeed: 213 RangeLimit: 30 Warhead@1Dam: SpreadDamage Spread: 128 @@ -78,10 +78,10 @@ OrcaAGMissiles: Blockable: false Inaccuracy: 128 Image: DRAGON - RateOfTurn: 20 - Trail: smokey + HorizontalRateOfTurn: 20 + TrailImage: smokey ContrailLength: 8 - Speed: 256 + MaximumLaunchSpeed: 256 RangeLimit: 30 Warhead@1Dam: SpreadDamage Spread: 128 @@ -112,10 +112,10 @@ OrcaAAMissiles: Blockable: false Inaccuracy: 128 Image: DRAGON - RateOfTurn: 20 - Trail: smokey + HorizontalRateOfTurn: 20 + TrailImage: smokey ContrailLength: 8 - Speed: 298 + MaximumLaunchSpeed: 298 RangeLimit: 30 Warhead@1Dam: SpreadDamage Spread: 128 @@ -143,10 +143,10 @@ MammothMissiles: Blockable: false Inaccuracy: 128 Image: DRAGON - RateOfTurn: 20 - Trail: smokey + HorizontalRateOfTurn: 20 + TrailImage: smokey ContrailLength: 8 - Speed: 341 + MaximumLaunchSpeed: 341 RangeLimit: 35 Warhead@1Dam: SpreadDamage Spread: 298 @@ -216,10 +216,10 @@ MammothMissiles: Blockable: false Inaccuracy: 213 Image: DRAGON - RateOfTurn: 10 - Trail: smokey + HorizontalRateOfTurn: 10 + TrailImage: smokey ContrailLength: 8 - Speed: 213 + MaximumLaunchSpeed: 213 RangeLimit: 40 Warhead@1Dam: SpreadDamage Spread: 128 @@ -248,10 +248,10 @@ BoatMissile: Blockable: false Inaccuracy: 213 Image: DRAGON - RateOfTurn: 10 - Trail: smokey + HorizontalRateOfTurn: 10 + TrailImage: smokey ContrailLength: 8 - Speed: 170 + MaximumLaunchSpeed: 170 RangeLimit: 60 Warhead@1Dam: SpreadDamage Spread: 256 @@ -283,10 +283,10 @@ TowerMissle: Blockable: false Inaccuracy: 128 Image: DRAGON - RateOfTurn: 20 - Trail: smokey + HorizontalRateOfTurn: 20 + TrailImage: smokey ContrailLength: 8 - Speed: 298 + MaximumLaunchSpeed: 298 RangeLimit: 40 Warhead@1Dam: SpreadDamage Spread: 683 @@ -313,10 +313,10 @@ SAMMissile: Arm: 0 Blockable: false Image: MISSILE - RateOfTurn: 20 - Speed: 426 + HorizontalRateOfTurn: 20 + MaximumLaunchSpeed: 426 RangeLimit: 35 - Trail: smokey + TrailImage: smokey ContrailLength: 8 Warhead@1Dam: SpreadDamage Spread: 682 @@ -372,10 +372,10 @@ Patriot: Projectile: Missile Blockable: false Image: patriot - Trail: smokey + TrailImage: smokey ContrailLength: 8 - RateOfTurn: 20 - Speed: 341 + HorizontalRateOfTurn: 20 + MaximumLaunchSpeed: 341 RangeLimit: 30 Warhead@1Dam: SpreadDamage Spread: 682 diff --git a/mods/d2k/weapons.yaml b/mods/d2k/weapons.yaml index 85e50dd447..d68c5ad5f8 100644 --- a/mods/d2k/weapons.yaml +++ b/mods/d2k/weapons.yaml @@ -29,11 +29,11 @@ Bazooka: Report: ROCKET1.WAV ValidTargets: Ground Projectile: Missile - Speed: 281 + MaximumLaunchSpeed: 281 Inaccuracy: 64 Image: RPG - RateOfTurn: 1 - Trail: bazooka_trail2 + HorizontalRateOfTurn: 1 + TrailImage: bazooka_trail2 TrailPalette: effect75alpha TrailInterval: 1 RangeLimit: 35 @@ -236,11 +236,11 @@ Rocket: Projectile: Missile Inaccuracy: 64 Image: RPG - RateOfTurn: 0 - Trail: bazooka_trail2 + HorizontalRateOfTurn: 0 + TrailImage: bazooka_trail2 TrailPalette: effect75alpha TrailInterval: 1 - Speed: 343 + MaximumLaunchSpeed: 343 RangeLimit: 40 Warhead@1Dam: SpreadDamage Spread: 250 @@ -309,12 +309,12 @@ TowerMissile: Projectile: Missile Blockable: false Shadow: true - RateOfTurn: 1 + HorizontalRateOfTurn: 1 Inaccuracy: 384 Image: MISSILE2 - Trail: large_trail + TrailImage: large_trail TrailInterval: 1 - Speed: 320 + MaximumLaunchSpeed: 320 Warhead@1Dam: SpreadDamage Spread: 280 Falloff: 100, 100, 100, 95, 60, 25, 0 @@ -463,13 +463,13 @@ mtank_pri: Report: ROCKET1.WAV ValidTargets: Ground, Air Projectile: Missile - Speed: 281 - RateOfTurn: 3 + MaximumLaunchSpeed: 281 + HorizontalRateOfTurn: 3 Blockable: false Shadow: yes Inaccuracy: 96 Image: MISSILE2 - Trail: large_trail + TrailImage: large_trail TrailInterval: 1 Warhead@1Dam: SpreadDamage Spread: 280 @@ -500,13 +500,13 @@ DeviatorMissile: Report: MISSLE1.WAV InvalidTargets: Infantry, Structure Projectile: Missile - Speed: 281 + MaximumLaunchSpeed: 281 Blockable: false Shadow: yes RateofTurn: 1 Inaccuracy: 256 Image: MISSILE - Trail: deviator_trail + TrailImage: deviator_trail TrailPalette: deviatorgas TrailUsePlayerPalette: true TrailInterval: 1 diff --git a/mods/ra/maps/fort-lonestar/map.yaml b/mods/ra/maps/fort-lonestar/map.yaml index c1966d92ac..5c5244cc8c 100644 --- a/mods/ra/maps/fort-lonestar/map.yaml +++ b/mods/ra/maps/fort-lonestar/map.yaml @@ -817,12 +817,12 @@ Weapons: Burst: 2 ValidTargets: Ground, Air Projectile: Missile - Speed: 128 + MaximumLaunchSpeed: 128 Arm: 2 High: true Shadow: false Proximity: true - Trail: smokey + TrailImage: smokey ContrailLength: 150 Inaccuracy: 853 Image: DRAGON diff --git a/mods/ra/weapons/missiles.yaml b/mods/ra/weapons/missiles.yaml index 3194d04db4..a23cbe777e 100644 --- a/mods/ra/weapons/missiles.yaml +++ b/mods/ra/weapons/missiles.yaml @@ -7,13 +7,13 @@ Maverick: BurstDelay: 7 ValidTargets: Ground, Water Projectile: Missile - Speed: 256 + MaximumLaunchSpeed: 256 Arm: 2 Blockable: false ContrailLength: 10 Inaccuracy: 512 Image: DRAGON - RateOfTurn: 5 + HorizontalRateOfTurn: 5 RangeLimit: 60 Warhead@1Dam: SpreadDamage Spread: 128 @@ -43,14 +43,14 @@ Dragon: Report: MISSILE6.AUD ValidTargets: Ground, Water Projectile: Missile - Speed: 213 + MaximumLaunchSpeed: 213 Arm: 2 Blockable: false - Trail: smokey + TrailImage: smokey ContrailLength: 10 Inaccuracy: 128 Image: DRAGON - RateOfTurn: 5 + HorizontalRateOfTurn: 5 RangeLimit: 35 Warhead@1Dam: SpreadDamage Spread: 128 @@ -82,13 +82,13 @@ HellfireAG: BurstDelay: 10 ValidTargets: Ground, Water Projectile: Missile - Speed: 256 + MaximumLaunchSpeed: 256 Arm: 2 Blockable: false ContrailLength: 10 Inaccuracy: 128 Image: DRAGON - RateOfTurn: 10 + HorizontalRateOfTurn: 10 RangeLimit: 20 Warhead@1Dam: SpreadDamage Spread: 128 @@ -120,13 +120,13 @@ HellfireAA: BurstDelay: 10 ValidTargets: Air Projectile: Missile - Speed: 384 + MaximumLaunchSpeed: 384 Arm: 2 Blockable: false ContrailLength: 10 Inaccuracy: 128 Image: DRAGON - RateOfTurn: 10 + HorizontalRateOfTurn: 10 RangeLimit: 20 Warhead@1Dam: SpreadDamage Spread: 128 @@ -156,13 +156,13 @@ MammothTusk: Burst: 2 ValidTargets: Air, Infantry Projectile: Missile - Speed: 341 + MaximumLaunchSpeed: 341 Arm: 2 Blockable: false ContrailLength: 10 Inaccuracy: 128 Image: DRAGON - RateOfTurn: 15 + HorizontalRateOfTurn: 15 RangeLimit: 40 Warhead@1Dam: SpreadDamage Spread: 256 @@ -199,9 +199,9 @@ Nike: Blockable: false ContrailLength: 10 Image: MISSILE - RateOfTurn: 25 + HorizontalRateOfTurn: 25 RangeLimit: 50 - Speed: 341 + MaximumLaunchSpeed: 341 Warhead@1Dam: SpreadDamage Spread: 128 Damage: 50 @@ -227,9 +227,9 @@ RedEye: Blockable: false ContrailLength: 10 Image: MISSILE - RateOfTurn: 20 + HorizontalRateOfTurn: 20 RangeLimit: 30 - Speed: 298 + MaximumLaunchSpeed: 298 Warhead@1Dam: SpreadDamage Spread: 128 Damage: 40 @@ -291,9 +291,9 @@ Stinger: Blockable: false ContrailLength: 10 Image: DRAGON - RateOfTurn: 20 + HorizontalRateOfTurn: 20 RangeLimit: 50 - Speed: 170 + MaximumLaunchSpeed: 170 Warhead@1Dam: SpreadDamage Spread: 128 Damage: 30 @@ -331,9 +331,9 @@ StingerAA: Blockable: false ContrailLength: 10 Image: DRAGON - RateOfTurn: 20 + HorizontalRateOfTurn: 20 RangeLimit: 50 - Speed: 255 + MaximumLaunchSpeed: 255 Warhead@1Dam: SpreadDamage Spread: 128 Damage: 30 @@ -369,9 +369,9 @@ TorpTube: Projectile: Missile Image: MISSILE Arm: 3 - Speed: 85 - Trail: bubbles - RateOfTurn: 1 + MaximumLaunchSpeed: 85 + TrailImage: bubbles + HorizontalRateOfTurn: 1 RangeLimit: 160 BoundToTerrainType: Water Palette: shadow @@ -441,14 +441,14 @@ APTusk: Report: MISSILE6.AUD ValidTargets: Ground, Water Projectile: Missile - Speed: 298 + MaximumLaunchSpeed: 298 Arm: 2 Blockable: false - Trail: smokey + TrailImage: smokey ContrailLength: 10 Inaccuracy: 128 Image: DRAGON - RateOfTurn: 10 + HorizontalRateOfTurn: 10 RangeLimit: 22 Warhead@1Dam: SpreadDamage Spread: 128 diff --git a/mods/ts/weapons/energyweapons.yaml b/mods/ts/weapons/energyweapons.yaml index 489a883005..5d64bc5966 100644 --- a/mods/ts/weapons/energyweapons.yaml +++ b/mods/ts/weapons/energyweapons.yaml @@ -61,9 +61,9 @@ CyCannon: Report: SCRIN5B.AUD ValidTargets: Ground Projectile: Missile - Speed: 192 + MaximumLaunchSpeed: 192 Blockable: false - RateOfTurn: 2 + HorizontalRateOfTurn: 2 Shadow: true Image: TORPEDO Warhead@1Dam: SpreadDamage diff --git a/mods/ts/weapons/missiles.yaml b/mods/ts/weapons/missiles.yaml index 93c05509fa..9853813d56 100644 --- a/mods/ts/weapons/missiles.yaml +++ b/mods/ts/weapons/missiles.yaml @@ -5,13 +5,13 @@ Bazooka: Report: RKETINF1.AUD ValidTargets: Ground, Air Projectile: Missile - Speed: 213 + MaximumLaunchSpeed: 213 Blockable: false Shadow: true Inaccuracy: 128 Image: DRAGON - Trail: small_smoke_trail - RateOfTurn: 8 + TrailImage: small_smoke_trail + HorizontalRateOfTurn: 8 RangeLimit: 50 Palette: ra Warhead@1Dam: SpreadDamage @@ -48,14 +48,14 @@ HoverMissile: Report: HOVRMIS1.AUD ValidTargets: Ground, Air Projectile: Missile - Speed: 213 + MaximumLaunchSpeed: 213 Arm: 2 Blockable: false Shadow: true Inaccuracy: 128 Image: DRAGON - Trail: small_smoke_trail - RateOfTurn: 8 + TrailImage: small_smoke_trail + HorizontalRateOfTurn: 8 RangeLimit: 35 Palette: ra Warhead@1Dam: SpreadDamage @@ -96,9 +96,9 @@ MammothTusk: Shadow: true Inaccuracy: 128 Image: DRAGON - Trail: small_smoke_trail - RateOfTurn: 10 - Speed: 213 + TrailImage: small_smoke_trail + HorizontalRateOfTurn: 10 + MaximumLaunchSpeed: 213 RangeLimit: 50 Palette: ra Warhead@1Dam: SpreadDamage @@ -135,9 +135,9 @@ BikeMissile: Shadow: true Inaccuracy: 128 Image: DRAGON - Trail: small_smoke_trail - RateOfTurn: 8 - Speed: 213 + TrailImage: small_smoke_trail + HorizontalRateOfTurn: 8 + MaximumLaunchSpeed: 213 RangeLimit: 50 Palette: ra Warhead@1Dam: SpreadDamage @@ -170,14 +170,14 @@ Dragon: Report: MISL1.AUD ValidTargets: Ground, Air Projectile: Missile - Speed: 171 + MaximumLaunchSpeed: 171 Arm: 2 Blockable: false Shadow: true Inaccuracy: 128 Image: DRAGON - Trail: small_smoke_trail - RateOfTurn: 8 + TrailImage: small_smoke_trail + HorizontalRateOfTurn: 8 RangeLimit: 50 Palette: ra Warhead@1Dam: SpreadDamage @@ -214,14 +214,14 @@ Hellfire: Burst: 2 ValidTargets: Ground Projectile: Missile - Speed: 256 + MaximumLaunchSpeed: 256 Arm: 2 Blockable: false Shadow: true Inaccuracy: 128 Image: DRAGON - Trail: small_smoke_trail - RateOfTurn: 8 + TrailImage: small_smoke_trail + HorizontalRateOfTurn: 8 RangeLimit: 35 Palette: ra Warhead@1Dam: SpreadDamage @@ -257,14 +257,14 @@ RedEye2: Report: SAMSHOT1.AUD ValidTargets: Air Projectile: Missile - Speed: 384 + MaximumLaunchSpeed: 384 Arm: 2 Blockable: false Shadow: true Inaccuracy: 128 Image: DRAGON - Trail: small_smoke_trail - RateOfTurn: 5 + TrailImage: small_smoke_trail + HorizontalRateOfTurn: 5 RangeLimit: 100 Palette: ra Warhead@1Dam: SpreadDamage diff --git a/mods/ts/weapons/superweapons.yaml b/mods/ts/weapons/superweapons.yaml index 24e75d7bd6..2d3143a87f 100644 --- a/mods/ts/weapons/superweapons.yaml +++ b/mods/ts/weapons/superweapons.yaml @@ -4,13 +4,13 @@ MultiCluster: Report: MISL1.AUD ValidTargets: Ground Projectile: Missile - Speed: 170 + MaximumLaunchSpeed: 170 Arm: 2 Blockable: false Shadow: true Inaccuracy: 128 Image: DRAGON - RateOfTurn: 8 + HorizontalRateOfTurn: 8 RangeLimit: 35 Palette: ra Warhead@1Dam: SpreadDamage