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 UpgradeActorsNear(Actor self, UpgradeActorsNearInfo info) { this.info = info; this.self = self; cachedRange = info.Range; cachedVRange = info.MaximumVerticalOffset; }
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; }
/// <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; }
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 Repair(Actor self, Actor host, WDist closeEnough) { this.host = Target.FromActor(host); this.closeEnough = closeEnough; repairsUnits = host.Info.TraitInfo<RepairsUnitsInfo>(); health = self.TraitOrDefault<Health>(); }
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 ProximityExternalCondition(Actor self, ProximityExternalConditionInfo info) { this.info = info; this.self = self; cachedRange = info.Range; cachedVRange = info.MaximumVerticalOffset; }
public FlyFollow(Actor self, Target target, WDist minRange, WDist maxRange) { this.target = target; plane = self.Trait<Aircraft>(); this.minRange = minRange; this.maxRange = maxRange; }
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; }
protected bool TryGetAlternateTargetInCircle( Actor self, WDist radius, Action<Target> update, Func<Actor, bool> primaryFilter, Func<Actor, bool>[] preferenceFilters = null) { var diff = new WVec(radius, radius, WDist.Zero); var candidates = self.World.ActorMap.ActorsInBox(self.CenterPosition - diff, self.CenterPosition + diff) .Where(primaryFilter).Select(a => new { Actor = a, Ls = (self.CenterPosition - a.CenterPosition).HorizontalLengthSquared }) .Where(p => p.Ls <= radius.LengthSquared).OrderBy(p => p.Ls).Select(p => p.Actor); if (preferenceFilters != null) foreach (var filter in preferenceFilters) { var preferredCandidate = candidates.FirstOrDefault(filter); if (preferredCandidate == null) continue; target = Target.FromActor(preferredCandidate); update(target); return true; } var candidate = candidates.FirstOrDefault(); if (candidate == null) return false; target = Target.FromActor(candidate); update(target); return true; }
public Follow(Actor self, Target target, WDist minRange, WDist maxRange) { this.target = target; this.minRange = minRange; this.maxRange = maxRange; move = self.Trait<IMove>(); }
public ProximityBounty(Actor self, ProximityBountyInfo info) { this.Info = info; this.self = self; cachedRange = info.Range; cachedVRange = info.MaximumVerticalOffset; ticks = Info.Delay; }
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 BeamRenderable(WPos pos, int zOffset, WVec length, BeamRenderableShape shape, WDist width, Color color) { this.pos = pos; this.zOffset = zOffset; this.length = length; this.shape = shape; this.width = width; this.color = color; }
ContrailRenderable(World world, WPos[] trail, WDist width, int next, int length, int skip, Color color, int zOffset) { this.world = world; this.trail = trail; this.width = width; this.next = next; this.length = length; this.skip = skip; this.color = color; this.zOffset = zOffset; }
public static bool AdjustAltitude(Actor self, Helicopter helicopter, WDist targetAltitude) { var altitude = helicopter.CenterPosition.Z; if (altitude == targetAltitude.Length) return false; var delta = helicopter.Info.AltitudeVelocity.Length; var dz = (targetAltitude.Length - altitude).Clamp(-delta, delta); helicopter.SetPosition(self, helicopter.CenterPosition + new WVec(0, 0, dz)); return true; }
public DetectionCircleRenderable(WPos centerPosition, WDist radius, int zOffset, int lineTrails, WAngle trailSeparation, WAngle trailAngle, Color color, Color contrastColor) { this.centerPosition = centerPosition; this.radius = radius; this.zOffset = zOffset; trailCount = lineTrails; this.trailSeparation = trailSeparation; this.trailAngle = trailAngle; this.color = color; this.contrastColor = contrastColor; }
public Attack(Actor self, Target target, WDist minRange, WDist maxRange, bool allowMovement) { Target = target; this.minRange = minRange; this.maxRange = maxRange; attack = self.Trait<AttackBase>(); facing = self.Trait<IFacing>(); positionable = self.Trait<IPositionable>(); move = allowMovement ? self.TraitOrDefault<IMove>() : null; }
public static bool AdjustAltitude(Actor self, Aircraft helicopter, WDist targetAltitude) { targetAltitude = new WDist(helicopter.CenterPosition.Z) + targetAltitude - self.World.Map.DistanceAboveTerrain(helicopter.CenterPosition); var altitude = helicopter.CenterPosition.Z; if (altitude == targetAltitude.Length) return false; var delta = helicopter.Info.AltitudeVelocity.Length; var dz = (targetAltitude.Length - altitude).Clamp(-delta, delta); helicopter.SetPosition(self, helicopter.CenterPosition + new WVec(0, 0, dz)); return true; }
public static IEnumerable<PPos> ProjectedCellsInRange(Map map, WPos pos, WDist range) { // Account for potential extra half-cell from odd-height terrain var r = (range.Length + 1023 + 512) / 1024; var limit = range.LengthSquared; // Project actor position into the shroud plane var projectedPos = pos - new WVec(0, pos.Z, pos.Z); var projectedCell = map.CellContaining(projectedPos); foreach (var c in map.FindTilesInCircle(projectedCell, r, true)) if ((map.CenterOfCell(c) - projectedPos).HorizontalLengthSquared <= limit) yield return (PPos)c.ToMPos(map); }
public Missile(MissileInfo info, ProjectileArgs args) { this.info = info; this.args = args; pos = args.Source; hFacing = args.Facing; gravity = new WVec(0, 0, -info.Gravity); targetPosition = args.PassiveTarget; rangeLimit = info.RangeLimit != WDist.Zero ? info.RangeLimit : args.Weapon.Range; minLaunchSpeed = info.MinimumLaunchSpeed.Length > -1 ? info.MinimumLaunchSpeed.Length : info.Speed.Length; maxLaunchSpeed = info.MaximumLaunchSpeed.Length > -1 ? info.MaximumLaunchSpeed.Length : info.Speed.Length; maxSpeed = info.Speed.Length; minLaunchAngle = info.MinimumLaunchAngle; maxLaunchAngle = info.MaximumLaunchAngle; var world = args.SourceActor.World; if (info.Inaccuracy.Length > 0) { var inaccuracy = Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers); offset = WVec.FromPDF(world.SharedRandom, 2) * inaccuracy / 1024; } DetermineLaunchSpeedAndAngle(world, out speed, out vFacing); 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))); if (world.SharedRandom.Next(100) <= info.LockOnProbability) lockOn = true; if (!string.IsNullOrEmpty(info.Image)) { anim = new Animation(world, info.Image, () => renderFacing); 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; }
public Actor[] ActorsInCircle(WPos location, WDist radius, LuaFunction filter = null) { var actors = Context.World.FindActorsInCircle(location, radius); if (filter != null) { actors = actors.Where(a => { using (var f = filter.Call(a.ToLuaValue(Context))) return f.First().ToBoolean(); }); } return actors.ToArray(); }
public Bullet(BulletInfo info, ProjectileArgs args) { this.info = info; this.args = args; pos = 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; } 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; }
public static void FlyToward(Actor self, Plane plane, int desiredFacing, WDist desiredAltitude) { var move = plane.FlyStep(plane.Facing); var altitude = plane.CenterPosition.Z; plane.Facing = Util.TickFacing(plane.Facing, desiredFacing, plane.ROT); if (altitude != desiredAltitude.Length) { var delta = move.HorizontalLength * plane.Info.MaximumPitch.Tan() / 1024; var dz = (desiredAltitude.Length - altitude).Clamp(-delta, delta); move += new WVec(0, 0, dz); } plane.SetPosition(self, plane.CenterPosition + move); }
public List<CPos> FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self) { using (new PerfSample("Pathfinder")) { var key = "FindUnitPathToRange" + self.ActorID + source.X + source.Y + target.X + target.Y; var cachedPath = cacheStorage.Retrieve(key); if (cachedPath != null) return cachedPath; var pb = pathFinder.FindUnitPathToRange(source, srcSub, target, range, self); cacheStorage.Store(key, pb); return pb; } }
public static void DrawRangeCircle(WorldRenderer wr, WPos centerPosition, WDist radius, float width, Color color, float contrastWidth, Color contrastColor) { var wcr = Game.Renderer.WorldRgbaColorRenderer; var offset = new WVec(radius.Length, 0, 0); for (var i = 0; i < RangeCircleSegments; i++) { var a = wr.ScreenPosition(centerPosition + offset.Rotate(RangeCircleStartRotations[i])); var b = wr.ScreenPosition(centerPosition + offset.Rotate(RangeCircleEndRotations[i])); if (contrastWidth > 0) wcr.DrawLine(a, b, contrastWidth / wr.Viewport.Zoom, contrastColor); if (width > 0) wcr.DrawLine(a, b, width / wr.Viewport.Zoom, color); } }
public static void FlyToward(Actor self, Aircraft plane, int desiredFacing, WDist desiredAltitude) { desiredAltitude = new WDist(plane.CenterPosition.Z) + desiredAltitude - self.World.Map.DistanceAboveTerrain(plane.CenterPosition); var move = plane.FlyStep(plane.Facing); var altitude = plane.CenterPosition.Z; plane.Facing = Util.TickFacing(plane.Facing, desiredFacing, plane.TurnSpeed); if (altitude != desiredAltitude.Length) { var delta = move.HorizontalLength * plane.Info.MaximumPitch.Tan() / 1024; var dz = (desiredAltitude.Length - altitude).Clamp(-delta, delta); move += new WVec(0, 0, dz); } plane.SetPosition(self, plane.CenterPosition + move); }
public void Tick(Actor self) { var disabled = self.IsDisabled(); if (cachedDisabled != disabled) { Sound.Play(disabled ? info.DisableSound : info.EnableSound, self.CenterPosition); desiredRange = disabled ? WDist.Zero : info.Range; cachedDisabled = disabled; } if (self.CenterPosition != cachedPosition || desiredRange != cachedRange) { cachedPosition = self.CenterPosition; cachedRange = desiredRange; self.World.ActorMap.UpdateProximityTrigger(proximityTrigger, cachedPosition, cachedRange); } }
public ImpactType GetImpactType(World world, CPos cell, WPos pos, Actor firedBy) { // 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); // Matching target actor if (GetDirectHit(world, cell, pos, firedBy, true)) return ImpactType.TargetHit; var dat = world.Map.DistanceAboveTerrain(pos); if (dat.Length > airMargin.Length) return ImpactType.Air; return ImpactType.Ground; }
public Activity MoveWithinRange(Target target, WDist minRange, WDist maxRange) { return(new MoveWithinRange(self, target, minRange, maxRange)); }
public void Move(CPos cell, int closeEnough = 0) { Self.QueueActivity(new Move(Self, cell, WDist.FromCells(closeEnough))); }
public AthenaProjectile(AthenaProjectileInfo info, ProjectileArgs args) { this.args = args; altitude = info.Altitude; delay = info.Delay; }
public Actor[] SendDropPods(Actor self, Order order, int[] podFacing, string[] spawnSounds) { var units = new List <Actor>(); var info = Info as DropPodsPowerInfo; var utLower = info.DropPodType.ToLowerInvariant(); ActorInfo unitType; if (!self.World.Map.Rules.Actors.TryGetValue(utLower, out unitType)) { throw new YamlException("Actors ruleset does not include the entry '{0}'".F(utLower)); } var altitude = unitType.TraitInfo <AircraftInfo>().CruiseAltitude.Length; var pFacing = podFacing.Random(self.World.SharedRandom); var approachRotation = WRot.FromFacing(pFacing); var delta = new WVec(0, -altitude, 0).Rotate(approachRotation); foreach (var p in info.DropItems) { var unit = self.World.CreateActor(false, p.ToLowerInvariant(), new TypeDictionary { new OwnerInit(self.Owner) }); units.Add(unit); } self.World.AddFrameEndTask(w => { PlayLaunchSounds(); Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.DropPodsAvailableNotification, self.Owner.Faction.InternalName); var target = order.Target.CenterPosition; var posOffset = new WVec(-altitude, -altitude, altitude); var targetCell = self.World.Map.CellContaining(target); var podLocations = self.World.Map.FindTilesInCircle(targetCell, info.PodScatter).Shuffle(self.World.SharedRandom); string dropType = null; if (pFacing >= 160) { posOffset = new WVec(-altitude, -altitude, altitude); dropType = info.DropPodType; } else { posOffset = new WVec(altitude, -altitude, altitude); dropType = info.DropPodType2; } using (var pe = podLocations.GetEnumerator()) foreach (var u in units) { CPos podDropCellPos = pe.Current; var a = w.CreateActor(dropType, new TypeDictionary { new CenterPositionInit(self.World.Map.CenterOfCell(podDropCellPos) - delta + posOffset), new OwnerInit(self.Owner), new FacingInit(pFacing) }); var sound = spawnSounds.RandomOrDefault(Game.CosmeticRandom); if (sound != null) { Game.Sound.Play(SoundType.World, sound, target); } var unloadDist = new WDist(10); a.QueueActivity(new Land(a, Target.FromCell(a.World, podDropCellPos))); a.QueueActivity(new CallFunc(() => a.Kill(a))); pe.MoveNext(); } }); return(units.ToArray()); }
public override Activity Tick(Actor self) { if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivityTick(self, ChildActivity); if (ChildActivity != null) { return(this); } } if (IsCanceling) { // Cancel the requested target, but keep firing on it while in range if (attack.Info.PersistentTargeting) { attack.OpportunityTarget = attack.RequestedTarget; attack.OpportunityForceAttack = attack.RequestedForceAttack; attack.OpportunityTargetIsPersistentTarget = true; } attack.RequestedTarget = Target.Invalid; return(NextActivity); } // Check that AttackFollow hasn't cancelled the target by modifying attack.Target // Having both this and AttackFollow modify that field is a horrible hack. if (hasTicked && attack.RequestedTarget.Type == TargetType.Invalid) { return(NextActivity); } if (attack.IsTraitPaused) { return(this); } bool targetIsHiddenActor; attack.RequestedForceAttack = forceAttack; attack.RequestedTarget = target = target.Recalculate(self.Owner, out targetIsHiddenActor); attack.RequestedTargetLastTick = self.World.WorldTick; hasTicked = true; if (!targetIsHiddenActor && target.Type == TargetType.Actor) { lastVisibleTarget = Target.FromTargetPositions(target); lastVisibleMaximumRange = attack.GetMaximumRangeVersusTarget(target); lastVisibleMinimumRange = attack.GetMinimumRange(); lastVisibleOwner = target.Actor.Owner; lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); // Try and sit at least one cell away from the min or max ranges to give some leeway if the target starts moving. if (move != null && target.Actor.Info.HasTraitInfo <IMoveInfo>()) { var preferMinRange = Math.Min(lastVisibleMinimumRange.Length + 1024, lastVisibleMaximumRange.Length); var preferMaxRange = Math.Max(lastVisibleMaximumRange.Length - 1024, lastVisibleMinimumRange.Length); lastVisibleMaximumRange = new WDist((lastVisibleMaximumRange.Length - 1024).Clamp(preferMinRange, preferMaxRange)); } } var oldUseLastVisibleTarget = useLastVisibleTarget; var maxRange = lastVisibleMaximumRange; var minRange = lastVisibleMinimumRange; useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self); // Most actors want to be able to see their target before shooting if (target.Type == TargetType.FrozenActor && !attack.Info.TargetFrozenActors && !forceAttack) { var rs = revealsShroud .Where(Exts.IsTraitEnabled) .MaxByOrDefault(s => s.Range); // Default to 2 cells if there are no active traits var sightRange = rs != null ? rs.Range : WDist.FromCells(2); if (sightRange < maxRange) { maxRange = sightRange; } } // If we are ticking again after previously sequencing a MoveWithRange then that move must have completed // Either we are in range and can see the target, or we've lost track of it and should give up if (wasMovingWithinRange && targetIsHiddenActor) { attack.RequestedTarget = Target.Invalid; return(NextActivity); } // Update target lines if required if (useLastVisibleTarget != oldUseLastVisibleTarget) { self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, Color.Red, false); } // Target is hidden or dead, and we don't have a fallback position to move towards if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self)) { attack.RequestedTarget = Target.Invalid; return(NextActivity); } var pos = self.CenterPosition; var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target; // We've reached the required range - if the target is visible and valid then we wait // otherwise if it is hidden or dead we give up if (checkTarget.IsInRange(pos, maxRange) && !checkTarget.IsInRange(pos, minRange)) { if (useLastVisibleTarget) { attack.RequestedTarget = Target.Invalid; return(NextActivity); } return(this); } // We can't move into range, so give up if (move == null || maxRange == WDist.Zero || maxRange < minRange) { attack.RequestedTarget = Target.Invalid; return(NextActivity); } wasMovingWithinRange = true; QueueChild(self, move.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, Color.Red), true); return(this); }
public void Tick(SquadCA owner) { // Basic check if (!owner.IsValid) { return; } if (!owner.IsTargetValid) { var randomSuitableEnemy = GetRandomValuableTarget(owner); if (randomSuitableEnemy != null) { owner.TargetActor = randomSuitableEnemy; } else { owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true); return; } } // Initialize PathGuider. Optimaze pathfinding by using PathGuider. PathGuider = owner.Units.FirstOrDefault(); if (PathGuider == null) { return; } // 1. Threat scan surroundings var attackScanRadius = WDist.FromCells(owner.SquadManager.Info.AttackScanRadius); var targetActor = ThreatScan(owner, PathGuider, attackScanRadius); if (targetActor != null) { owner.TargetActor = targetActor; owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackState(), true); return; } // 2. Force scattered for navigator if needed if (StuckInPath <= 0) { if (TryMakeWay > 0) { owner.Bot.QueueOrder(new Order("AttackMove", PathGuider, Target.FromCell(owner.World, owner.TargetActor.Location), false)); foreach (var a in owner.Units) { if (a != PathGuider) { owner.Bot.QueueOrder(new Order("Scatter", a, false)); } } TryMakeWay--; } else { // When going through is over, restore the check StuckInPath = StuckInPathCheckTimes + MakeWayTick; TryMakeWay = MakeWayTick; } return; } // 3. Check if the squad is stucked due to the map has very twisted path // or currently bridge and tunnel from TS mod if (PathGuider.CenterPosition == LastPos) { StuckInPath--; } else { StuckInPath = StuckInPathCheckTimes; } LastPos = PathGuider.CenterPosition; // 4. Since units have different movement speeds, they get separated while approaching the target. /* Let them regroup into tighter formation towards "PathGuider". * * "unitsArea" means the space the squad units will occupy (if 1 per Cell). * PathGuider only stop when scope of "unitsAround" is not covered all units; * units in "unitsHurryUp" will catch up, * which keep the team tight while not stucked. * * Imagining "unitsArea" takes up a a place shape like square, we need to draw a circle * to cover the the enitire circle. * * When look around, PathGuider find units around to decide if it need to continue. * and units that need hurry up will try catch up before guider waiting * * However in practice because of the poor PF, squad tend to PF to a eclipse. * "lookAround" now has the radius of two times of the circle mentioned before. */ var groupArea = (long)WDist.FromCells(owner.Units.Count).Length * 1024; var unitsHurryUp = owner.Units.Where(a => (a.CenterPosition - PathGuider.CenterPosition).LengthSquared >= groupArea).ToArray(); var lookAround = owner.Units.Where(a => (a.CenterPosition - PathGuider.CenterPosition).LengthSquared <= groupArea * 4).ToArray(); if (owner.Units.Count > lookAround.Length) { owner.Bot.QueueOrder(new Order("Stop", PathGuider, false)); } else { owner.Bot.QueueOrder(new Order("AttackMove", PathGuider, Target.FromCell(owner.World, owner.TargetActor.Location), false)); } foreach (var unit in unitsHurryUp) { owner.Bot.QueueOrder(new Order("AttackMove", unit, Target.FromCell(owner.World, PathGuider.Location), false)); } }
public RadBeamRenderable(WPos pos, int zOffset, WVec sourceToTarget, WDist width, Color color, WDist amplitude, WDist wavelength, int quantizationCount) { this.pos = pos; this.zOffset = zOffset; this.sourceToTarget = sourceToTarget; this.width = width; this.color = color; this.amplitude = amplitude; this.wavelength = wavelength; this.quantizationCount = quantizationCount; }
void ProtectOwn(IBot bot, Actor attacker) { var protectSq = GetSquadOfType(SquadTypeCA.Protection); if (protectSq == null) { protectSq = RegisterNewSquad(bot, SquadTypeCA.Protection, attacker); } if (!protectSq.IsTargetValid) { protectSq.TargetActor = attacker; } if (!protectSq.IsValid) { var ownUnits = World.FindActorsInCircle(World.Map.CenterOfCell(GetRandomBaseCenter()), WDist.FromCells(Info.ProtectUnitScanRadius)) .Where(unit => unit.Owner == Player && !unit.Info.HasTraitInfo <BuildingInfo>() && !unit.Info.HasTraitInfo <HarvesterInfo>() && !unit.Info.HasTraitInfo <AircraftInfo>() && !Info.NavalUnitsTypes.Contains(unit.Info.Name) && unit.Info.HasTraitInfo <AttackBaseInfo>()); foreach (var a in ownUnits) { protectSq.Units.Add(a); } } }
public HeliLand(Actor self, bool requireSpace, WDist landAltitude) { this.requireSpace = requireSpace; this.landAltitude = landAltitude; aircraft = self.Trait <Aircraft>(); }
internal Actor FindClosestEnemy(WPos pos, WDist radius) { return(World.FindActorsInCircle(pos, radius).Where(a => IsEnemyUnit(a) && IsNotHiddenUnit(a)).ClosestTo(pos)); }
public UnloadCargo(Actor self, WDist unloadRange, bool unloadAll = true) : this(self, Target.Invalid, unloadRange, unloadAll) { assignTargetOnFirstRun = true; }
public UpgradeActorsNear(Actor self, UpgradeActorsNearInfo info) { this.info = info; this.self = self; cachedRange = info.Range; }
public Activity MoveFollow(Actor self, Target target, WDist minRange, WDist maxRange) { return(new Follow(self, target, minRange, maxRange)); }
public void Tick(SquadCA owner) { if (!owner.IsValid) { return; } if (!owner.IsTargetValid) { var closestEnemy = FindClosestEnemy(owner); if (closestEnemy == null) { return; } owner.TargetActor = closestEnemy; } var enemyUnits = owner.World.FindActorsInCircle(owner.TargetActor.CenterPosition, WDist.FromCells(owner.SquadManager.Info.IdleScanRadius)) .Where(owner.SquadManager.IsEnemyUnit).ToList(); if (enemyUnits.Count == 0) { Retreat(owner, false, true, true); return; } if (AttackOrFleeFuzzyCA.Default.CanAttack(owner.Units, enemyUnits)) { // We have gathered sufficient units. Attack the nearest enemy unit. // Inform human allies about AI's rush attack. owner.Bot.QueueOrder(new Order("PlaceBeacon", owner.SquadManager.Player.PlayerActor, Target.FromCell(owner.World, owner.TargetActor.Location), false) { SuppressVisualFeedback = true }); owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackMoveState(), true); } else { owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true); } }
public override bool Tick(Actor self) { if ((this.DockActor.IsDead || !this.DockActor.IsInWorld || this.Dock.IsTraitDisabled) && !this.IsCanceling) { this.Cancel(self, true); } switch (this.DockingState) { case DockingState.Approaching: if (this.State == ActivityState.Canceling) { return(true); } if (this.ChildActivity != null) { break; } var distance = WDist.FromCells(this.Dock.Info.QueueDistance); if ((this.dockableActor.CenterPosition - this.DockActor.CenterPosition).Length > distance.Length) { this.QueueChild(new Move(this.dockableActor, Target.FromActor(this.DockActor), distance)); } else { this.DockingState = DockingState.Waiting; this.Dock.Add(this.dockableActor); } break; case DockingState.Waiting: if (this.State == ActivityState.Canceling) { this.Dock.Remove(this.dockableActor); return(true); } break; case DockingState.PrepareDocking: if (this.State == ActivityState.Canceling) { this.Dock.Remove(this.dockableActor); return(true); } if (this.ChildActivity != null) { break; } var target = this.DockActor.World.Map.CellContaining(this.DockActor.CenterPosition + this.Dock.Info.Position + this.Dock.Info.DragOffset); if (this.dockableActor.Location != target) { this.QueueChild(new Move(this.dockableActor, target)); } else { this.DockingState = DockingState.Docking; this.QueueChild(new Turn(this.dockableActor, this.Dock.Info.Angle)); this.initialPosition = this.dockableActor.CenterPosition; this.QueueChild( new Drag( this.dockableActor, this.dockableActor.CenterPosition, this.DockActor.CenterPosition + this.Dock.Info.Position, this.Dock.Info.DragLength ) ); } break; case DockingState.Docking: if (this.State == ActivityState.Canceling) { this.StartUndocking(); return(false); } if (this.ChildActivity == null) { this.DockingState = DockingState.Docked; this.Dock.OnDock(this.DockActor); } break; case DockingState.Docked: if (this.State == ActivityState.Canceling) { this.StartUndocking(); return(false); } break; case DockingState.Undocking: if (this.ChildActivity == null) { this.DockingState = DockingState.None; this.Dock.OnUndock(); this.Dock.Remove(this.dockableActor); if (!this.DockActor.IsDead && this.DockActor.IsInWorld) { var rallyPoint = this.DockActor.TraitOrDefault <RallyPoint>(); if (rallyPoint != null && rallyPoint.Path.Any()) { foreach (var cell in rallyPoint.Path) { this.dockableActor.QueueActivity(new Move(this.dockableActor, cell)); } } } } break; case DockingState.None: return(true); default: throw new ArgumentOutOfRangeException(Enum.GetName(this.DockingState)); } return(false); }
public void Tick(Squad owner) { if (!owner.IsValid) { return; } if (!owner.IsTargetValid) { var closestEnemy = FindClosestEnemy(owner); if (closestEnemy != null) { owner.TargetActor = closestEnemy; } else { owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true); return; } } var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition); if (leader == null) { return; } var ownUnits = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Units.Count) / 3) .Where(a => a.Owner == owner.Units.First().Owner&& owner.Units.Contains(a)).ToHashSet(); if (ownUnits.Count < owner.Units.Count) { // Since units have different movement speeds, they get separated while approaching the target. // Let them regroup into tighter formation. owner.Bot.QueueOrder(new Order("Stop", leader, false)); foreach (var unit in owner.Units.Where(a => !ownUnits.Contains(a))) { owner.Bot.QueueOrder(new Order("AttackMove", unit, Target.FromCell(owner.World, leader.Location), false)); } } else { var enemies = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.SquadManager.Info.AttackScanRadius)) .Where(owner.SquadManager.IsPreferredEnemyUnit); var target = enemies.ClosestTo(leader.CenterPosition); if (target != null) { owner.TargetActor = target; owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsAttackState(), true); } else { foreach (var a in owner.Units) { owner.Bot.QueueOrder(new Order("AttackMove", a, Target.FromCell(owner.World, owner.TargetActor.Location), false)); } } } if (ShouldFlee(owner)) { owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true); } }
public MoveWithinRange(Actor self, Target target, WDist minRange, WDist maxRange) : base(self, target) { this.minRange = minRange; this.maxRange = maxRange; }
public void Tick(Squad owner) { if (!owner.IsValid) { return; } if (!owner.IsTargetValid) { var closestEnemy = FindClosestEnemy(owner); if (closestEnemy == null) { return; } owner.TargetActor = closestEnemy; } var enemyUnits = owner.World.FindActorsInCircle(owner.TargetActor.CenterPosition, WDist.FromCells(owner.SquadManager.Info.IdleScanRadius)) .Where(owner.SquadManager.IsPreferredEnemyUnit).ToList(); if (enemyUnits.Count == 0) { return; } if (AttackOrFleeFuzzy.Default.CanAttack(owner.Units, enemyUnits)) { foreach (var u in owner.Units) { owner.Bot.QueueOrder(new Order("AttackMove", u, Target.FromCell(owner.World, owner.TargetActor.Location), false)); } // We have gathered sufficient units. Attack the nearest enemy unit. owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsAttackMoveState(), true); } else { owner.FuzzyStateMachine.ChangeState(owner, new NavyUnitsFleeState(), true); } }
public WDist FromCells(int numCells) { return(WDist.FromCells(numCells)); }
public static bool AnyBlockingActorsBetween(World world, WPos start, WPos end, WDist width, WDist overscan, out WPos hit) { var actors = world.FindActorsOnLine(start, end, width, overscan); var length = (end - start).Length; foreach (var a in actors) { var blockers = a.TraitsImplementing <IBlocksProjectiles>() .Where(Exts.IsTraitEnabled).ToList(); if (!blockers.Any()) { continue; } var hitPos = WorldExtensions.MinimumPointLineProjection(start, end, a.CenterPosition); var dat = world.Map.DistanceAboveTerrain(hitPos); if ((hitPos - start).Length < length && blockers.Any(t => t.BlockingHeight > dat)) { hit = hitPos; return(true); } } hit = WPos.Zero; return(false); }
Actor ChooseTarget(Actor self, AttackBase ab, Stance attackStances, WDist range, bool allowMove) { var actorsByArmament = new Dictionary <Armament, List <Actor> >(); var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, range); foreach (var actor in actorsInRange) { // PERF: Most units can only attack enemy units. If this is the case but the target is not an enemy, we // can bail early and avoid the more expensive targeting checks and armament selection. For groups of // allied units, this helps significantly reduce the cost of auto target scans. This is important as // these groups will continuously rescan their allies until an enemy finally comes into range. if (attackStances == OpenRA.Traits.Stance.Enemy && !actor.AppearsHostileTo(self)) { continue; } if (PreventsAutoTarget(self, actor) || !self.Owner.CanTargetActor(actor)) { continue; } // Select only the first compatible armament for each actor: if this actor is selected // it will be thanks to the first armament anyways, since that is the first selection // criterion var target = Target.FromActor(actor); var armaments = ab.ChooseArmamentsForTarget(target, false); if (!allowMove) { armaments = armaments.Where(arm => target.IsInRange(self.CenterPosition, arm.MaxRange()) && !target.IsInRange(self.CenterPosition, arm.Weapon.MinRange)); } var armament = armaments.FirstOrDefault(); if (armament == null) { continue; } List <Actor> actors; if (actorsByArmament.TryGetValue(armament, out actors)) { actors.Add(actor); } else { actorsByArmament.Add(armament, new List <Actor> { actor }); } } // Armaments are enumerated in attack.Armaments in construct order // When autotargeting, first choose targets according to the used armament construct order // And then according to distance from actor // This enables preferential treatment of certain armaments // (e.g. tesla trooper's tesla zap should have precedence over tesla charge) foreach (var arm in ab.Armaments) { List <Actor> actors; if (actorsByArmament.TryGetValue(arm, out actors)) { return(actors.ClosestTo(self)); } } return(null); }
public Fly(Actor self, Target t, WDist minRange, WDist maxRange) : this(self, t) { this.maxRange = maxRange; this.minRange = minRange; }
public override bool Tick(Actor self) { returnToBase = false; // Refuse to take off if it would land immediately again. if (aircraft.ForceLanding) { Cancel(self); } if (IsCanceling) { return(true); } // Check that AttackFollow hasn't cancelled the target by modifying attack.Target // Having both this and AttackFollow modify that field is a horrible hack. if (hasTicked && attackAircraft.RequestedTarget.Type == TargetType.Invalid) { return(true); } if (attackAircraft.IsTraitPaused) { return(false); } bool targetIsHiddenActor; target = target.Recalculate(self.Owner, out targetIsHiddenActor); attackAircraft.SetRequestedTarget(self, target, forceAttack); hasTicked = true; if (!targetIsHiddenActor && target.Type == TargetType.Actor) { lastVisibleTarget = Target.FromTargetPositions(target); lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target); lastVisibleOwner = target.Actor.Owner; lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); } // The target may become hidden in the same tick the FlyAttack constructor is called, // causing lastVisible* to remain uninitialized. // Fix the fallback values based on the frozen actor properties else if (target.Type == TargetType.FrozenActor && !lastVisibleTarget.IsValidFor(self)) { lastVisibleTarget = Target.FromTargetPositions(target); lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target); lastVisibleOwner = target.FrozenActor.Owner; lastVisibleTargetTypes = target.FrozenActor.TargetTypes; } useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self); // Target is hidden or dead, and we don't have a fallback position to move towards if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self)) { return(true); } // If all valid weapons have depleted their ammo and Rearmable trait exists, return to RearmActor to reload // and resume the activity after reloading if AbortOnResupply is set to 'false' if (rearmable != null && !useLastVisibleTarget && attackAircraft.Armaments.All(x => x.IsTraitPaused || !x.Weapon.IsValidAgainst(target, self.World, self))) { // Attack moves never resupply if (source == AttackSource.AttackMove) { return(true); } // AbortOnResupply cancels the current activity (after resupplying) plus any queued activities if (attackAircraft.Info.AbortOnResupply && NextActivity != null) { NextActivity.Cancel(self); } QueueChild(new ReturnToBase(self)); returnToBase = true; return(attackAircraft.Info.AbortOnResupply); } var pos = self.CenterPosition; var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target; // We don't know where the target actually is, so move to where we last saw it if (useLastVisibleTarget) { // We've reached the assumed position but it is not there - give up if (checkTarget.IsInRange(pos, lastVisibleMaximumRange)) { return(true); } // Fly towards the last known position QueueChild(new Fly(self, target, WDist.Zero, lastVisibleMaximumRange, checkTarget.CenterPosition, Color.Red)); return(false); } var delta = attackAircraft.GetTargetPosition(pos, target) - pos; var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw : aircraft.Facing; QueueChild(new TakeOff(self)); var minimumRange = attackAircraft.Info.AttackType == AirAttackType.Strafe ? WDist.Zero : attackAircraft.GetMinimumRangeVersusTarget(target); // Move into range of the target. if (!target.IsInRange(pos, lastVisibleMaximumRange) || target.IsInRange(pos, minimumRange)) { QueueChild(aircraft.MoveWithinRange(target, minimumRange, lastVisibleMaximumRange, target.CenterPosition, Color.Red)); } // The aircraft must keep moving forward even if it is already in an ideal position. else if (attackAircraft.Info.AttackType == AirAttackType.Strafe) { QueueChild(new StrafeAttackRun(self, attackAircraft, target, strafeDistance != WDist.Zero ? strafeDistance : lastVisibleMaximumRange)); } else if (attackAircraft.Info.AttackType == AirAttackType.Default && !aircraft.Info.CanHover) { QueueChild(new FlyAttackRun(self, target, lastVisibleMaximumRange)); } // Turn to face the target if required. else if (!attackAircraft.TargetInFiringArc(self, target, 4 * attackAircraft.Info.FacingTolerance)) { aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.TurnSpeed); } return(false); }
public Activity MoveWithinRange(Target target, WDist range) { return(new MoveWithinRange(self, target, WDist.Zero, range)); }
public Actor[] ActorsInCircle(WPos location, WDist radius, LuaFunction filter = null) { var actors = Context.World.FindActorsInCircle(location, radius); return(FilteredObjects(actors, filter).ToArray()); }
public ContrailRenderable(World world, Color color, WDist width, int length, int skip, int zOffset) : this(world, new WPos[length], width, 0, 0, skip, color, zOffset) { }
public Activity MoveTo(CPos cell, int nearEnough) { return(new Move(self, cell, WDist.FromCells(nearEnough))); }
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.GuidedTarget.CenterPosition + 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; 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.TargetExtraSearchRadius, out blockedPos)) { pos = blockedPos; shouldExplode = true; } // Create the smoke 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.TrailSequence, 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(Squad owner) { if (!owner.IsValid) { return; } if (!owner.IsTargetValid) { var closestEnemy = FindClosestEnemy(owner); if (closestEnemy != null) { owner.TargetActor = closestEnemy; } else { owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true); return; } } var leader = owner.Units.ClosestTo(owner.TargetActor.CenterPosition); if (leader == null) { return; } if (leader.Location != lastLeaderLocation) { lastLeaderLocation = leader.Location; lastUpdatedTick = owner.World.WorldTick; } if (owner.TargetActor != lastTarget) { lastTarget = owner.TargetActor; lastUpdatedTick = owner.World.WorldTick; } // HACK: Drop back to the idle state if we haven't moved in 2.5 seconds // This works around the squad being stuck trying to attack-move to a location // that they cannot path to, generating expensive pathfinding calls each tick. if (owner.World.WorldTick > lastUpdatedTick + 63) { owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsIdleState(), true); return; } var ownUnits = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Units.Count) / 3) .Where(a => a.Owner == owner.Units.First().Owner&& owner.Units.Contains(a)).ToHashSet(); if (ownUnits.Count < owner.Units.Count) { // Since units have different movement speeds, they get separated while approaching the target. // Let them regroup into tighter formation. owner.Bot.QueueOrder(new Order("Stop", leader, false)); var units = owner.Units.Where(a => !ownUnits.Contains(a)).ToArray(); owner.Bot.QueueOrder(new Order("AttackMove", null, Target.FromCell(owner.World, leader.Location), false, groupedActors: units)); } else { var enemies = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.SquadManager.Info.AttackScanRadius)) .Where(owner.SquadManager.IsPreferredEnemyUnit); var target = enemies.ClosestTo(leader.CenterPosition); if (target != null) { owner.TargetActor = target; owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsAttackState(), true); } else { owner.Bot.QueueOrder(new Order("AttackMove", null, Target.FromCell(owner.World, owner.TargetActor.Location), false, groupedActors: owner.Units.ToArray())); } } if (ShouldFlee(owner)) { owner.FuzzyStateMachine.ChangeState(owner, new GroundUnitsFleeState(), true); } }
public override Activity Tick(Actor self) { if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); if (ChildActivity != null) { return(this); } } if (cargo != carryall.Carryable) { return(NextActivity); } if (cargo.IsDead || IsCanceling || carryable.IsTraitDisabled || !cargo.AppearsFriendlyTo(self)) { carryall.UnreserveCarryable(self); return(NextActivity); } if (carryall.State != Carryall.CarryallState.Reserved) { return(NextActivity); } switch (state) { case PickupState.Intercept: QueueChild(self, movement.MoveWithinRange(Target.FromActor(cargo), WDist.FromCells(4), targetLineColor: Color.Yellow), true); state = PickupState.LockCarryable; return(this); case PickupState.LockCarryable: if (!carryable.LockForPickup(cargo, self)) { Cancel(self); } state = PickupState.MoveToCarryable; return(this); case PickupState.MoveToCarryable: { // Line up with the attachment point var localOffset = carryall.OffsetForCarryable(self, cargo).Rotate(carryableBody.QuantizeOrientation(self, cargo.Orientation)); var targetPosition = cargo.CenterPosition - carryableBody.LocalToWorld(localOffset); if ((self.CenterPosition - targetPosition).HorizontalLengthSquared != 0) { QueueChild(self, new Fly(self, Target.FromPos(targetPosition)), true); return(this); } state = PickupState.Turn; return(this); } case PickupState.Turn: if (carryallFacing.Facing != carryableFacing.Facing) { QueueChild(self, new Turn(self, carryableFacing.Facing), true); return(this); } state = PickupState.Land; return(this); case PickupState.Land: { var localOffset = carryall.OffsetForCarryable(self, cargo).Rotate(carryableBody.QuantizeOrientation(self, cargo.Orientation)); var targetPosition = cargo.CenterPosition - carryableBody.LocalToWorld(localOffset); if ((self.CenterPosition - targetPosition).HorizontalLengthSquared != 0 || carryallFacing.Facing != carryableFacing.Facing) { state = PickupState.MoveToCarryable; return(this); } if (targetPosition.Z != self.CenterPosition.Z) { QueueChild(self, new Land(self, Target.FromActor(cargo), -carryableBody.LocalToWorld(localOffset))); return(this); } state = delay > 0 ? PickupState.Wait : PickupState.Pickup; return(this); } case PickupState.Wait: QueueChild(self, new Wait(delay, false), true); state = PickupState.Pickup; return(this); case PickupState.Pickup: // Remove our carryable from world Attach(self); return(this); } return(NextActivity); }