Let’s take a moment to look at the monster A.I…
Monster A.I. can be as simple as just calculating the deltas of the positions and making them into coordinate change values. My favorite version of this came from an old BASIC program that used the SGN function on the deltas to just make them 1, 0, or -1. That makes the enemy move closer to the target.
For my CRPG, of course, something more complex is needed. I still regret that I can’t have multiple monster types in a single combat… it would be great to have melee types who charge the players while archers and spellcasters rain destruction upon them from afar… but I don’t have the codespace or graphic space to support that. So I definitely want the A.I. to be a bit more deep than “move towards player unit. Attack until dead.”
The first part is how smart the monsters are. I’ve decided on four levels of intelligence:
- Dumb – No brain to speak of, always attacks the closest enemy
- Animal – As #1, but also considers past attacks for optimal targets
- Smart – As #2, but also calculates threat for each movement vector based on closeness of enemy and allied units and obstacles
- Really Smart – As #3, but also considers ranged and spellcasting by enemy units as aggressive actions
For each monster, there are a potential four counters for each player unit, which are used to track aggression. These start at 0 for the beginning of combat, and are added to each time a player unit has a successful attack on the monster unit. That weighs the monster in favor of attacking that unit over others.
There are two phases to each action by a monster unit, acquiring a target and determining an action to take.
Targeting uses the distance of each enemy unit as a baseline, subtracting it from a high value so that closer units have higher weight values. If the monster considers aggression as a factor, this is also added. Targets that it can’t see (such as invisible units) are not added to the array at all.
If the monster is confused, ALL units are targetable, and it just picks one at random, skipping over weight calculations. In the case of only a single target (a solo party), it just picks that target. In the case of no targets (a solo invisible target) then a target isn’t factored into movement, and no targeted actions (like ranged weapons or spells) are done.
The next phase is actions. A monster only has a maximum of seven actions:
- Move up
- Move down
- Move left
- Move right
- Fire a ranged weapon
- Use a special attack
- Guard (ending turn)
Each action is added to a weight array. For movement, distance to the target is added again as a weight, so that the move that gets the monster closer to the target has the highest value. Ranged weapons have a higher weight value if distance is greater, and a random value is added to it just to keep things a little random.
I’m still considering special attacks. They are basically spells, which means they could be buffing spells, area attacks, and so forth. This requires a bit more calculation to determine when the monster uses it.
The guard action will have a higher value if the monster’s action points are low compared to the maximum. That should push the monster into ending his turn unless a particularly valuable action over-rides it. Like players, once action points hits 0 the turn ends, and any negative value is taken as fatigue damage.
For movement, if the monster is smart, he also considers threat levels. This means that the target square’s adjacent squares are considered. Obstacles like trees or boulders are good, they offer cover and reduce chances of being flanked by enemies. Likewise, allied units are good and add to the value. Enemy units, on the other hand, reduce the value. So does screen edges, because the monster doesn’t want to retreat. And magical effects like a fire wall or thorny patch will also reduce the value.
The nice thing with this approach is a simple switch to a different array allows me to simulate a fearful or retreating posture as well. In that instance, screen edges are VERY valuable, and enemy units close by make movement undesirable in that direction. Confusion will just mean the monster picks an action entirely at random.
This same A.I. is also used for players in a negative state, such as confusion, charm, or fear. This means the work I did to encapsulate actions into small consumable data words that could be passed into an engine is still useful, because I’ll need it for both the monsters and players.
The main pain of implementing the monster A.I. will be debugging it. Since a lot of these actions occur offscreen, I’ll have to set up some VERY specific test use cases to try out different scenarios, and then use the debugger to view the resulting action and make sure the logic chains are all working out. I expect the code hit will be hefty as well, to do all these calculations.
But it should be worth it to give the player a worthwhile and challenging tactical battle.