There was some interest in the AI improvements that I developed for my upcoming patch to Epic Encounters, which solves the problem of enemies "stalling" during their turns. I will outline here the issues addressed by my modifications and the implementation thereof.
There are other improvements that were previously made (Ice Wall destruction, generic priority improvements), as well as various considerations for performance that I will not discuss here (the scripts get quite long)--this is to present the system's logic. Of course, if anyone is interested in the full scripts, I can upload and provide them
Suggestions are of course welcome
The AI stalls when it is asked to move and the pathing algorithm cannot find a valid location to satisfy the request. In theory, the scripting should throw a MovementFailed interrupt, which is caught by the behavior to end and block it. However, the MovementFailed interrupt is usually or never thrown by CharacterMoveInRange(); this is the core of the problem. Generally, CharacterMoveInRange() is called from spell wrapper scripts.
In my scripting, I chose to address this by building my own interrupt for these behaviors. This is done via timer checks during the character's turn--if the character's AP or position doesn't change between ticks, I assume that it is doing nothing. The timer checks if the character has flagged itself as attempting a movement behavior, if it did, then the timer fires an interrupt. This same timer also ends a character's turn if no activity is detected for too long.
Here is the activity detection timer code:
To use this interrupt, we must provide the behavior reaction name before using CharacterMoveInRange():
Additionally, code to handle the interrupt must be added to a generic INTERRUPT block of the behavior reaction--because OnManualInterrupt() doesn't appear to work.
With the former code in place, our enemies will no longer remain stalled for too long. However this is not yet ideal behavior, as they may still end up passing many turns if they continue to attempt to target the same unreachable target. To remedy this problem, we need to implement a means for the AI to recalculate its target priority while ignoring unreachable targets. I have done this with a blacklist infrastructure.
The blacklist is composed of enough int variables to handle however many player indices might be present in combat--one for each player and one for each of their possible summons--I use twelve to support up to six players. Additionally, there is one generic character variable to support blacklisting of one NPC (relevant for charmed targets or combat with friendly NPCs). These variables should be reset any time the state of pathing could change; this commonly means at the beginning of a turn, but can also occur when the character destroys an Ice Wall or uses a movement spell. This is the code used to blacklist a target--called from interrupts of movement failure:
The system checks if the unreachable target was a player and, if it was, obtains the character's playerindex and flags the corresponding blacklist variable. The blacklist variables are considered in the default attack target deliberation routine when any of them have been flagged. Here is the code for this check, called from any target deliberation routine before recording a target:
With the addition of this "custom" interrupt and the blacklist system, characters will only pass their turns if every potential target is unreachable, and should never be met with a situation that does not generate an interrupt from movement failure. Since the implementation of this AI, I have yet to witness a single creature's turn ever stall, even when considering extremely tight scenarios with many characters.
While this AI does greatly improve combat performance of enemies, the implications of this system are grand; one could tailor movement spells and teleport spells to be used only when high-priority targets are unreachable, for example.