Inspired by FFXII's gambit system, I've overhauled the engine's artificial intelligence system for combat encounters
When it was originally released, FFXII got a lot of flack for the Gambit System. I believe a lot of that discontent came from players who felt that their loss of control over every combat action, which the more traditional ATB system gave you, was too much of a drastic change.
However, PC gamers were very used to this kind of combat. Some of the most beloved RPGs of all time - the Infinity Engine games such as Baldur’s Gate, Icewind Dale and Planescape Torment - carved their place in history by taking the classic turn based combat from Dungeons & Dragons and flipping it on its head with a system that’s become known as “Real Time With Pause” (RTWP). In this system, players can pause at any time during combat, issue commands to their units, and then let the battle play out according to their carefully crafted plan. It was a direct byproduct of the engine starting out as being built for RTS games. Pausing in the thick of battle allows you to change your tactics on the fly and it’s a mechanic that was so loved that it has lived on today in modern hits like Dragon Age: Origins and Obsidian’s Pillars of Eternity series.
When comparing the two, you start to realise that the Gambit System, which allowed you to interrupt your gambits to intervene when your plan started to go awry, is very similar to RTWP. You are just constructing your plan ahead of an encounter. I wasn’t a programmer when I played FFXII for the first time, but I believe that it introduced me to one of the most fundamental and powerful features of any programming language - conditionals. Because at the end of the day, the Gambit System is basically a high level programming language that allows you create a matrix of conditional statements which determine the AI behaviours of Vaan and the rest of the gang.
Some people found this boring, they felt like they weren’t in control, but I found it really satisfying. Now, I’m a turn based fan through and through. I think The Temple of Elemental Evil is the pinnacle of Dungeons & Dragons combat in a video game. Final Fantasy Tactics is in my top 5 games of all time. But I still found the Gambit System incredibly fun and to this day, FFXII is still one of my favourite games (I dream of a day when I have enough time to play the remastered version). Because of this, I looked to it for inspiration on how to rethink AI in the GoldScript Engine.
(Dragon) Scaling My AI
A few months ago, after I had finished the bulk of the Encounter Designer work, I began to want to set up some encounters and watch my AI fight itself to see how my combat and stat system was really working since there is a good amount of RNG going on and it's hard to get a feeling for how combat in the game might actually play. At the time, I only had a single AI behaviour and it was basically a procedurally written function. I wanted to write more behaviours but I immediately felt like it was going to be crappy to have to write every thought process of the behaviour each time. What about common aspects of behaviours? What if you wanted a generic set of rules for a unit to follow and then just pepper those with some customised ones? I’d be violating DRY all over the place and it would become really messy. It simply wasn't going to scale.
I had a good think about it and eventually decided to implement a Gambit System type pattern for my unit AI. This system, which I call the Strategy Tactics System (very generic, I know) basically works like this:
- Each unit has a strategy which is composed of an array of tactics
- Tactics are composed of two objects, a target selector and an action creator
- When a unit gets a turn, the strategy loops through all of its tactics and attempts them
- An attempt consists of calling the target selector’s select method to find a target and then passing that target to the action creator’s create method (as well as other arguments)
- The action creator will try to create an array of objects, each of which conform to an internally validated standard I call an AI Standard Action or ASA for short. The reason it is an array is one of the key differences between the Gambit System and the Strategy Tactics System - movement & pathfinding. In FFXII, a character’s position was never a factor, if a target could be selected based on the first condition, the assigned action would always get executed against it (as long as any consumable stat or item required was available). That’s not always going to be the case in a tactical game, you have to consider the grid and whether the unit can actually move to where it needs to be in order to perform the desired action
- To achieve this, the game package exports a Strategy class that the engine instantiates whenever an AI controlled unit gets a turn. All the dependencies are passed into the class constructor and a single public API method, getActions, is called which returns an array of ASAs which are then synchronously performed as a single promise chain
This is now all completely functional and working in the engine. What else is cool is that I've wired in UI for composing AI into the Encounter Designer so that map designers can craft whatever AI strategies they want for their units. Right now, there are simply sets of premade behaviours to choose from, but in the future it will be possible to mix and match tactics.