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); } return(false); }
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, args.SourceActor.Owner, 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)); } }
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 WPos blockedPos; if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, tailPos, headPos, info.Width, info.BlockerScanRadius, 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.AreaVictimScanRadius); 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) { 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, w, info.TrailImage, info.TrailSequences.Random(world.SharedRandom), trailPalette, facing: 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, 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 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) { if (launchDelay-- > 0) { return; } if (!isLaunched) { if (weapon.Report != null && weapon.Report.Any()) { Game.Sound.Play(SoundType.World, weapon.Report, world, pos); } if (anim != null) { anim.PlayRepeating(upSequence); world.ScreenMap.Add(this, pos, anim.Image); } isLaunched = true; } if (anim != null) { anim.Tick(); if (ticks == turn) { anim.PlayRepeating(downSequence); } } var isDescending = ticks >= turn; if (!isDescending) { pos = WPos.LerpQuadratic(ascendSource, ascendTarget, WAngle.Zero, ticks, turn); } else { pos = WPos.LerpQuadratic(descendSource, descendTarget, WAngle.Zero, ticks - turn, impactDelay - turn); } if (!string.IsNullOrEmpty(trailImage) && --trailTicks < 0) { var trailPos = !isDescending?WPos.LerpQuadratic(ascendSource, ascendTarget, WAngle.Zero, ticks - trailDelay, turn) : WPos.LerpQuadratic(descendSource, descendTarget, WAngle.Zero, ticks - turn - trailDelay, impactDelay - turn); world.AddFrameEndTask(w => w.Add(new SpriteEffect(trailPos, w, trailImage, trailSequences.Random(world.SharedRandom), trailPalette))); trailTicks = trailInterval; } var dat = world.Map.DistanceAboveTerrain(pos); if (ticks == impactDelay || (isDescending && dat <= detonationAltitude)) { Explode(world, ticks == impactDelay || removeOnDetonation); } if (anim != null) { world.ScreenMap.Update(this, pos, anim.Image); } ticks++; }