public override bool Tick(Actor 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 && attack.RequestedTarget.Type == TargetType.Invalid) { return(true); } if (attack.IsTraitPaused) { return(false); } target = target.Recalculate(self.Owner, out var targetIsHiddenActor); attack.SetRequestedTarget(self, target, forceAttack); 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)); } } // The target may become hidden in the same tick the AttackActivity 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 = attack.GetMaximumRangeVersusTarget(target); lastVisibleOwner = target.FrozenActor.Owner; lastVisibleTargetTypes = target.FrozenActor.TargetTypes; } 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) { return(true); } // Target is hidden or dead, and we don't have a fallback position to move towards if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self)) { return(true); } 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) { return(true); } return(false); } // We can't move into range, so give up if (move == null || maxRange == WDist.Zero || maxRange < minRange) { return(true); } wasMovingWithinRange = true; QueueChild(move.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition)); return(false); }
public override Activity Tick(Actor self) { if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivity(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); }