focus_interface.MaintainFocus now instantly completes

Interesting… I extended my NPC behavior a bit, so maybe there’s an edge case somewhere in there. I pasted some semi-cleaned up code below, but the gist is:

  • I’ve added an interface (team_targetable_v2) to leverage a method, TargetTeam.
  • Instead of telling the NPC what to do inside of OnBegin when it’s created, I first let it create itself, then use TargetTeam in some other code to tell the NPC what to do
  • Other NPC behaviors will extend creep_behavior_default to override “TargetReached”
  • The NPC will go through some logic of prioritizing who to walk towards, which results in “ValidTarget”
  • In the code below you can see my workaround for the MaintainFocus finishing by adding a loop around it. Previously I just had MaintainFocus outside of the block on its own. I verified this was breaking by putting a print before and after MaintainFocus.
creep_behavior_default := class(npc_behavior, team_targetable_v2):

    @editable
    MyReachDistance : float = 500.0

    @editable
    MyMovementType : NPCMovementTypes = NPCMovementTypes.Run

    @editable
    MyMovementSpeedMultiplier : float = 1.0

    var NPCUtilities : npc_utilities = npc_utilities{}

    TargetTeam<override>(AttackLane : Lane, ?ForceAttackPlayers : logic = false)<suspends> : void =
        if:
            # Get the Agent (this NPC).
            Agent := GetAgent[]
            Character := Agent.GetFortCharacter[]
            Navigatable := Character.GetNavigatable[]
            Focus := Character.GetFocusInterface[]        
        then:
            loop:
                # If the NPC died then break the loop
                if (not Character.IsActive[]):
                    break

                # If we are overriding to target a player only, then choose the player. Otherwise follow
                # standard logic.
                MaybeClosestTarget := 
                    if (ForceAttackPlayers?, PlayerInLane := AttackLane.GetPlayersInLane()[0]):
                        option{PlayerInLane}
                    else:
                        NPCUtilities.GetClosestCreepTarget(AttackLane, Self)

                if:
                    ValidTarget := MaybeClosestTarget?
                    TargetDistance := NPCUtilities.CheckAgentDistance(ValidTarget, Self)
                    TargetDistance > MyReachDistance
                then:
                    var CreepReachedTarget : logic = false

                    race:
                        # Keep looking at the target
                        block:
                            loop:
                                Focus.MaintainFocus(ValidTarget)
                                Sleep(0.1)
                        block:
                            # If the target we're navigating to dies, then stop the race
                            loop:
                                Sleep(0.2)
                                if (not ValidTarget.GetFortCharacter[].IsActive[]):
                                    break
                        block:
                            # If we were targeting a player but there's now a guard, re-evaluate
                            if (not ForceAttackPlayers?, IsPlayer := player[ValidTarget]):
                                loop:
                                    Sleep(2.0)
                                    MaybeNewTarget := NPCUtilities.GetClosestCreepTarget(AttackLane, Self)

                                    if (NewTarget := MaybeNewTarget?, NewTarget <> ValidTarget):
                                        break
                            else:
                                # If we are already chasing a guard then let this thread wait
                                # This should never win the race.
                                Sleep(Inf)
                        block:
                            #Create a navigation target from ValidTarget
                            NavTarget := MakeNavigationTarget(ValidTarget)

                            ThisMovementType := if (MyMovementType = NPCMovementTypes.Run) then movement_types.Running else movement_types.Walking

                            # Tell the NPC to go to this spot within MyReachDistance. Method will wait here until we get a result.
                            NavResultGoTo := Navigatable.NavigateTo(NavTarget, ?MovementType:=ThisMovementType, ?ReachRadius := MyReachDistance)

                            if (NavResultGoTo <> navigation_result.Reached):
                                  # [Some logic here to try a backup for navigating]
                            else:
                                set CreepReachedTarget = true
                                
                    # If our race ended with reaching the target, make sure the creep has time to finish the TargetReached logic before restarting.
                    if (CreepReachedTarget?):
                        TargetReached(ValidTarget)

                # Sleep for the overall loop
                Sleep(1.0)

    # Override to perform custom behavior in subclass when we reached InTarget based in MyReachDistance.
    TargetReached(InTarget : agent)<suspends> : void=
        block: