示例#1
0
            public AttackActivity(Actor self, Target target, bool allowMove, bool forceAttack, Color?targetLineColor = null)
            {
                attack        = self.Trait <AttackFollow>();
                move          = allowMove ? self.TraitOrDefault <IMove>() : null;
                revealsShroud = self.TraitsImplementing <RevealsShroud>().ToArray();

                this.target          = target;
                this.forceAttack     = forceAttack;
                this.targetLineColor = targetLineColor;

                // The target may become hidden between the initial order request and the first tick (e.g. if queued)
                // Moving to any position (even if quite stale) is still better than immediately giving up
                if ((target.Type == TargetType.Actor && target.Actor.CanBeViewedByPlayer(self.Owner)) ||
                    target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain)
                {
                    lastVisibleTarget       = Target.FromPos(target.CenterPosition);
                    lastVisibleMaximumRange = attack.GetMaximumRangeVersusTarget(target);
                    lastVisibleMinimumRange = attack.GetMinimumRangeVersusTarget(target);

                    if (target.Type == TargetType.Actor)
                    {
                        lastVisibleOwner       = target.Actor.Owner;
                        lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes();
                    }
                    else if (target.Type == TargetType.FrozenActor)
                    {
                        lastVisibleOwner       = target.FrozenActor.Owner;
                        lastVisibleTargetTypes = target.FrozenActor.TargetTypes;
                    }
                }
            }
示例#2
0
            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);
            }
示例#3
0
            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);
            }