public void Tick(World world) { if (anim != null) { anim.Tick(); } pos = WPos.LerpQuadratic(args.Source, target, angle, ticks, length); if (!string.IsNullOrEmpty(info.Trail) && --smokeTicks < 0) { var delayedPos = WPos.LerpQuadratic(args.Source, target, angle, ticks - info.TrailDelay, length); world.AddFrameEndTask(w => w.Add(new Smoke(w, delayedPos, info.Trail, trailPalette, info.Sequences.Random(world.SharedRandom)))); smokeTicks = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } var shouldExplode = ticks++ >= length || // Flight length reached/exceeded (info.Blockable && BlocksProjectiles.AnyBlockingActorAt(world, pos)); // Hit a wall or other blocking obstacle if (shouldExplode) { Explode(world); } }
public void Tick(World world) { ticks++; if (anim != null) { anim.Tick(); } // 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) { newTarPos = args.GuidedTarget.CenterPosition + new WVec(WDist.Zero, WDist.Zero, info.AirburstAltitude); } // 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; // Compute current distance from target position var tarDistVec = targetPosition + offset - pos; var relTarDist = tarDistVec.Length; var relTarHorDist = tarDistVec.HorizontalLength; WVec move; if (state == States.Freefall) { move = FreefallTick(); } else { move = HomingTick(world, tarDistVec, relTarHorDist); } renderFacing = WAngle.ArcTan(move.Z - move.Y, move.X).Angle / 4 - 64; // Move the missile pos += move; // 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.TrailImage, trailPalette, info.TrailSequence))); ticksToNextSmoke = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } var cell = world.Map.CellContaining(pos); // 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 (height.Length < info.AirburstAltitude.Length && relTarHorDist < info.CloseEnough.Length); // Airburst if (shouldExplode) { Explode(world); } }