public void Tick(World world) { if (--remaining <= 0) world.AddFrameEndTask(w => w.Remove(this)); pos += velocity; }
public NukeLaunch(Player firedBy, string weapon, WPos launchPos, WPos targetPos, WDist velocity, int delay, bool skipAscent, string flashType) { this.firedBy = firedBy; this.weapon = weapon; this.delay = delay; this.turn = delay / 2; this.flashType = flashType; var offset = new WVec(WDist.Zero, WDist.Zero, velocity * turn); ascendSource = launchPos; ascendTarget = launchPos + offset; descendSource = targetPos + offset; descendTarget = targetPos; anim = new Animation(firedBy.World, weapon); anim.PlayRepeating("up"); pos = launchPos; var weaponRules = firedBy.World.Map.Rules.Weapons[weapon.ToLowerInvariant()]; if (weaponRules.Report != null && weaponRules.Report.Any()) Sound.Play(weaponRules.Report.Random(firedBy.World.SharedRandom), pos); if (skipAscent) ticks = turn; }
public IEnumerable<Actor> ActorsInBox(WPos a, WPos b) { var left = Math.Min(a.X, b.X); var top = Math.Min(a.Y, b.Y); var right = Math.Max(a.X, b.X); var bottom = Math.Max(a.Y, b.Y); var region = BinRectangleCoveringWorldArea(left, top, right, bottom); var minCol = region.Left; var minRow = region.Top; var maxCol = region.Right; var maxRow = region.Bottom; for (var row = minRow; row <= maxRow; row++) { for (var col = minCol; col <= maxCol; col++) { foreach (var actor in BinAt(row, col).Actors) { if (actor.IsInWorld) { var c = actor.CenterPosition; if (left <= c.X && c.X <= right && top <= c.Y && c.Y <= bottom) yield return actor; } } } } }
public SatelliteLaunch(Actor a) { doors.PlayThen("active", () => a.World.AddFrameEndTask(w => w.Remove(this))); pos = a.CenterPosition; }
public SpriteEffect(WPos pos, World world, string sprite, string palette) { this.pos = pos; this.palette = palette; anim = new Animation(world, sprite); anim.PlayThen("idle", () => world.AddFrameEndTask(w => w.Remove(this))); }
public IEnumerable<Actor> ActorsInBox(WPos a, WPos b) { var left = Math.Min(a.X, b.X); var top = Math.Min(a.Y, b.Y); var right = Math.Max(a.X, b.X); var bottom = Math.Max(a.Y, b.Y); var i1 = (left / info.BinSize).Clamp(0, cols - 1); var i2 = (right / info.BinSize).Clamp(0, cols - 1); var j1 = (top / info.BinSize).Clamp(0, rows - 1); var j2 = (bottom / info.BinSize).Clamp(0, rows - 1); for (var j = j1; j <= j2; j++) { for (var i = i1; i <= i2; i++) { foreach (var actor in bins[j * cols + i].Actors) { if (actor.IsInWorld) { var c = actor.CenterPosition; if (left <= c.X && c.X <= right && top <= c.Y && c.Y <= bottom) yield return actor; } } } } }
public GpsSatellite(World world, WPos pos) { this.pos = pos; anim = new Animation(world, "sputnik"); anim.PlayRepeating("idle"); }
public Missile(MissileInfo info, ProjectileArgs args) { this.info = info; this.args = args; pos = args.Source; facing = args.Facing; targetPosition = args.PassiveTarget; var world = args.SourceActor.World; if (world.SharedRandom.Next(100) <= info.LockOnProbability) lockOn = true; if (info.Inaccuracy.Range > 0) { var inaccuracy = OpenRA.Traits.Util.ApplyPercentageModifiers(info.Inaccuracy.Range, args.InaccuracyModifiers); offset = WVec.FromPDF(world.SharedRandom, 2) * inaccuracy / 1024; } if (info.Image != null) { anim = new Animation(world, info.Image, () => facing); anim.PlayRepeating("idle"); } if (info.ContrailLength > 0) { var color = info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.SourceActor) : info.ContrailColor; trail = new ContrailRenderable(world, color, info.ContrailLength, info.ContrailDelay, 0); } }
public List<CPos> FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self) { var mi = self.Info.TraitInfo<MobileInfo>(); var targetCell = world.Map.CellContaining(target); // Correct for SubCell offset target -= world.Map.OffsetOfSubCell(srcSub); // Select only the tiles that are within range from the requested SubCell // This assumes that the SubCell does not change during the path traversal var tilesInRange = world.Map.FindTilesInCircle(targetCell, range.Length / 1024 + 1) .Where(t => (world.Map.CenterOfCell(t) - target).LengthSquared <= range.LengthSquared && mi.CanEnterCell(self.World, self, t)); // See if there is any cell within range that does not involve a cross-domain request // Really, we only need to check the circle perimeter, but it's not clear that would be a performance win var domainIndex = world.WorldActor.TraitOrDefault<DomainIndex>(); if (domainIndex != null) { var passable = mi.GetMovementClass(world.TileSet); tilesInRange = new List<CPos>(tilesInRange.Where(t => domainIndex.IsPassable(source, t, (uint)passable))); if (!tilesInRange.Any()) return EmptyPath; } using (var fromSrc = PathSearch.FromPoints(world, mi, self, tilesInRange, source, true)) using (var fromDest = PathSearch.FromPoint(world, mi, self, source, targetCell, true).Reverse()) return FindBidiPath(fromSrc, fromDest); }
public NukeLaunch(Player firedBy, string name, WeaponInfo weapon, string weaponPalette, string upSequence, string downSequence, WPos launchPos, WPos targetPos, WDist velocity, int delay, bool skipAscent, string flashType) { this.firedBy = firedBy; this.weapon = weapon; this.weaponPalette = weaponPalette; this.downSequence = downSequence; this.delay = delay; turn = delay / 2; this.flashType = flashType; var offset = new WVec(WDist.Zero, WDist.Zero, velocity * turn); ascendSource = launchPos; ascendTarget = launchPos + offset; descendSource = targetPos + offset; descendTarget = targetPos; anim = new Animation(firedBy.World, name); anim.PlayRepeating(upSequence); pos = launchPos; if (weapon.Report != null && weapon.Report.Any()) Game.Sound.Play(weapon.Report.Random(firedBy.World.SharedRandom), pos); if (skipAscent) ticks = turn; }
public bool IsValidImpact(WPos pos, Actor firedBy) { var world = firedBy.World; var targetTile = world.Map.CellContaining(pos); if (!world.Map.Contains(targetTile)) return false; var impactType = GetImpactType(world, targetTile, pos, firedBy); var validImpact = false; switch (impactType) { case ImpactType.TargetHit: validImpact = true; break; case ImpactType.Air: validImpact = IsValidTarget(new string[] { "Air" }); break; case ImpactType.Ground: var tileInfo = world.Map.GetTerrainInfo(targetTile); validImpact = IsValidTarget(tileInfo.TargetTypes); break; } return validImpact; }
public static ImpactType GetImpactType(World world, CPos cell, WPos pos) { // Missiles need a margin because they sometimes explode a little above ground // due to their explosion check triggering slightly too early (because of CloseEnough). // TODO: Base ImpactType on target altitude instead of explosion altitude. var airMargin = new WDist(128); var dat = world.Map.DistanceAboveTerrain(pos); var isAir = dat.Length > airMargin.Length; var isWater = dat.Length <= 0 && world.Map.GetTerrainInfo(cell).IsWater; var isDirectHit = GetDirectHit(world, cell, pos); if (isAir && !isDirectHit) return ImpactType.Air; else if (isWater && !isDirectHit) return ImpactType.Water; else if (isAir && isDirectHit) return ImpactType.AirHit; else if (isWater && isDirectHit) return ImpactType.WaterHit; else if (isDirectHit) return ImpactType.GroundHit; return ImpactType.Ground; }
public FrozenActor(Actor self, IEnumerable<CPos> footprint) { actor = self; Footprint = footprint; CenterPosition = self.CenterPosition; Bounds = self.Bounds.Value; }
public SelectionBoxRenderable(WPos pos, Rectangle bounds, float scale, Color color) { this.pos = pos; this.bounds = bounds; this.scale = scale; this.color = color; }
/// <summary> /// Finds all the actors of which their health radius is intersected by a line (with a definable width) between two points. /// </summary> /// <param name="world">The engine world the line intersection is to be done in.</param> /// <param name="lineStart">The position the line should start at</param> /// <param name="lineEnd">The position the line should end at</param> /// <param name="lineWidth">How close an actor's health radius needs to be to the line to be considered 'intersected' by the line</param> /// <returns>A list of all the actors intersected by the line</returns> public static IEnumerable<Actor> FindActorsOnLine(this World world, WPos lineStart, WPos lineEnd, WDist lineWidth, WDist targetExtraSearchRadius) { // This line intersection check is done by first just finding all actors within a square that starts at the source, and ends at the target. // Then we iterate over this list, and find all actors for which their health radius is at least within lineWidth of the line. // For actors without a health radius, we simply check their center point. // The square in which we select all actors must be large enough to encompass the entire line's width. var xDir = Math.Sign(lineEnd.X - lineStart.X); var yDir = Math.Sign(lineEnd.Y - lineStart.Y); var dir = new WVec(xDir, yDir, 0); var overselect = dir * (1024 + lineWidth.Length + targetExtraSearchRadius.Length); var finalTarget = lineEnd + overselect; var finalSource = lineStart - overselect; var actorsInSquare = world.ActorMap.ActorsInBox(finalTarget, finalSource); var intersectedActors = new List<Actor>(); foreach (var currActor in actorsInSquare) { var actorWidth = 0; var healthInfo = currActor.Info.TraitInfoOrDefault<HealthInfo>(); if (healthInfo != null) actorWidth = healthInfo.Shape.OuterRadius.Length; var projection = MinimumPointLineProjection(lineStart, lineEnd, currActor.CenterPosition); var distance = (currActor.CenterPosition - projection).HorizontalLength; var maxReach = actorWidth + lineWidth.Length; if (distance <= maxReach) intersectedActors.Add(currActor); } return intersectedActors; }
/// <summary> /// Find the point (D) on a line (A-B) that is closest to the target point (C). /// </summary> /// <param name="lineStart">The source point (tail) of the line</param> /// <param name="lineEnd">The target point (head) of the line</param> /// <param name="point">The target point that the minimum distance should be found to</param> /// <returns>The WPos that is the point on the line that is closest to the target point</returns> public static WPos MinimumPointLineProjection(WPos lineStart, WPos lineEnd, WPos point) { var squaredLength = (lineEnd - lineStart).HorizontalLengthSquared; // Line has zero length, so just use the lineEnd position as the closest position. if (squaredLength == 0) return lineEnd; // Consider the line extending the segment, parameterized as target + t (source - target). // We find projection of point onto the line. // It falls where t = [(point - target) . (source - target)] / |source - target|^2 // The normal DotProduct math would be (xDiff + yDiff) / dist, where dist = (target - source).LengthSquared; // But in order to avoid floating points, we do not divide here, but rather work with the large numbers as far as possible. // We then later divide by dist, only AFTER we have multiplied by the dotproduct. var xDiff = ((long)point.X - lineEnd.X) * (lineStart.X - lineEnd.X); var yDiff = ((long)point.Y - lineEnd.Y) * (lineStart.Y - lineEnd.Y); var t = xDiff + yDiff; // Beyond the 'target' end of the segment if (t < 0) return lineEnd; // Beyond the 'source' end of the segment if (t > squaredLength) return lineStart; // Projection falls on the segment return WPos.Lerp(lineEnd, lineStart, t, squaredLength); }
public TextRenderable(SpriteFont font, WPos pos, int zOffset, Color color, string text) : this(font, pos, zOffset, color, ChromeMetrics.Get<Color>("TextContrastColorDark"), ChromeMetrics.Get<Color>("TextContrastColorLight"), text) { }
public AreaBeam(AreaBeamInfo info, ProjectileArgs args, Color color) { this.info = info; this.args = args; this.color = color; actorAttackBase = args.SourceActor.Trait<AttackBase>(); var world = args.SourceActor.World; if (info.Speed.Length > 1) speed = new WDist(world.SharedRandom.Next(info.Speed[0].Length, info.Speed[1].Length)); else speed = info.Speed[0]; // Both the head and tail start at the source actor, but initially only the head is travelling. headPos = args.Source; tailPos = headPos; target = args.PassiveTarget; if (info.Inaccuracy.Length > 0) { var inaccuracy = Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers); var maxOffset = inaccuracy * (target - headPos).Length / args.Weapon.Range.Length; target += WVec.FromPDF(world.SharedRandom, 2) * maxOffset / 1024; } towardsTargetFacing = (target - headPos).Yaw.Facing; // Update the target position with the range we shoot beyond the target by // I.e. we can deliberately overshoot, so aim for that position var dir = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(towardsTargetFacing)); target += dir * info.BeyondTargetRange.Length / 1024; length = Math.Max((target - headPos).Length / speed.Length, 1); }
public FallDown(Actor self, WPos dropPosition, int fallRate, Actor ignoreActor = null) { pos = self.TraitOrDefault<IPositionable>(); IsInterruptible = false; fallVector = new WVec(0, 0, fallRate); this.dropPosition = dropPosition; }
public IEnumerable<IRenderable> Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition) { var jamsMissiles = ai.TraitInfoOrDefault<JamsMissilesInfo>(); if (jamsMissiles != null) { yield return new RangeCircleRenderable( centerPosition, jamsMissiles.Range, 0, Color.FromArgb(128, Color.Red), Color.FromArgb(96, Color.Black)); } var jamsRadar = ai.TraitInfoOrDefault<JamsRadarInfo>(); if (jamsRadar != null) { yield return new RangeCircleRenderable( centerPosition, jamsRadar.Range, 0, Color.FromArgb(128, Color.Blue), Color.FromArgb(96, Color.Black)); } foreach (var a in w.ActorsWithTrait<RenderJammerCircle>()) if (a.Actor.Owner.IsAlliedWith(w.RenderPlayer)) foreach (var r in a.Trait.RenderAfterWorld(wr)) yield return r; }
public Missile(MissileInfo info, ProjectileArgs args) { this.info = info; this.args = args; pos = args.Source; facing = args.Facing; targetPosition = args.PassiveTarget; // Convert ProjectileArg definitions to world coordinates // TODO: Change the yaml definitions so we don't need this var inaccuracy = (int)(info.Inaccuracy * 1024 / Game.CellSize); speed = info.Speed * 1024 / (5 * Game.CellSize); if (info.Inaccuracy > 0) offset = WVec.FromPDF(args.SourceActor.World.SharedRandom, 2) * inaccuracy / 1024; if (info.Image != null) { anim = new Animation(info.Image, () => facing); anim.PlayRepeating("idle"); } if (info.ContrailLength > 0) { var color = info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.SourceActor) : info.ContrailColor; trail = new ContrailRenderable(args.SourceActor.World, color, info.ContrailLength, info.ContrailDelay, 0); } }
public AirstrikePowerASEffect(World world, Player p, WPos pos, IEnumerable<Actor> planes, AirstrikePowerASInfo info) { this.info = info; this.world = world; this.Owner = p; this.pos = pos; this.planes = planes; if (info.DisplayBeacon) { var distance = (planes.First().OccupiesSpace.CenterPosition - pos).HorizontalLength; beacon = new Beacon( Owner, pos - new WVec(WDist.Zero, WDist.Zero, world.Map.DistanceAboveTerrain(pos)), info.BeaconPaletteIsPlayerPalette, info.BeaconPalette, info.BeaconImage, info.BeaconPoster, info.BeaconPosterPalette, info.ArrowSequence, info.CircleSequence, info.ClockSequence, () => 1 - ((planes.First().OccupiesSpace.CenterPosition - pos).HorizontalLength - info.BeaconDistanceOffset.Length) * 1f / distance); world.AddFrameEndTask(w => w.Add(beacon)); } }
public Parachute(Actor cargo, WPos dropPosition) { this.cargo = cargo; parachutableInfo = cargo.Info.Traits.GetOrDefault<ParachutableInfo>(); if (parachutableInfo != null) fallVector = new WVec(0, 0, parachutableInfo.FallRate); var parachuteSprite = parachutableInfo != null ? parachutableInfo.ParachuteSequence : null; if (parachuteSprite != null) { parachute = new Animation(cargo.World, parachuteSprite); parachute.PlayThen("open", () => parachute.PlayRepeating("idle")); } var shadowSprite = parachutableInfo != null ? parachutableInfo.ShadowSequence : null; if (shadowSprite != null) { shadow = new Animation(cargo.World, shadowSprite); shadow.PlayRepeating("idle"); } if (parachutableInfo != null) parachuteOffset = parachutableInfo.ParachuteOffset; // Adjust x,y to match the target subcell cargo.Trait<IPositionable>().SetPosition(cargo, cargo.World.Map.CellContaining(dropPosition)); var cp = cargo.CenterPosition; pos = new WPos(cp.X, cp.Y, dropPosition.Z); }
public Missile(MissileInfo info, ProjectileArgs args) { this.info = info; this.args = args; pos = args.Source; facing = args.Facing; targetPosition = args.PassiveTarget; if (info.Inaccuracy.Range > 0) offset = WVec.FromPDF(args.SourceActor.World.SharedRandom, 2) * info.Inaccuracy.Range / 1024; if (info.Image != null) { anim = new Animation(args.SourceActor.World, info.Image, () => facing); anim.PlayRepeating("idle"); } if (info.ContrailLength > 0) { var color = info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.SourceActor) : info.ContrailColor; trail = new ContrailRenderable(args.SourceActor.World, color, info.ContrailLength, info.ContrailDelay, 0); } }
public RangeCircleRenderable(WPos centerPosition, WDist radius, int zOffset, Color color, Color contrastColor) { this.centerPosition = centerPosition; this.radius = radius; this.zOffset = zOffset; this.color = color; this.contrastColor = contrastColor; }
public Explosion(World world, WPos pos, string style) { this.world = world; this.pos = pos; this.cell = pos.ToCPos(); anim = new Animation("explosion"); anim.PlayThen(style, () => world.AddFrameEndTask(w => w.Remove(this))); }
public Explosion(World world, WPos pos, string image, string sequence, string palette) { this.world = world; this.pos = pos; this.palette = palette; anim = new Animation(world, image); anim.PlayThen(sequence, () => world.AddFrameEndTask(w => w.Remove(this))); }
public TextRenderable(SpriteFont font, WPos pos, int zOffset, Color color, string text) { this.font = font; this.pos = pos; this.zOffset = zOffset; this.color = color; this.text = text; }
public FloatingText(WPos pos, Color color, string text, int duration) { this.font = Game.Renderer.Fonts["TinyBold"]; this.pos = pos; this.color = color; this.text = text; this.remaining = duration; }
public bool IsVisible(WPos pos) { return(IsVisible(map.ProjectedCellCovering(pos))); }
public TintedCell(TintedCellsLayer layer, CPos location, WPos centeredLocation) { this.layer = layer; this.location = location; this.centeredLocation = centeredLocation; }
void SaveBookmark(int index, WPos position) { viewPortBookmarkSlots[index] = position; }
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)); } }
public Bullet(BulletInfo info, ProjectileArgs args) { this.info = info; this.args = args; pos = args.Source; source = args.Source; var world = args.SourceActor.World; if (info.LaunchAngle.Length > 1) { angle = new WAngle(world.SharedRandom.Next(info.LaunchAngle[0].Angle, info.LaunchAngle[1].Angle)); } else { angle = info.LaunchAngle[0]; } if (info.Speed.Length > 1) { speed = new WDist(world.SharedRandom.Next(info.Speed[0].Length, info.Speed[1].Length)); } else { speed = info.Speed[0]; } target = args.PassiveTarget; if (info.Inaccuracy.Length > 0) { var inaccuracy = Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers); var range = Util.ApplyPercentageModifiers(args.Weapon.Range.Length, args.RangeModifiers); var maxOffset = inaccuracy * (target - pos).Length / range; target += WVec.FromPDF(world.SharedRandom, 2) * maxOffset / 1024; } if (info.AirburstAltitude > WDist.Zero) { target += new WVec(WDist.Zero, WDist.Zero, info.AirburstAltitude); } facing = (target - pos).Yaw.Facing; length = Math.Max((target - pos).Length / speed.Length, 1); if (!string.IsNullOrEmpty(info.Image)) { anim = new Animation(world, info.Image, new Func <int>(GetEffectiveFacing)); anim.PlayRepeating(info.Sequences.Random(world.SharedRandom)); } if (info.ContrailLength > 0) { var color = info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.SourceActor) : info.ContrailColor; contrail = new ContrailRenderable(world, color, info.ContrailWidth, info.ContrailLength, info.ContrailDelay, info.ContrailZOffset); } trailPalette = info.TrailPalette; if (info.TrailUsePlayerPalette) { trailPalette += args.SourceActor.Owner.InternalName; } smokeTicks = info.TrailDelay; remainingBounces = info.BounceCount; }
public Actor[] TargetParatroopers(WPos target, WAngle?facing = null) { var actors = pp.SendParatroopers(Self, target, facing); return(actors.Aircraft); }
public void Center(WPos pos) { CenterLocation = worldRenderer.ScreenPxPosition(pos).Clamp(mapBounds); cellsDirty = true; }
public void SendAirstrike(Actor self, WPos target, bool randomize = true, int attackFacing = 0) { var info = Info as AirstrikePowerInfo; if (randomize) { attackFacing = Util.QuantizeFacing(self.World.SharedRandom.Next(256), info.QuantizedFacings) * (256 / info.QuantizedFacings); } var altitude = self.World.Map.Rules.Actors[info.UnitType].TraitInfo <AircraftInfo>().CruiseAltitude.Length; var attackRotation = WRot.FromFacing(attackFacing); var delta = new WVec(0, -1024, 0).Rotate(attackRotation); target = target + new WVec(0, 0, altitude); var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; Actor camera = null; Beacon beacon = null; var aircraftInRange = new Dictionary <Actor, bool>(); Action <Actor> onEnterRange = a => { // Spawn a camera and remove the beacon when the first plane enters the target area if (info.CameraActor != null && !aircraftInRange.Any(kv => kv.Value)) { self.World.AddFrameEndTask(w => { camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(self.World.Map.CellContaining(target)), new OwnerInit(self.Owner), }); }); } if (beacon != null) { self.World.AddFrameEndTask(w => { w.Remove(beacon); beacon = null; }); } aircraftInRange[a] = true; }; Action <Actor> onExitRange = a => { aircraftInRange[a] = false; // Remove the camera when the final plane leaves the target area if (!aircraftInRange.Any(kv => kv.Value)) { if (camera != null) { camera.QueueActivity(new Wait(info.CameraRemoveDelay)); camera.QueueActivity(new RemoveSelf()); } camera = null; if (beacon != null) { self.World.AddFrameEndTask(w => { w.Remove(beacon); beacon = null; }); } } }; self.World.AddFrameEndTask(w => { var notification = self.Owner.IsAlliedWith(self.World.RenderPlayer) ? Info.LaunchSound : Info.IncomingSound; Game.Sound.Play(notification); Actor distanceTestActor = null; for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane if (i == 0 && (info.SquadSize & 1) == 0) { continue; } // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(attackRotation); var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(attackRotation); var a = w.CreateActor(info.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(attackFacing), }); var attack = a.Trait <AttackBomber>(); attack.SetTarget(w, target + targetOffset); attack.OnEnteredAttackRange += onEnterRange; attack.OnExitedAttackRange += onExitRange; attack.OnRemovedFromWorld += onExitRange; a.QueueActivity(new Fly(a, Target.FromPos(target + spawnOffset))); a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); aircraftInRange.Add(a, false); distanceTestActor = a; } if (Info.DisplayBeacon) { var distance = (target - startEdge).HorizontalLength; beacon = new Beacon( self.Owner, target - new WVec(0, 0, altitude), Info.BeaconPalettePrefix, Info.BeaconPoster, Info.BeaconPosterPalette, () => 1 - ((distanceTestActor.CenterPosition - target).HorizontalLength - info.BeaconDistanceOffset.Length) * 1f / distance); w.Add(beacon); } }); }
public MoveFlash(WPos pos, World world) { this.pos = pos; anim = new Animation(world, "moveflsh"); anim.PlayThen("idle", () => world.AddFrameEndTask(w => w.Remove(this))); }
public override void DoImpact(Target target, Actor firedBy, IEnumerable <int> damageModifiers) { if (!target.IsValidFor(firedBy)) { return; } var random = firedBy.World.SharedRandom; var pos = target.CenterPosition + new WVec(Radius.X == 0 ? 0 : random.Next(-Radius.X, Radius.X), Radius.Y == 0 ? 0 : random.Next(-Radius.Y, Radius.Y), 0); var world = firedBy.World; var targetTile = world.Map.CellContaining(pos); var isValid = IsValidImpact(pos, firedBy); if ((!world.Map.Contains(targetTile)) || (!isValid)) { return; } var palette = ExplosionPalette; if (UsePlayerPalette) { palette += firedBy.Owner.InternalName; } if (ForceDisplayAtGroundLevel) { var dat = world.Map.DistanceAboveTerrain(pos); pos = new WPos(pos.X, pos.Y, pos.Z - dat.Length); } var explosion = Explosions.RandomOrDefault(Game.CosmeticRandom); if (Image != null && explosion != null) { world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos, w, Image, explosion, palette))); } if (ShrapnelWeapon != null) { WeaponInfo weaponInfo; var weaponToLower = ShrapnelWeapon.ToLowerInvariant(); if (!Game.ModData.DefaultRules.Weapons.TryGetValue(weaponToLower, out weaponInfo)) { throw new YamlException("Weapons Ruleset does not contain an entry '{0}'".F(weaponToLower)); } var rotation = WRot.FromFacing(world.SharedRandom.Next(1024)); var range = world.SharedRandom.Next(ShrapnelRange[0].Length, ShrapnelRange[1].Length); var passiveTarget = pos + new WVec(range, 0, 0).Rotate(rotation); var args = new ProjectileArgs { Weapon = weaponInfo, DamageModifiers = new int[0], InaccuracyModifiers = new int[0], RangeModifiers = new int[0], Source = pos, CurrentSource = () => pos, SourceActor = firedBy, PassiveTarget = passiveTarget, GuidedTarget = target }; world.AddFrameEndTask(x => { if (args.Weapon.Projectile != null) { var projectile = args.Weapon.Projectile.Create(args); if (projectile != null) { world.Add(projectile); } } else { foreach (var warhead in args.Weapon.Warheads.Keys) { var wh = warhead; // force the closure to bind to the current warhead if (wh.Delay > 0) { firedBy.World.AddFrameEndTask(w => w.Add(new DelayedImpact(wh.Delay, wh, Target.FromPos(args.PassiveTarget), args.SourceActor, new int[0]))); } else { wh.DoImpact(Target.FromPos(args.PassiveTarget), args.SourceActor, new int[0]); } } } }); } var impactSound = ImpactSounds.RandomOrDefault(Game.CosmeticRandom); if (impactSound != null && Game.CosmeticRandom.Next(0, 100) < ImpactSoundChance) { Game.Sound.Play(SoundType.World, impactSound, pos); } }
public Drag(WPos start, WPos end, int length) { this.start = start; this.end = end; this.length = length; }
public ISound Play2D(ISoundSource sound, bool loop, bool relative, WPos pos, float volume, bool attenuateVolume) { if (sound == null) { Log.Write("sound", "Attempt to Play2D a null `ISoundSource`"); return(null); } var currFrame = Game.LocalTick; var atten = 1f; // Check if max # of instances-per-location reached: if (attenuateVolume) { int instances = 0, activeCount = 0; foreach (var s in sourcePool.Values) { if (!s.IsActive) { continue; } if (s.IsRelative != relative) { continue; } ++activeCount; if (s.Sound != sound) { continue; } if (currFrame - s.FrameStarted >= 5) { continue; } // Too far away to count? var lensqr = (s.Pos - pos).LengthSquared; if (lensqr >= GroupDistanceSqr) { continue; } // If we are starting too many instances of the same sound within a short time then stop this one: if (++instances == MaxInstancesPerFrame) { return(null); } } // Attenuate a little bit based on number of active sounds: atten = 0.66f * ((PoolSize - activeCount * 0.5f) / PoolSize); } uint source; if (!TryGetSourceFromPool(out source)) { return(null); } var slot = sourcePool[source]; slot.Pos = pos; slot.FrameStarted = currFrame; slot.Sound = sound; slot.IsRelative = relative; return(new OpenAlSound(source, ((OpenAlSoundSource)sound).Buffer, loop, relative, pos, volume * atten)); }
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, 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, 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 Actor[] ActorsInBox(WPos topLeft, WPos bottomRight, LuaFunction filter = null) { var actors = Context.World.ActorMap.ActorsInBox(topLeft, bottomRight); return(FilteredObjects(actors, filter).ToArray()); }
public void SendAirstrike(Actor self, WPos target, bool randomize = true, int attackFacing = 0) { if (randomize) { attackFacing = 256 * self.World.SharedRandom.Next(info.QuantizedFacings) / info.QuantizedFacings; } Actor camera = null; Beacon beacon = null; var aircraftInRange = new Dictionary <Actor, bool>(); Action <Actor> onEnterRange = a => { // Spawn a camera and remove the beacon when the first plane enters the target area if (info.CameraActor != null && camera == null && !aircraftInRange.Any(kv => kv.Value)) { self.World.AddFrameEndTask(w => { camera = w.CreateActor(info.CameraActor, new TypeDictionary { new LocationInit(self.World.Map.CellContaining(target)), new OwnerInit(self.Owner), }); }); } RemoveBeacon(beacon); aircraftInRange[a] = true; }; Action <Actor> onExitRange = a => { aircraftInRange[a] = false; // Remove the camera when the final plane leaves the target area if (!aircraftInRange.Any(kv => kv.Value)) { RemoveCamera(camera); } }; Action <Actor> onRemovedFromWorld = a => { aircraftInRange[a] = false; // Checking for attack range is not relevant here because // aircraft may be shot down before entering. Thus we remove // the camera and beacon only if the whole squad is dead. if (aircraftInRange.All(kv => kv.Key.IsDead)) { RemoveCamera(camera); RemoveBeacon(beacon); } }; self.World.AddFrameEndTask(w => { WPos?startPos = null; foreach (var squadMember in info.Squad) { var altitude = self.World.Map.Rules.Actors[squadMember.UnitType].TraitInfo <AircraftInfo>().CruiseAltitude.Length; var attackRotation = WRot.FromFacing(attackFacing); var delta = new WVec(0, -1024, 0).Rotate(attackRotation); var targetPos = target + new WVec(0, 0, altitude); var startEdge = targetPos - (self.World.Map.DistanceToEdge(targetPos, -delta) + info.Cordon).Length * delta / 1024; var finishEdge = targetPos + (self.World.Map.DistanceToEdge(targetPos, delta) + info.Cordon).Length * delta / 1024; startPos = startEdge; PlayLaunchSounds(); var spawnOffset = squadMember.SpawnOffset.Rotate(attackRotation); var targetOffset = squadMember.TargetOffset.Rotate(attackRotation); var a = w.CreateActor(squadMember.UnitType, new TypeDictionary { new CenterPositionInit(startEdge + spawnOffset), new OwnerInit(self.Owner), new FacingInit(attackFacing), new CreationActivityDelayInit(squadMember.SpawnDelay) }); var attack = a.Trait <AttackBomber>(); attack.SetTarget(w, targetPos + targetOffset); attack.OnEnteredAttackRange += onEnterRange; attack.OnExitedAttackRange += onExitRange; attack.OnRemovedFromWorld += onRemovedFromWorld; for (var strikes = 0; strikes < info.Strikes; strikes++) { a.QueueActivity(new Fly(a, Target.FromPos(target + spawnOffset))); if (info.Strikes > 1) { a.QueueActivity(new FlyTimed(info.CircleDelay, a)); } } a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); aircraftInRange.Add(a, false); } if (Info.DisplayBeacon && startPos.HasValue) { var distance = (target - startPos.Value).HorizontalLength; beacon = new Beacon( self.Owner, new WPos(target.X, target.Y, 0), Info.BeaconPaletteIsPlayerPalette, Info.BeaconPalette, Info.BeaconImage, Info.BeaconPoster, Info.BeaconPosterPalette, Info.BeaconSequence, Info.ArrowSequence, Info.CircleSequence, Info.ClockSequence, () => { // To account for different spawn times and potentially different movement speeds. var closestActor = aircraftInRange.Keys.MinBy(x => (x.CenterPosition - target).HorizontalLengthSquared); return(1 - ((closestActor.CenterPosition - target).HorizontalLength - info.BeaconDistanceOffset.Length) * 1f / distance); }, Info.BeaconDelay); w.Add(beacon); } }); }
public Actor[] ActorsInCircle(WPos location, WDist radius, LuaFunction filter = null) { var actors = Context.World.FindActorsInCircle(location, radius); return(FilteredObjects(actors, filter).ToArray()); }
protected override void TraitEnabled(Actor self) { cachedPosition = self.CenterPosition; }
public WDist DistanceFromEdge(WPos pos, Actor actor) { return(DistanceFromEdge((pos - actor.CenterPosition).Rotate(-actor.Orientation))); }
public static Target FromPos(WPos p) { return(new Target { pos = p, type = TargetType.Terrain }); }
public IEnumerable <IRenderable> Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition) { var findactors = w.FindActorsInCircle(centerPosition, Distance + ExtraSearchDistance) .Where(a => { if (a.IsDead || !a.IsInWorld) { return(false); } if (!Actors.Contains(a.Info.Name)) { return(false); } if (CellDistanceBetweenCenterpositions(a.CenterPosition, centerPosition) <= CellLengthOfDistance(Distance)) { return(true); } var cells = w.Map.FindTilesInCircle(w.Map.CellContaining(centerPosition), CellLengthOfDistance(ExtraSearchDistance)) .Where(c => w.WorldActor.Trait <ResourceLayer>().GetResourceDensity(c) > 0 && w.WorldActor.Trait <ResourceLayer>().GetRenderedResource(c) != null) .Where(ce => ce.X <= Math.Max(w.Map.CellContaining(centerPosition).X, a.Location.X) && ce.X >= Math.Min(w.Map.CellContaining(centerPosition).X, a.Location.X) && ce.Y <= Math.Max(w.Map.CellContaining(centerPosition).Y, a.Location.Y) && ce.Y >= Math.Min(w.Map.CellContaining(centerPosition).Y, a.Location.Y)); if (cells.Any()) { var cell = cells.MinByOrDefault(c => (w.Map.CellContaining(centerPosition) - c).LengthSquared); var extradistance = CellDistanceBetweenCenterpositions(w.Map.CenterOfCell(cell), a.CenterPosition); if (CellDistanceBetweenCenterpositions(a.CenterPosition, centerPosition) <= CellLengthOfDistance(Distance) + extradistance) { return(true); } } return(false); }).ToList(); if (findactors.Any()) { foreach (var act in findactors) { yield return(new ArcRenderable( centerPosition + Offset, act.CenterPosition + TargetOffset, ZOffset, Angle, Color, Width, QuantizedSegments)); } } }
public int CellDistanceBetweenCenterpositions(WPos c1, WPos c2) { return((int)Math.Round((c1 - c2).Length / 1024.0)); }
internal Actor FindClosestEnemy(WPos pos, WDist radius) { return(World.FindActorsInCircle(pos, radius).Where(isEnemyUnit).ClosestTo(pos)); }
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, info.BlockerScanRadius, 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); } }
internal Actor FindClosestEnemy(WPos pos) { return(World.Actors.Where(isEnemyUnit).ClosestTo(pos)); }
public IRenderable[] Render(WPos pos, PaletteReference palette) { return(Render(pos, WVec.Zero, 0, palette, 1f)); }
public bool IsExplored(WPos pos) { return(IsExplored(map.ProjectedCellCovering(pos))); }
public void SetVisualPosition(Actor self, WPos pos) { SetPosition(self, pos); }
public MoveSecondHalf(MoveUnderground move, WPos from, WPos to, int fromFacing, int toFacing, int startingFraction) : base(move, from, to, fromFacing, toFacing, startingFraction) { }
public Activity VisualMove(Actor self, WPos fromPos, WPos toPos) { return(null); }
public IEnumerable <Rectangle> ScreenBounds(WorldRenderer wr, WPos pos) { yield return(animation.ScreenBounds(wr, pos, offset(), scale)); }
public CashTick(WPos pos, Color color, int value) { this.font = Game.Renderer.Fonts["TinyBold"]; this.pos = pos; this.color = color; this.text = "{0}${1}".F(value < 0 ? "-" : "+", Math.Abs(value)); }