I vividly remember when the HD Remaster of Final Fantasy XII was announced back in 2016, I couldn’t believe my eyes and felt like I was dreaming.  Coming at the end of the PS2 generation, and after the first mainline MMO (Final Fantasy XI) the original game was met with a mixed reception because of its radical departure of FF series’ conventions.  Nevertheless, many people including myself loved this game and its unique gameplay, the Ivalice lore and aesthetic, and some even consider it to be the best Final Fantasy game of the modern age. A remaster was something that people hoped for, but it seemed unlikely as Square never acknowledged FFXII or the people behind it in recent years (unlike the older Final Fantasy games before it).

Final Fantasy XII has an interesting combat system with a rich AI management system as a central feature. I’ve always been fascinated by it as a kid back when I played it on PS2, and replaying it on PS4  urged me to prototype its battle system. The day after launch, instead of playing the game, I fired up Unity and did just that.

Below’s an explanation of how the Gambit system works, and my approach in code.

The Gambit system

With the gambit system, you can control what every party member should do in battle in a series of if-else statements. Each statement is called a gambit rule and can be turned on/off or moved up/down to give it a different priority. The if part is a selector that denotes a certain target based on a condition (i.e: nearest enemy, enemy with lowest health, player with “Slow” status effect).  If the condition is met, the assigned action will be performed. If not, it will move to the statement below it. Initially, these if-else statements are easy and obvious things like “Attack the nearest enemy”, “Cast cure when an ally is below 50% health”, but there are many more complex ones available. Here’s a look at some setups you can do (source: PlayStation Blog and Gameskinny)

 

 

The available range of conditions is so big however that it comes close to every possible action the player would want to do when manually controlling the flow of combat in RPG. You have full control over what each character would do in combat on a microscopic level. All of this is “programmed” prior to combat and during combat, you can sit back, drink a cup of coffee and see the action unfold on screen. When needed you can manually give instructions, or change gambits mid-battle, but skilled players can finish entire boss battles without having to touch the controller.046209_Window_15-22-39

Somehow it is incredibly satisfying to see the result of your “programming” and how the battle unfolds without having to do too much yourself. At its core it’s a simple system but it works so well in the game and even 10 years later, and I still have to see a better micromanagement system in an RPG.

Programming Considerations

Before starting this I made a list of considerations:

  • Easy to read
  • Use object-oriented programming to create a rich framework where it’s easy and non-destructive to create new gambit rules.
  • Provide basic UI to see a change gambits in real-time while playing

We start with creating the condition class. This holds core data such as the name, the type of condition and the resulting unit when the condition is met. Derived classes each model one condition. In this example, a condition that searches for a unit that is weak to a specified element. These derived classes have to override and implement the IsConditionMet function. This bool should return true if the condition is true and assign a target to the ResultUnit variable.

public abstract class GambitTargetCondition
{
    public GambitTargetType TargetType;
    public string Name;
    public Unit ResultUnit;

    public abstract bool IsConditionMet(Unit instigator);

    public GambitTargetCondition(string name, GambitTargetType type)
    {
        this.Name = name;
        this.TargetType = type;
    }
}

The implementation of one condition

public class TargetWeakToElement : GambitTargetCondition
{
    private UnitWeakness weaknessToCheck;
    public TargetWeakToElement(string displayName, UnitWeakness weakness) : base(displayName, GambitTargetType.Enemy)
    {
        weaknessToCheck = weakness;
    }

    public override bool IsConditionMet(Unit instigator = null)
    {
        var result = GameManager.Instance.GetNearestUnitsInRangeBasedOnSourceTag(instigator);

        if (result != null)
        {
            foreach (var unit in result)
            {
                if (unit.Weakness == weaknessToCheck)
                {
                    ResultUnit = unit;
                    return true;
                }
            }
        }

        return false;
    }
}

