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 (rangeLimit >= WDist.Zero && distanceCovered > rangeLimit) { 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.Weapon.TargetActorCenter ? args.GuidedTarget.CenterPosition : args.GuidedTarget.Positions.PositionClosestTo(args.Source)) + new WVec(WDist.Zero, WDist.Zero, info.AirburstAltitude); } // Compute target's predicted velocity vector (assuming uniform circular motion) var yaw1 = tarVel.HorizontalLengthSquared != 0 ? tarVel.Yaw : WAngle.FromFacing(hFacing); tarVel = newTarPos - targetPosition; var yaw2 = tarVel.HorizontalLengthSquared != 0 ? tarVel.Yaw : WAngle.FromFacing(hFacing); predVel = tarVel.Rotate(WRot.FromYaw(yaw2 - yaw1)); 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 = new WVec(move.X, move.Y - move.Z, 0).Yaw.Facing; // Move the missile var lastPos = pos; if (info.AllowSnapping && state != States.Freefall && relTarDist < move.Length) { pos = targetPosition + offset; } else { pos += move; } // Check for walls or other blocking obstacles var shouldExplode = false; WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, lastPos, pos, info.Width, out blockedPos)) { pos = blockedPos; shouldExplode = true; } // Create the sprite trail effect if (!string.IsNullOrEmpty(info.TrailImage) && --ticksToNextSmoke < 0 && (state != States.Freefall || info.TrailWhenDeactivated)) { world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos - 3 * move / 2, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette, false, false, renderFacing))); ticksToNextSmoke = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } distanceCovered += new WDist(speed); var cell = world.Map.CellContaining(pos); var height = world.Map.DistanceAboveTerrain(pos); shouldExplode |= height.Length < 0 || // Hit the ground relTarDist < info.CloseEnough.Length || // Within range (info.ExplodeWhenEmpty && rangeLimit >= WDist.Zero && distanceCovered > rangeLimit) || // Ran out of fuel !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); } }
public void Tick(World world) { if (anim != null) { anim.Tick(); } var lastPos = pos; pos = WPos.LerpQuadratic(source, target, angle, ticks, length); // Check for walls or other blocking obstacles var shouldExplode = false; WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, lastPos, pos, info.Width, out blockedPos)) { pos = blockedPos; shouldExplode = true; } if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0) { var delayedPos = WPos.LerpQuadratic(source, target, angle, ticks - info.TrailDelay, length); world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, GetEffectiveFacing(), w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette))); smokeTicks = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } var flightLengthReached = ticks++ >= length; var shouldBounce = remainingBounces > 0; if (flightLengthReached && shouldBounce) { shouldExplode |= AnyValidTargetsInRadius(world, pos, info.Width, args.SourceActor, true); target += (pos - source) * info.BounceRangeModifier / 100; var dat = world.Map.DistanceAboveTerrain(target); target += new WVec(0, 0, -dat.Length); length = Math.Max((target - pos).Length / speed.Length, 1); ticks = 0; source = pos; Game.Sound.Play(SoundType.World, info.BounceSound, source); remainingBounces--; } // Flight length reached / exceeded shouldExplode |= flightLengthReached && !shouldBounce; // Driving into cell with higher height level shouldExplode |= world.Map.DistanceAboveTerrain(pos).Length < 0; // After first bounce, check for targets each tick if (remainingBounces < info.BounceCount) { shouldExplode |= AnyValidTargetsInRadius(world, pos, info.Width, args.SourceActor, true); } if (shouldExplode) { Explode(world); } }
public void Tick(World world) { ticks++; anim.Tick(); // Missile tracks target if (args.GuidedTarget.IsValidFor(args.SourceActor)) { targetPosition = args.GuidedTarget.CenterPosition; } var dist = targetPosition + offset - pos; var desiredFacing = Traits.Util.GetFacing(dist, facing); var desiredAltitude = targetPosition.Z; var jammed = info.Jammable && world.ActorsWithTrait <JamsMissiles>().Any(j => JammedBy(j)); if (jammed) { desiredFacing = facing + world.SharedRandom.Next(-20, 21); desiredAltitude = world.SharedRandom.Next(-43, 86); } else if (!args.GuidedTarget.IsValidFor(args.SourceActor)) { desiredFacing = facing; } facing = Traits.Util.TickFacing(facing, desiredFacing, info.ROT); var move = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)) * speed / 1024; if (targetPosition.Z > 0 && info.TurboBoost) { move = (move * 3) / 2; } 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); } pos += move; if (info.Trail != null && --ticksToNextSmoke < 0) { world.AddFrameEndTask(w => w.Add(new Smoke(w, pos - 3 * move / 2, info.Trail))); ticksToNextSmoke = info.TrailInterval; } if (info.ContrailLength > 0) { trail.Update(pos); } var shouldExplode = (pos.Z < 0) || // Hit the ground (dist.LengthSquared < MissileCloseEnough.Range * MissileCloseEnough.Range) || // Within range (info.RangeLimit != 0 && ticks > info.RangeLimit) || // Ran out of fuel (!info.High && world.ActorMap.GetUnitsAt(pos.ToCPos()) .Any(a => a.HasTrait <IBlocksBullets>())); // Hit a wall if (shouldExplode) { Explode(world); } }
bool ShouldExplode(World world) { // Check for walls or other blocking obstacles if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, args.SourceActor.Owner, lastPos, pos, info.Width, out var blockedPos)) { pos = blockedPos; return(true); } if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0) { var delayedPos = WPos.LerpQuadratic(source, target, angle, ticks - info.TrailDelay, length); world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, GetEffectiveFacing(), w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette))); smokeTicks = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } var flightLengthReached = ticks++ >= length; var shouldBounce = remainingBounces > 0; if (flightLengthReached && shouldBounce) { var cell = world.Map.CellContaining(pos); if (!world.Map.Contains(cell)) { return(true); } if (info.InvalidBounceTerrain.Contains(world.Map.GetTerrainInfo(cell).Type)) { return(true); } if (AnyValidTargetsInRadius(world, pos, info.Width, args.SourceActor, true)) { return(true); } target += (pos - source) * info.BounceRangeModifier / 100; var dat = world.Map.DistanceAboveTerrain(target); target += new WVec(0, 0, -dat.Length); length = Math.Max((target - pos).Length / speed.Length, 1); ticks = 0; source = pos; Game.Sound.Play(SoundType.World, info.BounceSound, source); remainingBounces--; } // Flight length reached / exceeded if (flightLengthReached && !shouldBounce) { return(true); } // Driving into cell with higher height level if (world.Map.DistanceAboveTerrain(pos).Length < 0) { return(true); } // After first bounce, check for targets each tick if (remainingBounces < info.BounceCount && AnyValidTargetsInRadius(world, pos, info.Width, args.SourceActor, true)) { return(true); } if (!string.IsNullOrEmpty(info.PointDefenseType) && world.ActorsWithTrait <IPointDefense>().Any(x => x.Trait.Destroy(pos, args.SourceActor.Owner, info.PointDefenseType))) { return(true); } return(false); }
public void Tick(World world) { ticks++; if (anim != null) { anim.Tick(); } // Missile tracks target if (args.GuidedTarget.IsValidFor(args.SourceActor) && lockOn) { targetPosition = args.GuidedTarget.CenterPosition; } var dist = targetPosition + offset - pos; var desiredFacing = OpenRA.Traits.Util.GetFacing(dist, facing); var desiredAltitude = targetPosition.Z; var jammed = info.Jammable && world.ActorsWithTrait <JamsMissiles>().Any(JammedBy); if (jammed) { desiredFacing = facing + world.SharedRandom.Next(-20, 21); desiredAltitude = world.SharedRandom.Next(-43, 86); } else if (!args.GuidedTarget.IsValidFor(args.SourceActor)) { desiredFacing = facing; } facing = OpenRA.Traits.Util.TickFacing(facing, desiredFacing, info.RateOfTurn); var move = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)) * info.Speed.Length / 1024; 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); } pos += move; if (!string.IsNullOrEmpty(info.Trail) && --ticksToNextSmoke < 0) { world.AddFrameEndTask(w => w.Add(new Smoke(w, pos - 3 * move / 2, info.Trail, trailPalette, info.Sequence))); ticksToNextSmoke = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } 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 (info.Blockable && world.ActorMap.GetUnitsAt(cell).Any(a => a.HasTrait <IBlocksProjectiles>())) || // 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 if (shouldExplode) { Explode(world); } }
public void Tick(Actor self) { var local = info.Offset.Rotate(body.QuantizeOrientation(self, self.Orientation)); trail.Update(self.CenterPosition + body.LocalToWorld(local)); }