My favourite joke format at the moment is this: “It’s only X if it comes from the X region of France, otherwise it’s just sparkling Y.” In this case, “it’s only artificial intelligence if it’s from the AI region of France, otherwise it’s just sparkling if statements”. I can dunk on AI hype, you see, because I’m programming AI for my game again.
I’ve done quite a bit of AI programming already. The bots have fairly well-developed situational awareness — if they see an item they want they’ll move to pick it up; if they see an enemy they will attack; and if they are fighting they’ll use their abilities.
What they lack, however, is strategic awareness. Seeing as the Arena will have bonus objectives, and the same logic will power boss fights and full characters in the campaign, bots are going to need to that extra layer of intelligence.
Sparkling If Statements
My bots are built like units in Warcraft III. Effectively, they are given “orders”, and an order is comprised of a heap of logic that spins round its awareness of its surroundings, checking for distractions that might supersede the order and keeping track of the target. The only difference between the player’s hero and the bots is that the hero’s orders come directly and unfiltered from the mouse and keyboard, whereas the bots’ are built up from banks of the aforementioned sparkling if statements.
Since bots need to respond to changing circumstances, I have been using a stack to store orders. As some new distraction is spotted, a new order is added to the top of the stack and is used to set the bot’s moment-to-moment actions. Once that order has been completed, it dies and the next one is popped off the stack; the theory being that the bot will never leave some action undone, and will eventually unwind back to its original patrol or guard order.
Except I found that many orders should replace themselves on completion, so each order has the concept of a follow-up action. For example, an investigation order either expires and reverts to the previous order, or expires and replaces itself with combat against the “distraction” it has spotted.
Within the states of mind of a bot, it seems to me now that there is a distinction between transient orders and persistent ones, and it’s actually very important. A bot’s patrol route is its default mode of existence, the core to which it should always return. A combat behaviour, however, should only exist for as long as there are opponents to fight. Investigation too is a throw-away action, to be released when the bot gives up the chase or when it finds somebody to battle. A bot that successfully kills a target shouldn’t then go back to finish off investigating the same distraction it’s just eliminated.
So despite building my bot intelligence on a stack, I’ve already implemented many features to subvert that structure.
In the traditional campaign format, the stack structure does fit fairly well, because enemy units have a simple sequence of behaviours: patrol, investigate/attack, return to patrolling. Even combat behaviour for the average campaign unit is straightforward: engage with the primary attack, either closing in for melee or attempting to maintain distance for ranged weapons.
In the Arena, or facing off against fully-sentient human pilots, that’s much too basic. Arena combatants are as well-equipped as the player — they have up to five abilities, should prioritise targets like a human might, and have the potential to retreat and seek repairs.
Health seeking is a fun one because it’s something of a special case. This is an order that supersedes all others once it is in effect — a bot in need of repair will not fight nor attempt to pick up potentially useful items along the way. Once it’s got those repairs, though, what should it do? Right now, it’ll fall back into what it was doing before — probably combat against a unit which may well be on the other side of the level by now, or even dead by another’s hand.
If you’re still with me, you’ll probably agree that this is starting to feel very convoluted. The stack approach only seems to work if all behaviours are equal and valid forever until properly carried out, and what I am starting to realise is that it, for Exon at least, that simple patrol/attack/patrol case is actually a tiny minority of situations.
What I think I actually need is something much more clear-cut, where there’s a solid distinction between the root order and one transient distraction action. This should eliminate the awkwardness of follow-up orders and returning to orders that have long ceased to have any relevance.
The final reason I want to dismantle the stack is for the strategic awareness. While most standard campaign units will patrol and always patrol, if the Arena is to have multiple objectives then the bots should be able to decide during a fight what they might want to do afterwards. Most will start with a pseudo-patrol that takes them wandering around the Arena, but if I add a flag to capture, then some should go for that instead once they’ve finished their current action. (And once a flag has been picked up, that root order now needs to be either “run home” or “hunt the flag carrier”.)
With a stack, that’s truly awful to implement — the whole point of a stack is that you add things to the top, not the bottom. If I stop using a stack, however, and have a straight switch between “the persistent objective” and “a single transient distraction”, I can swap the persistent action beneath the distraction — and once the distraction is dealt with, the base order takes hold and the bot goes off on its merry way.
… and I thought making everything singleplayer would avoid a load of headaches!