public void Tick(World world) { // Check for blocking actors WPos blockedPos; if (info.Blockable) { // If GuidedTarget has become invalid due to getting killed the same tick, // we need to set target to args.PassiveTarget to prevent target.CenterPosition below from crashing. // The warheads have target validity checks themselves so they don't need this, but AnyBlockingActorsBetween does. if (target.Type == TargetType.Invalid) { target = Target.FromPos(args.PassiveTarget); } if (BlocksProjectiles.AnyBlockingActorsBetween(world, args.Source, target.CenterPosition, info.Width, out blockedPos)) { target = Target.FromPos(blockedPos); } } args.Weapon.Impact(target, new WarheadArgs(args)); world.AddFrameEndTask(w => w.Remove(this)); }
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) { source = args.CurrentSource(); if (hasLaunchEffect && ticks == 0) { world.AddFrameEndTask(w => w.Add(new SpriteEffect(args.CurrentSource, args.CurrentMuzzleFacing, world, info.LaunchEffectImage, info.LaunchEffectSequence, info.LaunchEffectPalette))); } // Beam tracks target if (info.TrackTarget && args.GuidedTarget.IsValidFor(args.SourceActor)) { target = args.Weapon.TargetActorCenter ? args.GuidedTarget.CenterPosition : args.GuidedTarget.Positions.PositionClosestTo(source); } // Check for blocking actors WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, source, target, info.Width, out blockedPos)) { target = blockedPos; } if (ticks < info.DamageDuration && --interval <= 0) { var warheadArgs = new WarheadArgs(args) { ImpactOrientation = new WRot(WAngle.Zero, Util.GetVerticalAngle(source, target), args.CurrentMuzzleFacing()), ImpactPosition = target, }; args.Weapon.Impact(Target.FromPos(target), warheadArgs); interval = info.DamageInterval; } if (showHitAnim) { if (ticks == 0) { hitanim.PlayThen(info.HitAnimSequence, () => showHitAnim = false); } hitanim.Tick(); } if (++ticks >= info.Duration && !showHitAnim) { world.AddFrameEndTask(w => w.Remove(this)); } }
public void Tick(World world) { //Check for blocking actors. WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, source, target.CenterPosition, info.Width, info.BlockerScanRadius, out blockedPos)) { target = Target.FromPos(blockedPos); } args.Weapon.Impact(target, args.SourceActor, args.DamagedModifiers); world.AddFrameEndTask(w => w.Remove(this)); }
void CalculateVectors() { // Check for blocking actors WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(args.SourceActor.World, target, args.Source, info.BeamWidth, out blockedPos)) { target = blockedPos; } // Note: WAngle.Sin(x) = 1024 * Math.Sin(2pi/1024 * x) AngleStep = new WAngle(1024 / info.QuantizationCount); SourceToTarget = target - args.Source; // Forward step, pointing from src to target. // QuantizationCont * forwardStep == One cycle of beam in src2target direction. ForwardStep = (info.HelixPitch.Length * SourceToTarget) / (info.QuantizationCount * SourceToTarget.Length); // An easy vector to find which is perpendicular vector to forwardStep, with 0 Z component LeftVector = new WVec(ForwardStep.Y, -ForwardStep.X, 0); if (LeftVector.LengthSquared != 0) { LeftVector = 1024 * LeftVector / LeftVector.Length; } // Vector that is pointing upwards from the ground UpVector = new WVec( -ForwardStep.X * ForwardStep.Z, -ForwardStep.Z * ForwardStep.Y, ForwardStep.X * ForwardStep.X + ForwardStep.Y * ForwardStep.Y); if (UpVector.LengthSquared != 0) { UpVector = 1024 * UpVector / UpVector.Length; } //// LeftVector and UpVector are unit vectors of size 1024. CycleCount = SourceToTarget.Length / info.HelixPitch.Length; if (SourceToTarget.Length % info.HelixPitch.Length != 0) { CycleCount += 1; // math.ceil, int version. } // Using ForwardStep * CycleCount, the helix and the main beam gets "out of sync" // if drawn from source to target. Instead, the main beam is drawn from source to end point of helix. // Trade-off between computation vs Railgun weapon range. // Modders must not have too large range for railgun weapons. SourceToTarget = info.QuantizationCount * CycleCount * ForwardStep; }
void CalculateVectors() { // Check for blocking actors if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(args.SourceActor.World, args.SourceActor.Owner, target, args.Source, info.LineWidth, out var blockedPos)) { target = blockedPos; } // Note: WAngle.Sin(x) = 1024 * Math.Sin(2pi/1024 * x) angleStep = new WAngle(1024 / info.QuantizationCount); var sourceToTarget = target - args.Source; // Forward step, pointing from src to target. // QuantizationCont * forwardStep == One cycle of beam in src2target direction. forwardStep = (info.HelixPitch.Length * sourceToTarget) / (info.QuantizationCount * sourceToTarget.Length); if (forwardStep == WVec.Zero) { return; } // An easy vector to find which is perpendicular vector to forwardStep, with 0 Z component leftVector = new WVec(forwardStep.Y, -forwardStep.X, 0); if (leftVector.Length != 0) { leftVector = 1024 * leftVector / leftVector.Length; } // Vector that is pointing upwards from the ground upVector = leftVector.Length != 0 ? new WVec( -forwardStep.X * forwardStep.Z, -forwardStep.Z * forwardStep.Y, forwardStep.X * forwardStep.X + forwardStep.Y * forwardStep.Y) : new WVec(forwardStep.Z, forwardStep.Z, 0); if (upVector.Length != 0) { upVector = 1024 * upVector / upVector.Length; } //// LeftVector and UpVector are unit vectors of size 1024. cycleCount = sourceToTarget.Length / info.HelixPitch.Length; if (sourceToTarget.Length % info.HelixPitch.Length != 0) { cycleCount += 1; // math.ceil, int version. } }
public void Tick(World world) { ticks++; if (anim != null) { anim.Tick(); } var lastPos = projectilepos; projectilepos = WPos.Lerp(source, targetpos, ticks, estimatedlifespan); // Check for walls or other blocking obstacles. WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, lastPos, projectilepos, info.Width, out blockedPos)) { projectilepos = blockedPos; DetonateSelf = true; } if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0) { var delayedPos = WPos.Lerp(source, targetpos, ticks - info.TrailDelay, estimatedlifespan); world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette, false, false, GetEffectiveFacing()))); smokeTicks = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(projectilepos); } var flightLengthReached = ticks >= lifespan; if (flightLengthReached) { DetonateSelf = true; } // Driving into cell with higher height level DetonateSelf |= world.Map.DistanceAboveTerrain(projectilepos) < info.ExplodeUnderThisAltitude; if (DetonateSelf) { Explode(world); } }
public void Tick(World world) { // Beam tracks target if (info.TrackTarget && args.GuidedTarget.IsValidFor(args.SourceActor)) { target = args.Weapon.TargetActorCenter ? args.GuidedTarget.CenterPosition : args.GuidedTarget.Positions.PositionClosestTo(source); } // Check for blocking actors WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, source, target, info.Width, out blockedPos)) { target = blockedPos; } if (!doneDamage) { if (hitanim != null) { hitanim.PlayThen(info.HitAnimSequence, () => animationComplete = true); } else { animationComplete = true; } var warheadArgs = new WarheadArgs(args) { ImpactOrientation = new WRot(WAngle.Zero, Common.Util.GetVerticalAngle(source, target), args.CurrentMuzzleFacing()), ImpactPosition = target, }; args.Weapon.Impact(Target.FromPos(target), warheadArgs); doneDamage = true; } if (hitanim != null) { hitanim.Tick(); } if (++ticks >= info.Duration && animationComplete) { world.AddFrameEndTask(w => w.Remove(this)); } }
public void Tick(World world) { source = args.CurrentSource(); if (hasLaunchEffect && ticks == 0) { world.AddFrameEndTask(w => w.Add(new LaunchEffect(world, args.CurrentSource, () => 0, info.LaunchEffectImage, info.LaunchEffectSequence, info.LaunchEffectPalette))); } // Beam tracks target if (info.TrackTarget && args.GuidedTarget.IsValidFor(args.SourceActor)) { target = args.Weapon.TargetActorCenter ? args.GuidedTarget.CenterPosition : args.GuidedTarget.Positions.PositionClosestTo(source); } // Check for blocking actors WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, source, target, info.Width, out blockedPos)) { target = blockedPos; } if (ticks < info.DamageDuration && --interval <= 0) { args.Weapon.Impact(Target.FromPos(target), args.SourceActor, args.DamageModifiers); interval = info.DamageInterval; } if (showHitAnim) { if (ticks == 0) { hitanim.PlayThen(info.HitAnimSequence, () => showHitAnim = false); } hitanim.Tick(); } if (++ticks >= info.Duration && !showHitAnim) { world.AddFrameEndTask(w => w.Remove(this)); } }
public void Tick(World world) { if (anim != null) { anim.Tick(); } var lastPos = pos; pos = WPos.LerpQuadratic(args.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, info.TargetExtraSearchRadius, out blockedPos)) { pos = blockedPos; shouldExplode = true; } if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0) { var delayedPos = WPos.LerpQuadratic(args.Source, target, angle, ticks - info.TrailDelay, length); world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette, false, false, GetEffectiveFacing()))); smokeTicks = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(pos); } // Flight length reached / exceeded shouldExplode |= ticks++ >= length; if (shouldExplode) { Explode(world); } }
public void Tick(World world) { // Beam tracks target if (info.TrackTarget && args.GuidedTarget.IsValidFor(args.SourceActor)) { target = args.GuidedTarget.CenterPosition; } // Check for blocking actors WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, source, target, info.Width, info.TargetExtraSearchRadius, out blockedPos)) { target = blockedPos; } if (!doneDamage) { if (hitanim != null) { hitanim.PlayThen(info.HitAnimSequence, () => animationComplete = true); } else { animationComplete = true; } args.Weapon.Impact(Target.FromPos(target), args.SourceActor, args.DamageModifiers); doneDamage = true; } if (hitanim != null) { hitanim.Tick(); } if (++ticks >= info.Duration && animationComplete) { world.AddFrameEndTask(w => w.Remove(this)); } }
public void Tick(World world) { // If GuidedTarget has become invalid due to getting killed the same tick, // we need to set target to args.PassiveTarget to prevent target.CenterPosition below from crashing. if (target.Type == TargetType.Invalid) { target = Target.FromPos(args.PassiveTarget); } // Check for blocking actors if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, args.SourceActor.Owner, args.Source, target.CenterPosition, info.Width, out var blockedPos)) { target = Target.FromPos(blockedPos); } var warheadArgs = new WarheadArgs(args) { ImpactOrientation = new WRot(WAngle.Zero, Util.GetVerticalAngle(args.Source, target.CenterPosition), args.Facing), ImpactPosition = target.CenterPosition, }; args.Weapon.Impact(target, warheadArgs); world.AddFrameEndTask(w => w.Remove(this)); }
public void Tick(World world) { if (info.TrackTarget) { TrackTarget(); } if (++headTicks >= length) { headPos = target; isHeadTravelling = false; } else if (isHeadTravelling) { headPos = WPos.LerpQuadratic(args.Source, target, WAngle.Zero, headTicks, length); } if (tailTicks <= 0 && args.SourceActor.IsInWorld && !args.SourceActor.IsDead) { args.Source = args.CurrentSource(); tailPos = args.Source; } // Allow for leniency to avoid edge case stuttering (start firing and immediately stop again). var outOfWeaponRange = args.Weapon.Range + info.BeyondTargetRange < new WDist((args.PassiveTarget - args.Source).Length); // While the head is travelling, the tail must start to follow Duration ticks later. // Alternatively, also stop emitting the beam if source actor dies or is ordered to stop. if ((headTicks >= info.Duration && !isTailTravelling) || args.SourceActor.IsDead || !actorAttackBase.IsAiming || outOfWeaponRange) { StopTargeting(); } if (isTailTravelling) { if (++tailTicks >= length) { tailPos = target; isTailTravelling = false; } else { tailPos = WPos.LerpQuadratic(args.Source, target, WAngle.Zero, tailTicks, length); } } // Check for blocking actors if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, tailPos, headPos, info.Width, out var blockedPos)) { headPos = blockedPos; target = headPos; length = Math.Min(headTicks, length); } // Damage is applied to intersected actors every DamageInterval ticks if (headTicks % info.DamageInterval == 0) { var actors = world.FindActorsOnLine(tailPos, headPos, info.Width); foreach (var a in actors) { var adjustedModifiers = args.DamageModifiers.Append(GetFalloff((args.Source - a.CenterPosition).Length)); var warheadArgs = new WarheadArgs(args) { ImpactOrientation = new WRot(WAngle.Zero, Util.GetVerticalAngle(args.Source, target), args.CurrentMuzzleFacing()), // Calculating an impact position is bogus for line damage. // FindActorsOnLine guarantees that the beam touches the target's HitShape, // so we just assume a center hit to avoid bogus warhead recalculations. ImpactPosition = a.CenterPosition, DamageModifiers = adjustedModifiers.ToArray(), }; args.Weapon.Impact(Target.FromActor(a), warheadArgs); } } if (IsBeamComplete) { world.AddFrameEndTask(w => w.Remove(this)); } }
bool ShouldExplode(World world) { // Check for walls or other blocking obstacles if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, 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) { 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, info.TargetExtraSearchRadius, 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, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette, false, false, GetEffectiveFacing()))); 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 + info.TargetExtraSearchRadius, 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; remainingBounces--; } // Flight length reached / exceeded shouldExplode |= flightLengthReached && !shouldBounce; // Driving into cell with different height level shouldExplode |= world.Map.DistanceAboveTerrain(pos) < info.ExplodeUnderThisAltitude; // After first bounce, check for targets each tick if (remainingBounces < info.BounceCount) { shouldExplode |= AnyValidTargetsInRadius(world, pos, info.Width + info.TargetExtraSearchRadius, args.SourceActor, true); } if (shouldExplode) { Explode(world); } }
public void Tick(World world) { ticks++; if (anim != null) { anim.Tick(); } var lastPos = projectilepos; var dx = projectilepos.X - source.X; var dy = projectilepos.Y - source.Y; var normal = new WVec(dy, -dx, 0); targetpos = projectilepos; var originalVec = projectilepos - source; if (dx != 0 || dy != 0) { int circSpeed = 1 + (ticks * 3); int maxSpeed = 200; if (circSpeed > maxSpeed) { circSpeed = maxSpeed; } normal = WVec.Lerp(WVec.Zero, normal, circSpeed, normal.Length); targetpos -= normal; targetpos += WVec.Lerp(WVec.Zero, originalVec, 30, originalVec.Length); } else { targetpos += args.VecNormalized; } projectilepos = targetpos; if (ticks > 90) { DetonateSelf = true; } // Check for walls or other blocking obstacles. WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, lastPos, projectilepos, info.Width, out blockedPos)) { projectilepos = blockedPos; DetonateSelf = true; } if (!string.IsNullOrEmpty(info.TrailImage) && --smokeTicks < 0) { var delayedPos = WPos.Lerp(lastPos, targetpos, ticks - info.TrailDelay, estimatedlifespan); world.AddFrameEndTask(w => w.Add(new SpriteEffect(delayedPos, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette, false, GetEffectiveFacing().Angle))); smokeTicks = info.TrailInterval; } if (info.ContrailLength > 0) { contrail.Update(projectilepos); } var flightLengthReached = ticks >= lifespan; if (flightLengthReached) { DetonateSelf = true; } // Driving into cell with higher height level DetonateSelf |= world.Map.DistanceAboveTerrain(projectilepos) < info.ExplodeUnderThisAltitude; if (DetonateSelf) { 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); } }
public void Tick(World world) { if (info.TrackTarget) { TrackTarget(); } if (++headTicks >= length) { headPos = target; isHeadTravelling = false; } else if (isHeadTravelling) { headPos = WPos.LerpQuadratic(args.Source, target, WAngle.Zero, headTicks, length); } if (tailTicks <= 0 && args.SourceActor.IsInWorld && !args.SourceActor.IsDead) { args.Source = args.CurrentSource(); tailPos = args.Source; } // Allow for leniency to avoid edge case stuttering (start firing and immediately stop again). var outOfWeaponRange = args.Weapon.Range + info.BeyondTargetRange < new WDist((args.PassiveTarget - args.Source).Length); // While the head is travelling, the tail must start to follow Duration ticks later. // Alternatively, also stop emitting the beam if source actor dies or is ordered to stop. if ((headTicks >= info.Duration && !isTailTravelling) || args.SourceActor.IsDead || !actorAttackBase.IsAttacking || outOfWeaponRange) { StopTargeting(); } if (isTailTravelling) { if (++tailTicks >= length) { tailPos = target; isTailTravelling = false; } else { tailPos = WPos.LerpQuadratic(args.Source, target, WAngle.Zero, tailTicks, length); } } // Check for blocking actors WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, tailPos, headPos, info.Width, info.TargetExtraSearchRadius, out blockedPos)) { headPos = blockedPos; target = headPos; length = Math.Min(headTicks, length); } // Damage is applied to intersected actors every DamageInterval ticks if (headTicks % info.DamageInterval == 0) { var actors = world.FindActorsOnLine(tailPos, headPos, info.Width, info.TargetExtraSearchRadius); foreach (var a in actors) { var adjustedModifiers = args.DamageModifiers.Append(GetFalloff((args.Source - a.CenterPosition).Length)); args.Weapon.Impact(Target.FromActor(a), args.SourceActor, adjustedModifiers); } } if (IsBeamComplete) { world.AddFrameEndTask(w => w.Remove(this)); } }
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, facing: 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); } }