Like this, we check for a condition and when it’s met, we’ll get a target back. On this target, we can execute an action. These actions also get wrapped in an abstract class, which contains vital info such as the name, cast time and MP cost. Derived classes will override the DoAction abstract method and get access to both the instigator and target. We aren’t locking health changes to this abstract class, the derived class has to implement it when needed. Therefore, the derived action class can do anything it wants to do. Want to just damage an enemy? Fine! Want to cast an AoE on all party members? Fine! Want to change the scene lighting to something moody and play Utada Hikaru’s Face My Fears? You name it!

public abstract class GambitAction {

    public string Name;
    public float CastTime;
    public int MPCost = 0;

    public abstract void DoAction(Unit source, Unit target);

    public GambitAction(string name, float castTime)
    {
        this.Name = name;
        this.CastTime = castTime;
    }

    public GambitAction(string name, float castTime, int mpCost)
    {
        this.Name = name;
        this.CastTime = castTime;
        this.MPCost = mpCost;
    }
}

And an implementation of a simple action to cast Fire:

public class FireAction : GambitAction
{
    public FireAction() : base("Fire", 4, 6) { }

    public override void DoAction(Unit source, Unit target)
    {
        float value = 0.3f * Mathf.Pow(source.Magic, 2.0f);

        if (target.Weakness == UnitWeakness.Fire)
        {
            value = 130; //incinerate it
            GameManager.Instance.SpawnDamageText("WEAK", target.transform.position);
        }

        target.ChangeHealth(-(int)value);

        GameManager.Instance.SpawnActionEffect(EffectType.FireMagick, target);
        GameManager.Instance.SpawnDamageText(value.ToString(), target.transform.position);

        GameManager.Instance.AddToCombatLog(source.Name, Name);
    }
}

Note that the action doesn’t know anything about the condition. They exist separately and could be used separately. The condition just checks if a condition is valid, and assigns a target when it is. The action just does something you want on the specified target. To glue them together like in FFXII’s Gambit System, we’ll create a wrapper for each gambit rule, and a simple loop that iterates through them, checks if a Gambit Rule’s condition is valid, and executes the action on the Gambit Rule’s target.

The Gambit Rule

public class GambitRule {

    public int Order;
    public bool IsEnabled;
    public GambitTargetCondition TargetCondition;
    public GambitAction GambitAction;

    public void ToggleEnabled()
    {
        IsEnabled = !IsEnabled;
    }
}

For each Unit, we’ll have a loop for their gambit system, this is the core of their AI:


    [Header("Unit Gambits")]
    public GambitRule CurrentValidatedRule;
    public List GambitRules = new List();

    public void GambitLoop()
    {
        if (CurrentValidatedRule == null)
            ValidateNextRuleFromGambits();

        if (CurrentValidatedRule != null && CurrentValidatedRule.TargetCondition.ResultUnit != null)
        {
            StartDelay -= Time.deltaTime;

            if (StartDelay > 0.01)
                return;

            TimeToAttack -= Time.deltaTime;

            if (TimeToAttack = gambit.GambitAction.MPCost)
            {
                CurrentValidatedRule = gambit;
                TimeToAttack = gambit.GambitAction.CastTime * Speed; //default speed modifier for player is 1
                MaxTimeToAttack = TimeToAttack;
                return;
            }
        }
    }

    public void GambitsChanged()
    {
        //triggers the system to go over the gambits again and pick a new rule.
        CurrentValidatedRule = null;
    }

The only thing left is to create some gambit rules in code and add them to each unit’s GambitRules. In the video above I’ve also added some empty ones and made a UI to edit them while playing.

 GambitRule fireRule = new GambitRule();
        fireRule.TargetCondition = new TargetWeakToElement("Target: Weak to Fire", UnitWeakness.Fire);
        fireRule.GambitAction = new FireAction();
        fireRule.IsEnabled = true;

With this, we have the Gambit System up and working, and you can add many more conditions and actions with each different implementations.

I’ve added a short video below to demonstrate this:

It’s a powerful and easy to use AI system that could easily double as a simple enemy AI in other kinds of games. The system in Final Fantasy XII has both been praised for its ingenuity, as well as criticized for taking away player control, yet I find it something really cool and I hope more RPGs let you customize party AI behavior at such a granular level like this.

Thanks for reading, have a great day!

Nick