Building Your Own Planetary Annihilation AI

  • Date: 4th October, 2015
  • Updated: 15th November, 2016
  • Category: AI, Step-by-step

So before we begin this guide to building your own Planetary Annihilation AI, let me give you my credentials. My name is Quitch and I used to run AI Central for Total Annihilation and Total Annihilation Kingdoms, as well as creating my own Queller AI for these games. I never really became a fan of Supreme Commander, but thankfully Sorian picked up the reigns with his now famous Sorian AI.

Now here we are now with Planetary Annihilation. Sorian is making the official AI and I’m modding it.

Queller AI

Author: Quitch Mod Type: Server
A smarter and more humanlike non-cheating AI for Titans and Legion, with easier and harder difficulties than vanilla. Normal through Absurd remain the vanilla AI, while Casual through Uber are Queller. See the forum thread under details for more information on the differences between difficulties. Doesn't affect Galactic War due to it lacking server mod support.
Discuss on the forums
Provided by CMM

I’m going to lay out what I’ve learned, in brief, to give you a headstart on creating your own AI. This guide doesn’t cover everything, that would be a guide vastly more complex than this, but it will provide you with enough to set you well on your way.

Some elements of the AI are moddable while others are not. You can setup build lists for structures, units and platoons. You will provide prioritisations and the criteria which must be met for the build to occur. You can also choose the composition of the AI’s platoons and which micro behaviour is assigned to which squad.

There are two key elements of the AI: the personality and the brain.

Prerequisites

This guide will be written from the perspective of a Windows user running Planetary Annihilation: Titans.

You will need:

An optional extra is AI Showdown. This is a tool which can format your AI brain to ensure it is compatible with others. The readme that it ships with explains how to use it, though it is a tool for advanced users. You won’t need it if you follow the advice from the Compatibility With Other AIs section from day one.

Setting Up Your Workspace

Modifying the original game files is bad practice, instead you should setup your AI as a mod so it will overwrite the original AI without literally overwriting it. This occurs through a process called shadowing. Be aware that it is not possible to mod the AI for Galactic War.

%LOCALAPPDATA%\Uber Entertainment\Planetary Annihilation\

There are two folders here of interest, create them if you don’t see them:

  • client_mods
  • server_mods
Planetary Annihilation AI mod folder structure

Early PAMM adopters will have a mods directory in place of client_mods. This works just as well.

Personality changes are UI mods, they live in the client_mods folder. Changes to the brain are server mods and live in server_mods, they are applied to the server when you’re the host and all players are subject to those changes for the length of that game.

Personality Mod

Planetary Annihilation AI mod personalities

Under client_mods create a new folder, it can be named anything you like. Give it the following folder structure: \ui\mods\modname\ and create a file called modname.js in the last folder. You’ll be creating your ai.js file in here later.

In the root folder of your mod create a file called modinfo.json. It should look as follows:

{
  "context": "client",
  "identifier": "com.pa.yourname.aipersonalitymodnamewithoutspaces",
  "display_name": "modname",
  "description": "a short description",
  "author": "yourname",
  "version": "#.#",
  "build": "pabuildnumber",
  "date": "yyyy/mm/dd",
  "signature": "not yet implemented",
  "forum": "forum thread URL",
  "category": [
    "ai",
    "classic",
    "titans"
  ],
  "product": ["Classic or Titans as appropriate"],
  "id": "modnamewithoutspaces",
  "icon": "URL of preview picture",
  "dependencies": [
    "com.pa.yourname.aibrainmodnamewithoutspaces"
  ],
  "priority": 100,
  "scenes": {
    "new_game": [
      "coui://ui/mods/modname/modname.js"
    ]
  }
}

Here is the completed Queller AI Personalities modinfo.json:

{
  "context": "client",
  "identifier": "com.pa.quitch.qQuellerAIPersonalities",
  "display_name": "Queller AI Personalities",
  "description": "AI personalities for use with the Queller AI server mod.",
  "author": "Quitch",
  "version": "1.3",
  "build": "87296",
  "date": "2015/09/09",
  "signature": "not yet implemented",
  "forum": "https://forums.uberent.com/threads/rel-server-queller-ai-v3-0.65795/",
  "category": [
    "ai",
    "titans"
  ],
  "product": ["Titans"],
  "id": "qQuellerAIPersonalities",
  "icon": "http://i.imgur.com/cFJeCgf.gif",
  "dependencies": [
    "com.pa.quitch.qQuellerAI"
  ],
  "priority": 100,
  "scenes": {
    "new_game": [
      "coui://ui/mods/Queller-AI-Personalities/Queller-AI-Personalities.js"
    ]
  }
}

Brain Mod

Planetary Annihilation AI mod brain

Under server_mods create a new folder, it can be named anything you like.

In the root folder of your mod create a file called modinfo.json. It should look as follows:

{
  "context": "server",
  "identifier": "com.pa.yourname.aibrainmodnamewithoutspaces",
  "display_name": "modname",
  "description": "a short description",
  "author": "yourname",
  "version": "#.#",
  "build": "pabuildnumber",
  "date": "yyyy/mm/dd",
  "signature": "not yet implemented",
  "forum": "forum thread URL",
  "category": [
    "ai",
    "classic",
    "titans"
  ],
  "product": ["Classic or Titans as appropriate"],
  "id": "modnamewithoutspaces",
  "icon": "URL of preview picture",
  "dependencies": [
    "com.pa.yourname.aipersonalitymodnamewithoutspaces"
  ],
  "priority": 100
}

Here is the completed Queller AI modinfo.json:

{
  "context": "server",
  "identifier": "com.pa.quitch.qQuellerAI",
  "display_name": "Queller AI v3.1.2",
  "description": "A more humanlike AI offering a wide range of difficulties. Doesn't affect Galactic War. Works alongside the vanilla AI",
  "author": "Quitch",
  "version": "3.1.2",
  "build": "88163",
  "date": "2015/09/21",
  "signature": "not yet implemented",
  "forum": "https://forums.uberent.com/threads/rel-server-queller-ai-v3-0.65795/",
  "category": [
    "ai",
    "titans"
  ],
  "product": ["Titans"],
  "id": "qQuellerAI",
  "icon": "http://i.imgur.com/cFJeCgf.gif",
  "dependencies": [
    "com.pa.quitch.qQuellerAIPersonalities",
    "com.pa.quitch.qAIModCompatibilityPatch"
  ],
  "priority": 100
}

The extra dependency is covered in the Compatibility With Other AIs section.

The rest of your mod’s folder structure should be identical to that of the vanilla AI, excluding neural_networks. The final structure should be:

  • modname
    • pa
      • ai
        • fabber_builds
        • factory_builds
        • platoon_builds
        • platoon_templates
        • unit_maps

Both your UI and server mod should now show up in PAMM following launch or a refresh. If they don’t then recheck the modinfo.json files.

Personality

The Planetary Annihilation AI’s personality list can be found in the following Planetary Annihilation path:

\media\ui\main\game\new_game\js\ai.js

The ai.js file defines all the AI personalities, which can be used to modify AI behaviour. In the vanilla AI this is primarily used as a means to provide difficulty levels. The following are a list of settings found in the file:

  • display name – the name shown in the personality dropdown
  • idle: true | false – when enabled the AI will do nothing
  • percent_vehicle [0 1] – a float used by the test Need[Basic/Advanced]VehicleFactory
  • percent_bot [0 1] – a float used by the test Need[Basic/Advanced]BotFactory
  • percent_air [0 1] – a float used by the test Need[Basic/Advanced]AirFactory
  • percent_naval [0 1] – a float used by the test Need[Basic/Advanced]NavalFactory
  • percent_orbital [0 1] – a float used by the test NeedOrbital[Launcher/Factory]
  • personality_tags: [<string>, <string>, …] – used by the boolean test HasPersonalityTag
  • metal_drain_check [0 1] – a float that modifies the outcome of CanAffordPotentialDrain
  • energy_drain_check [0 1] – a float that modifies the outcome of CanAffordPotentialDrain
  • metal_demand_check [0 1] – a float that modifies the outcome of CanAffordBuildDemand
  • energy_demand_check [0 1] – a float that modifies the outcome of CanAffordBuildDemand
  • micro_type 0:none | 1:platoon | 2:squad – determines whether the AI micros by squad type within a platoon, platoon, or not at all
  • go_for_the_kill: true | false – when true set focus target to the weakest player and when false set it to the strongest based on recon
  • priority_scout_metal_spots: true | false – when false the AI scouts all areas equally but if true it understands the concept of symmetrical spawns and will scout metal areas
  • enable_commander_danger_responses: true | false – when false the AI will not engage certain commander behaviours
  • neural_data_mod: [0 … ) – a float which modifies the AI’s threat checks with values below 1 making it more conservative and above more aggressive
  • adv_eco_mod: [0 … ) – a float which modifies the outcome of CanAffordAdvancedEco when AloneOnPlanet is false
  • adv_eco_mod_alone: [0 … ) – a float which modifies the outcome of CanAffordAdvancedEco when AloneOnPlanet is true
  • factory_build_delay_min: [0 … ] (in seconds) – an integer which sets a minimum downtime for a factory following completion of a build
  • factory_build_delay_max: [0 … ] (in seconds) – an integer which sets a maximum downtime for a factory following completion of a build
  • unable_to_expand_delay: [0 … ] (in seconds) – an integer which sets a delay before the AI realises UnableToExpand should be set to true
  • per_expansion_delay: [0 … ] (in seconds) – an integer which controls the time between bases being created
  • fabber_to_factory_ratio_basic: [0 … ] – a float which is used to determine NeedBasic___Fabber is true in conjunction with the percent_ X values
  • fabber_to_factory_ratio_advanced: [0 … ] – a float which is used to determine NeedBasic___Fabber is true in conjunction with the percent_ X values
  • fabber_alone_on_planet_mod: [0 … ] – a float which is used to modify the outcome of Need[Basic/Advanced]___Fabber
  • basic_to_advanced_factory_ratio: [0 … ] – a float which is used to modify the outcome of Need[Basic/Advanced]___Fabber
  • factory_alone_on_planet_mod: [0 … ] – a float which is used to modify the outcome of Need[Basic/Advanced]___Factory
  • min_basic_fabbers: [0 … ] – an integer which is used to modify the outcome of NeedBasic___Fabber
  • max_basic_fabbers: [0 … ] – an integer which is used to modify the outcome of NeedBasic___Fabber
  • min_advanced_fabbers: [0 … ] – an integer which is used to modify the outcome of NeedAdvanced___Fabber
  • max_advanced_fabbers: [0 … ] – an integer which is used to modify the outcome of NeedAdvanced___Fabber

A completed personality can look as follows:

  'Absurd': {
    display_name: 'Absurd',
    percent_vehicle: 0.45,
    percent_bot: 0.25,
    percent_air: 0.2,
    percent_naval: 0.05,
    percent_orbital: 0.05,
    personality_tags:
    [
      "PreventsWaste"
    ],
    metal_drain_check: 0.54,
    energy_drain_check: 0.65,
    metal_demand_check: 0.71,
    energy_demand_check: 0.8,
    micro_type: 2,
    go_for_the_kill: true,
    priority_scout_metal_spots: true,
    enable_commander_danger_responses: true,
    neural_data_mod: 1.0,
    adv_eco_mod: 1.3,
    adv_eco_mod_alone: 0.85,
    fabber_to_factory_ratio_basic: 1.0,
    fabber_to_factory_ratio_advanced: 1.0,
    fabber_alone_on_planet_mod: 2.0,
    basic_to_advanced_factory_ratio: 0,
    factory_alone_on_planet_mod: 0.5,
    min_basic_fabbers: 2,
    max_basic_fabbers: 6,
    min_advanced_fabbers: 3,
    max_advanced_fabbers: 20
  },

Formatting Your Personality

You want to append your personalities to the existing personality list rather than overwrite it. This ensures compatibility is maintained with other AI mods, and allows your AI to work alongside the vanilla AI. Thanks go to wondible for the Javascript we’ll be using here.

Your ai.js file should be stored in your client mod, under \ui\mods\modname\

Setup your ai.js as follows:

(function() {
  var extensions = {

Here you should insert your personality block(s)

  }

  var baseline = model.aiPersonalities.Absurd || {

Put in the values here that you wish to use as a baseline for your AI – these values will be used by each personality where an alternate value has not been entered

  }
  Object.keys(extensions).forEach(function(name) {
    extensions[name] = _.extend(JSON.parse(JSON.stringify(baseline)), extensions[name])
    extensions[name].name = name
    extensions[name].display_name = name
  })

  _.extend(model.aiPersonalities, extensions)

  model.aiPersonalityNames(_.keys(model.aiPersonalities));
})()

Your final file will look something like this:

(function() {
  var extensions = {
    'Bot Rush': {
      percent_vehicle: 0.00,
      percent_bot: 0.60,
      percent_air: 0.05,
      percent_naval: 0.35,
      percent_orbital: 0.00,
      metal_drain_check: 0.3,
      energy_drain_check: 0.3,
      metal_demand_check: 0.72,
      energy_demand_check: 0.4,
      neural_data_mod: 2.0,
      adv_eco_mod: 2,
      adv_eco_mod_alone: 0.85
    },
    'Advanced Rush': {
      percent_vehicle: 0.50,
      percent_bot: 0.25,
      percent_air: 0.2,
      percent_naval: 0.05,
      percent_orbital: 0.00,
      metal_drain_check: 0.3,
      energy_drain_check: 0.3,
      metal_demand_check: 0.72,
      energy_demand_check: 0.4,
      adv_eco_mod: 0.25,
      adv_eco_mod_alone: 0.1
    },
  }
  var baseline = model.aiPersonalities.Absurd || {
    percent_vehicle: 0.45,
    percent_bot: 0.25,
    percent_air: 0.2,
    percent_naval: 0.05,
    percent_orbital: 0.05,
    metal_drain_check: 0.54,
    energy_drain_check: 0.65,
    metal_demand_check: 0.71,
    energy_demand_check: 0.8,
    micro_type: 2,
    go_for_the_kill: true,
    priority_scout_metal_spots: true,
    enable_commander_danger_responses: true,
    neural_data_mod: 1.0,
    adv_eco_mod: 1.3,
    adv_eco_mod_alone: 0.85,
    fabber_to_factory_ratio_basic: 1.0,
    fabber_to_factory_ratio_advanced: 1.0,
    fabber_alone_on_planet_mod: 2.0,
    basic_to_advanced_factory_ratio: 0,
    factory_alone_on_planet_mod: 0.5,
    min_basic_fabbers: 2,
    max_basic_fabbers: 6,
    min_advanced_fabbers: 3,
    max_advanced_fabbers: 20
  }
  Object.keys(extensions).forEach(function(name) {
    extensions[name] = _.extend(JSON.parse(JSON.stringify(baseline)), extensions[name])
    extensions[name].name = name
    extensions[name].display_name = name
  })

  _.extend(model.aiPersonalities, extensions)

  model.aiPersonalityNames(_.keys(model.aiPersonalities));
})()

In the lobby it should look like this:

Planetary Annihilation AI mod personalities

Brain

Planetary Annihilation AI mod brain

The AI brain is where all the build priority and platoon composition decisions are made, and is where the bulk of any AI modding will take place.  All the relevant files can be found under:

  • \media\pa\ai\
  • \media\pa_ex1\ai\

Files from pa_ex1 take precedence over identical files in the pa folder. Mods can only shadow the pa folder, but override both classic and Titans content.

Folder Structure

The AI consists of the following folders:

  • fabber_builds – where all fabber orders are kept, this includes builds, assists and any orders pertaining to teleporters
  • factory_builds – controls what factories produce
  • neural_networks – holds all the neural network information produced during testing. You should not modify or shadow this folder’s contents so it can be ignored
  • platoon_builds – controls the conditions for the forming of platoons (armies) as formatted by platoon_templates and moving them between planets
  • platoon_templates – defines the AI’s platoons, the squads within those platoons and what type of micro each squad will use
  • unit_maps – where the AI’s shorthand names for units map to the unit files. It’s also used to create shorthand names for groups of units e.g. AnyBasicFabber.

Everything under these folders is parsed, including any subfolders you might want to create. There’s also a file called ai_config.json at the root of the \pa\ai\ folder which defines the maximum number of units the AI can field.

The easiest way to start modding is to take a copy of an existing AI file and drop it into the appropriate folder in your mod structure, then make some tweaks. Where a file exists in the mod in the same folder with the same name as in the PA directory, the mod version will be used instead. While this is good for early experimentation and learning the ropes, it is not my recommended way of developing anything you intend to release and I recommend you check out the Compatibility With Other AIs section of this guide.

Builds Structure

Planetary Annihilation AI mod brain

All the vanilla AI files have had their whitespace removed during the build process, making them tricky to read. The first thing to do is copy the contents of a file and paste them into your JSON formatter and have it format them, This should leave you with a file much easier to understand. Take this example of commander_economy_builds.json.

Before formatting:

{"build_list":[{"name":"Basic Metal Extractor - cdr Desire","to_build":"BasicMetalExtractor","instance_count":1,"shared_instance_count":"BasicMetal","priority":501,"base_sort":"FromBuilder","builders":["Commander"],"build_conditions":[[{"test_type":"DesireMetal"},{"test_type":"CanFindMetalSpotToBuildBasic","boolean":true},{"test_type":"UnitCountOnPlanet","unit_type_string0":"Fabber - Orbital","compare0":"<","value0":1,"boolean":true}],[{"test_type":"DesireMetal"},{"test_type":"CanFindMetalSpotToBuildBasic","boolean":true},{"test_type":"CanDeployNavalFromBase","boolean":true},{"test_type":"UnitCountOnPlanet","unit_type_string0":"Fabber & (Air | Naval)","compare0":"<","value0":1,"boolean":true}]],"placement_rules":{"placement_type":"FromMainBaseCenter","threat":{"influence_type":"AntiSurface","compare_type":"<","radius":10,"value":50}}},{"name":"Basic Energy Generator - late Desire cdr","to_build":"BasicEnergyGenerator","instance_count":1,"max_num_assisters":3,"shared_instance_count":"BasicEnergy","cross_planet_shared_count":true,"priority":350,"builders":["Commander"],"build_conditions":[[{"test_type":"DesireEnergy"},{"test_type":"UnitCount","unit_type_string0":"EnergyProduction & Basic","compare0":">=","value0":4},{"test_type":"UnitCount","unit_type_string0":"EnergyProduction & Advanced","compare0":"<","value0":1},{"test_type":"CanFindPlaceToBuild","string0":"BasicEnergyGenerator"}]],"placement_rules":{"buffer":6,"placement_type":"FromBaseCenter","threat":{"influence_type":"AntiSurface","compare_type":"<","radius":10,"value":50}}},{"name":"Basic Energy Generator - early Desire Cdr","to_build":"BasicEnergyGenerator","instance_count":1,"max_num_assisters":3,"shared_instance_count":"BasicEnergy","cross_planet_shared_count":true,"priority":500,"builders":["Commander"],"build_conditions":[[{"test_type":"DesireEnergy"},{"test_type":"UnitCount","unit_type_string0":"EnergyProduction & Basic","compare0":"<","value0":4},{"test_type":"UnitCount","unit_type_string0":"EnergyProduction & Advanced","compare0":"<","value0":1},{"test_type":"CanFindPlaceToBuild","string0":"BasicEnergyGenerator"}]],"placement_rules":{"buffer":6,"placement_type":"FromBaseCenter","threat":{"influence_type":"AntiSurface","compare_type":"<","radius":10,"value":50}}}]}

After formatting:

{
  "build_list":[
    {
      "name":"Basic Metal Extractor - cdr Desire",
      "to_build":"BasicMetalExtractor",
      "instance_count":1,
      "shared_instance_count":"BasicMetal",
      "priority":501,
      "base_sort":"FromBuilder",
      "builders":[
        "Commander"
      ],
      "build_conditions":[
        [
          {
            "test_type":"DesireMetal"
          },
          {
            "test_type":"CanFindMetalSpotToBuildBasic",
            "boolean":true
          },
          {
            "test_type":"UnitCountOnPlanet",
            "unit_type_string0":"Fabber - Orbital",
            "compare0":"<",
            "value0":1,
            "boolean":true
          }
        ],
        [
          {
            "test_type":"DesireMetal"
          },
          {
            "test_type":"CanFindMetalSpotToBuildBasic",
            "boolean":true
          },
          {
            "test_type":"CanDeployNavalFromBase",
            "boolean":true
          },
          {
            "test_type":"UnitCountOnPlanet",
            "unit_type_string0":"Fabber & (Air | Naval)",
            "compare0":"<",
            "value0":1,
            "boolean":true
          }
        ]
      ],
      "placement_rules":{
        "placement_type":"FromMainBaseCenter",
        "threat":{
          "influence_type":"AntiSurface",
          "compare_type":"<", "radius":10, "value":50
        }
      }
    },
    {
      "name":"Basic Energy Generator - late Desire cdr",
      "to_build":"BasicEnergyGenerator",
      "instance_count":1,
      "max_num_assisters":3,
      "shared_instance_count":"BasicEnergy",
      "cross_planet_shared_count":true,
      "priority":350,
      "builders":[
        "Commander"
      ],
      "build_conditions":[
        [
          {
            "test_type":"DesireEnergy"
          },
          {
            "test_type":"UnitCount",
            "unit_type_string0":"EnergyProduction & Basic",
            "compare0":">=",
            "value0":4
          },
          {
            "test_type":"UnitCount",
            "unit_type_string0":"EnergyProduction & Advanced",
            "compare0":"<",
            "value0":1
          },
          {
            "test_type":"CanFindPlaceToBuild",
            "string0":"BasicEnergyGenerator"
          }
        ]
      ],
      "placement_rules":{
        "buffer":6,
        "placement_type":"FromBaseCenter",
        "threat":{
          "influence_type":"AntiSurface",
          "compare_type":"<",
          "radius":10,
          "value":50
        }
      }
    },
    {
      "name":"Basic Energy Generator - early Desire Cdr",
      "to_build":"BasicEnergyGenerator",
      "instance_count":1,
      "max_num_assisters":3,
      "shared_instance_count":"BasicEnergy",
      "cross_planet_shared_count":true,
      "priority":500,
      "builders":[
        "Commander"
      ],
      "build_conditions":[
        [
          {
            "test_type":"DesireEnergy"
          },
          {
            "test_type":"UnitCount",
            "unit_type_string0":"EnergyProduction & Basic",
            "compare0":"<",
            "value0":4
          },
          {
            "test_type":"UnitCount",
            "unit_type_string0":"EnergyProduction & Advanced",
            "compare0":"<",
            "value0":1
          },
          {
            "test_type":"CanFindPlaceToBuild",
            "string0":"BasicEnergyGenerator"
          }
        ]
      ],
      "placement_rules":{
        "buffer":6,
        "placement_type":"FromBaseCenter",
        "threat":{
          "influence_type":"AntiSurface",
          "compare_type":"<",
          "radius":10,
          "value":50
        }
      }
    }
  ]
}

You will see the file is broken down by each build item and each one uses the following structure:

  • Name – Used to identify this item in your files, it must be unique
    • to_build – a name from the unit_maps or platoon_templates files
    • instance_count – how many fabbers on the planet this job can be assigned to
    • shared_instance_count – counts towards the instance count on any project on the planet using the same shared instance
    • cross_planet_shared_count – instance count is system-wide
    • min_num_assisters – at least this many fabbers should assist
    • max_num_assisters – no more fabbers than this may assist
    • priority – the AI works down its priority list, highest to lowest, until it finds something to do
    • builders – which units can carry out this action, using names drawn from the unit_maps files
    • build_conditions – blocks of tests which, if passed, lead to the item in to_build being built
    • placement_rules – requirements for placement of the unit
      • buffer – the units of space required around the build
      • threat – won’t be placed in areas which don’t meet the threat criteria

The AI will parse tests in the order they’re found under build_conditions, you should therefore be careful to optimise your order for performance. The Test Structure section will provide some recommendations on test order, you can also look at the vanilla AI files for some ideas on optimal ordering. Also use common sense, a planet-wide test will be less expensive than a system-wide one, for example.

Templates Structure

The platoon_templates have their own unique structure:

 "Land_Attack_Max":{
      "units":[
        {
          "unit_types":"(Bot & Mobile) - Fabber - AirDefense - Construction - Artillery - Heavy - SelfDestruct",
          "min_count":0,
          "max_count":-1,
          "squad":"Fast"
        },
        {
          "unit_types":"((Tank | Bot) & Mobile) - Fabber - AirDefense - Construction - Artillery - Heavy - Scout - SelfDestruct",
          "min_count":0,
          "max_count":-1,
          "squad":"General"
        },
        {
          "unit_types":"((Tank | Bot) & Mobile) & Artillery",
          "min_count":0,
          "percent":0.35,
          "squad":"Artillery"
        },
        {
          "unit_types":"((Tank | Bot) & Mobile & Heavy)",
          "min_count":0,
          "percent":0.5,
          "squad":"Close"
        },
        {
          "unit_types":"((Tank | Bot) & Mobile) & AirDefense",
          "min_count":0,
          "percent":0.25,
          "squad":"Defense"
        },
        {
          "unit_types":"((Tank | Bot) & Mobile) & Construction",
          "min_count":0,
          "percent":0.1,
          "squad":"Defense"
        },
        {
          "unit_types":"Land & Scout",
          "min_count":0,
          "percent":0.05,
          "squad":"Defense"
        }
      ]
    },

The above represents a single platoon from the vanilla platoon_templates.json file. Your platoon_templates are called by platoon_builds and define the composition of the AI’s armies. A platoon consists of multiple squads and the AI will micro on  the level of squad type, platoon or not at all depending on the personality. While you can define as many squads as you like, they will be handled by type once the platoon forms e.g. all units in defense squads within a platoon form a single squad.

  • Name – called by platoon_builds files
    • units – fixed name used to declare that the following blocks are the squads
      • unit_types – all units with UNITTYPEs (see the All Unit Types section) which meet these criteria can be included in this squad
      • min_count – at least this many units must exist in this squad before the platoon will successfully form
      • max_count – no more units than this can exist within the squad
      • percent – maximum percentage of the overall platoon size that this squad can represent
      • squad – how the AI should handle the squad; see All Squad Types for more information

Test Structure

There are a number of standard layouts you will encounter when looking at tests. The order presented below is a rough order of fastest to slowest, with exceptions provided. You should attempt to have the AI reject a block as quickly as possible to keep its running cost down.

Boolean

The simple true or false test which comes in many different formats. In some instances you will see there is no check done against a boolean value, in which case the test is looking for a returned value of true. Each test_type has a particular set of formatting that it requires, you cannot simply drop the boolean check.

{
  "test_type": ""
}

{
  "test_type": "",
  "boolean":
}

{
  "test_type": "",
  "string0": "",
  "boolean":
}

Note: CanFind___ToBuild___ checks should be placed at the bottom of any build_condition block they appear in.

Fixed Value

A test against a fixed value

{
  "test_type": "",
  "string0": "",
  "compare0": "",
  "value0":
}

{
  "test_type": "",
  "unit_type_string0": "",
  "compare0": "",
  "value0":
}

String0 is measured against value0 using the relational operator in compare0.

  • ==
  • <
  • >
  • <=
  • >=

unit_type_string checks use logical operators:

  • & – AND
  • | – OR
  • – NOT
  • ( ) – use brackets to ensure the desired order of operations

Ratio

A test between two separate values which can use any of the following conditions:

{
  "test_type": "",
  "unit_type_string0": "",
  "unit_type_string1": "",
  "compare0": "",
  "value0": 
}

String0 is divided by string1 to produce a ratio which is the checked against value0 using the compare0 relational operator.

  • ==
  • <
  • >
  • <=
  • >=

unit_type_string checks use logical operators:

  • & – AND
  • | – OR
  • – NOT
  • ( ) – use brackets to ensure the desired order of operations

String

A test which requires a string input. The string will be drawn from the unit_maps files.

"test_type": ""
"string0": ""

Note: CanFindPlaceToBuild uses this format and is one of the most expensive tests the AI can run. Place at the bottom any build_condition block in which it appears.

Common Tests

I won’t be detailing how every test works since there are too many, your best bet is to look through the AI files and get an idea of how each is used. What I will do is detail some of the most common.

  • AloneOnPlanet
{
  "test_type": "AloneOnPlanet",
  "boolean": false
}

This test is always false for a planet until the AI has scouted the entire planet and determined that no enemies are present.

  • CanAffordBuildDemand
{
  "test_type": "CanAffordBuildDemand"
}

Passed only if the AI can afford the build cost of the unit, taking into account expected changes in income. Found in fabber and factory files. Modified by metal_demand_check and energy_demand_check in the personality.

  • CanAffordPotentialDrain
{
  "test_type": "CanAffordPotentialDrain",
  "string0": "BasicAirFactory"
}

Passed only if the AI can afford the running cost of the unit, accounting for planned resource generators. Found in fabber files. Modified by metal_drain_check and energy_drain_check in the personality.

  • CanFindPlaceToBuild
{
  "test_type": "CanFindPlaceToBuild",
  "string0": "BasicAirFactory"
}

Passed if the AI can find a location to place the unit. Found in fabber files.

  • HasPersonalityTag
{
  "test_type": "HasPersonalityTag",
  "string0": "uber",
  "boolean": true
}

Checks whether the AI personality in use has a personality_tags entry which matches string0.

  • HaveEcoForAdvanced
{
  "test_type": "HaveEcoForAdvanced",
  "boolean": true
}

Checks whether the AI can afford to run one advanced factory and one advanced fabber. Modified by the adv_eco_mod and adv_eco_mod_alone values in the personality.

  • UnitCountOnPlanet
{
  "test_type": "UnitCountOnPlanet",
  "unit_type_string0": "Factory & Advanced",
  "compare0": "<",
  "value0": 1
}

Parses all units on the planet with the UNITTYPE_ criteria in unit_type_string0, then checks if they meet the criteria laid out in compare0 and value0.

  • UnitPoolCount
{
  "test_type": "UnitPoolCount",
  "unit_type_string0": "((Tank | (Bot & Artillery)) & Mobile) - Fabber - AirDefense - Construction - Titan",
  "compare0": ">=",
  "value0": 4
}

The unit pool consists of units not yet assigned to a platoon.

  • UnitRatioOnPlanet
{
  "test_type": "UnitRatioOnPlanet",
  "unit_type_string0": "(Tank | (Bot & (Artillery | Advanced)) & Mobile) & AirDefense",
  "unit_type_string1": "(Tank | (Bot & (Artillery | Advanced)) & Mobile) - Fabber - Deconstruction - Titan",
  "compare0": "<",
  "value0": 0.2
}

Works in the same way as a UnitCountOnPlanet test, except this time value0 is a float used as a ratio measure. The count of string0 is divided by string1 to obtain the value for comparison.

All Test Types

Every valid test_type value. You will find test_type in every AI file apart from platoon_templates.

  • AlliedUnitCountOnPlanet
  • AloneOnPlanet
  • BaseThreat
  • BaseThreatened
  • BaseThreatRatio
  • CanAffordBuildDemand
  • CanAffordPotentialDrain
  • CanAttackWithPoolUnits
  • CanAttackWithPoolUnitsBomber
  • CanAttackWithPoolUnitsFighter
  • CanAttackWithPoolUnitsLand
  • CanDeployLandFromBase
  • CanDeployNavalFromBase
  • CanFindControlPointToBuild
  • CanFindMetalSpotToBuildAdvanced
  • CanFindMetalSpotToBuildBasic
  • CanFindPlaceToBuild
  • CanProvideAirSupportWithPoolUnits
  • CanProvideLandUnitAssistance
  • CurrentEnergyEfficiency
  • CurrentMetalEfficiency
  • DesireEnergy
  • DesireMetal
  • DistFromMainBase
  • DistFromNearestEnemyThreat
  • EnemyAirPresenceOnPlanet
  • EnemyOrbitalPresenceOnPlanet
  • EnemyPresenceOnPlanet
  • EnemySurfacePresenceOnPlanet
  • EnergyStorageToProductionRatio
  • FactoryHasOpenSlot
  • FactorySlotsEmpty
  • FocusTargetThreat
  • FocusTargetThreatRatio
  • HasPersonalityTag
  • HaveEcoForAdvanced
  • HaveFullPlanetIntel
  • HaveHadANukeEvent
  • HaveSeenEnemyUnits
  • HaveThrustToMovePlanet
  • MetalStorageFrac
  • MetalStorageToProductionRatio
  • MetMinAdvancedFabberCount
  • MetMinBasicFabberCount
  • NeedAdvancedAirFabber
  • NeedAdvancedAirFactory
  • NeedAdvancedBotFabber
  • NeedAdvancedBotFactory
  • NeedAdvancedNavalFactory
  • NeedAdvancedVehicleFabber
  • NeedAdvancedVehicleFactory
  • NeedBasicAirFabber
  • NeedBasicAirFactory
  • NeedBasicBotFabber
  • NeedBasicBotFactory
  • NeedBasicNavalFactory
  • NeedBasicVehicleFabber
  • NeedBasicVehicleFactory
  • NeedOrbitalFactory
  • NeedOrbitalLauncher
  • NoWhereToRun
  • OtherPlanetCanProvideLandUnitAssistance
  • OtherPlanetCanReceiveLandUnitAssistance
  • OtherPlanetNeedsLandUnitAssistance
  • OtherPlanetNeedsOrbitalUnitAssistance
  • OtherPlanetNeedsReconAssistance
  • PlanetCanBeUsedAsKineticWeapon
  • PlanetCount
  • PlanetHasUseablePlanetWeapon
  • PlanetHighestEnemyArmyThreat
  • PlanetHighestEnemyArmyThreatRatio
  • PlanetIsGasGiant
  • PlanetIsMainEcoBase
  • PlanetIsRespawnable
  • PlanetOrGasGiantWithoutPresence
  • PlanetThreat
  • PlanetWithoutFabberWithTeleporter
  • PlanetWithoutPresence
  • PotentialEnergyEfficiency
  • PotentialMetalEfficiency
  • PresenceOnOtherPlanet
  • SafePlanetOrGasGiantWithoutPresence
  • SystemThreat
  • SystemToPlanetThreatRatio
  • ThisPlanetNeedsLandUnitAssistance
  • ThisPlanetNeedsOrbitalUnitAssistance
  • ThisPlanetNeedsReconAssistance
  • UnableToExpand
  • UnitCount
  • UnitCountAroundBase
  • UnitCountInBase
  • UnitCountInCelestialTransit
  • UnitCountOnPlanet
  • UnitCountPerPlanetRadius
  • UnitPoolCount
  • UnitRatioOnPlanet
  • WantCommanderOffPlanet
  • WantCommanderOffPlanetByTeleporter

All Task Types

These are all the orders the AI can issue. You will see task_type in platoon files as well as in the miscellaneous fabber file.

  • AreaBuild
  • Artillery
  • BomberAttack
  • BuilderAssist
  • FighterAttack
  • LandAttack
  • NavalAttack
  • Nuke
  • OrbitalFabberMoveToPlanet
  • OrbitalFabberMoveToSafePlanet
  • OrbitalFighterAttack
  • OrbitalLaserAttack
  • OrbitalRecon
  • Scout
  • TeleportFabberToPlanet
  • TeleportLandToPlanet
  • TransferOrbitalToPlanet
  • TransferReconToPlanet
  • TransportToPlanet
  • UnitCannon

All Threats

Some tests and checks reference various threats. I have divided the strings into threats which look at health, DPS, income and count.

  • Health
    • Air
    • Land
    • Naval
      • Does not include subs
    • Orbital
    • Sub
  • DPS
    • AntiAir
    • AntiOrbital
    • AntiSub
    • AntiSurface
      • Includes naval surface weapons
  • Income
    • Economy
  • Count
    • Nuke
    • AntiNuke

All Squads

Valid squad names for use by the AI. When the AI’s personality micro_type is 2 these values will be used to determine how squads are handled in combat.

  • Artillery
  • Close
  • Defense
  • Escort
  • Fast
  • General
  • Suicide

The following was sourced from Sorian:

  • Fast: used for microing when the neural net determines that is the best course of attack
  • Artillery: assumes these are longer range than average units and uses them as such
  • General: general rank and file units
  • Escort: all units in the escort squad perform an area patrol to protect the other squads

When microing:

  • The Fast squad will break off and micro back and forth near their max range
  • The Artillery squad will break off and attack from near their max range
  • The General squad will attack from near their max range
  • The Close squad will issue an attack order
  • The Defense squad will tag along with whoever is going to be furthest away

When attacking from range:

  • The Artillery squad will break off and attack from their max range
  • Everyone else will tag along with the Artillery squad

When issuing an attack:

  • All attack squads issue an attack
  • The defense squad tags along with whoever has the longest range

Suicide operates in the same fashion as Close, but doesn’t try to withdraw.

All Unit Types

The files in unit_maps contain paths to the unit files. These files contain a number of UNITTYPE_ references which are used by the AI to filter the units it wants. The following is a complete list of every valid unit type:

  • Advanced
  • Air
  • AirDefense
  • Artillery
  • Basic
  • Bomber
  • Bot
  • CannonBuildable
  • CmdBuild
  • CombatFabAdvBuild
  • CombatFabBuild
  • Commander
  • Construction
  • Custom1
  • Custom2
  • Custom3
  • Custom4
  • Deconstruction
  • Defense
  • Economy
  • EnergyProduction
  • FabAdvBuild
  • Fabber
  • FabBuild
  • FabOrbBuild
  • Factory
  • FactoryBuild
  • Fighter
  • Gunship
  • Heavy
  • Hover
  • Important
  • Land
  • LaserPlatform
  • MetalProduction
  • MissileDefense
  • Mobile
  • Naval
  • NoBuild
  • Nuke
  • NukeDefense
  • Offense
  • Orbital
  • OrbitalDefense
  • Scout
  • SelfDestruct
  • Structure
  • Sub
  • SupportCommander
  • Tactical
  • Tank
  • Titan
  • Transport
  • Wall

Compatibility With Other AIs

When you first start experimenting with AI it’s easiest to just tweak values in the existing files and see how those changes play out. Over time you begin to develop an AI with its own identity, and eventually almost nothing of the original files will remain.

However, if you want to release your AI to the wider world it’s better that it works with other AIs already established to maximise the number of people who can use your AI. There’s even actually a subsection of players who will be more interested in watching different AIs fight it out than they are in playing against the AI themselves. Having other AIs to test yours against is also a valuable way to identify flaws in your approach across a wide-range of scenarios, especially as you can run many more AI tests than you can human tests.

The key elements involved in this are:

  • Use subfolders
Planetary Annihilation AI mod subfolders

Don’t put your files in the root of each folder, create your own structure.

  • Use unique filenames
Planetary Annihilation AI mod filenames

If you use the same filename as someone else, or the vanilla AI, you risk overwriting that AI.

  • Use unique Name values in build_list and platoon_templates
Planetary Annihilation AI mod names

Prefix all your Name fields with something unique to your AI, such as its name or the personality.

  • Use personality tags
Planetary Annihilation AI mod personality tags

Put a HasPersonalityTag test at the top of every build block to look for a personality tag unique to your AI.

  • AI Mod Compatibility Patch dependency

Add this mod as a dependency in your modinfo.json to ensure the vanilla AI works alongside yours without interfering.

This:

  "dependencies": [
    "com.pa.yourname.aibrainmodnamewithoutspaces"
  ],

Becomes this:

  "dependencies": [
    "com.pa.yourname.aibrainmodnamewithoutspaces",
    "com.pa.quitch.qAIModCompatibilityPatch"
  ],

Only one thing cannot differ between AIs, and that is the unit cap in ai_config.json. My advice is not to mod this file, if you’re hitting the unit limit then you’re hitting the limits of what the engine can handle.

Testing

Testing comes in two important parts. Firstly, you want to make sure your JSON files are valid, otherwise you’re just going to crash the server. Secondly, you want to see how your AI performs and get a feel for whether you’re making it better or worse.

JSON Validation

Planetary Annihilation AI mod JSON validation

This one is simple, just copy and paste the contents of your file into your JSON validator tool and then have it process it. Any errors will be highlighted, and you need to fix them before you can use the AI.

I cannot emphasise enough the importance of verifying every change you make. No matter how confident you are that you couldn’t have screwed up the formatting, trust me, you will sometimes screw up the formatting. Always change, test and then save. Not saving until you’ve validated is an excellent way to remind yourself that the changes need to be checked.

Game Tests

You’re going to watch AI vs. AI games until your eyeballs bleed, and if you try to do this at regular speed you’ll die of old age. You can run a local server window which will allow you to control game speed, as well as enabling additional AI logging that will help you identify invalid tests and issues with brackets.

\bin_x64\server.exe --ai-log --enable-crash-reporting --game-mode PAExpansion1:Config

Setup a shortcut to the above file with those options. Launching it should give you a pair of windows.

Planetary Annihilation AI mod server

You need to enable local server within your Planetary Annihilation settings, and you must launch this server session before setting up a skirmish lobby. Once you’ve launched the game you can adjust the target sim speed by selecting the PA Server window and using the PdUp/PgDn keys. Adjustments made prior to launching will not take effect.

Logs are saved to %LOCALAPPDATA%\Uber Entertainment\PA Server\log\

As of Planetary Annihilation: Titans, changes made to your AI will not take effect until you restart the game.

Test often as there are a lot of variables involved in any game.

Never assume two spawns on a symmetrical planet are equal in terms of AI performance. Any planet you want to use to check your AI’s progress should be first checked in numerous games with equal AIs to find out whether there is any spawn bias you need to be aware of.

Publishing

So you’ve built the world’s greatest AI and now you want to unleash it on the community. But how? Publish it on PAMM. For instructions on that refer to the forum thread Getting your mod on PAMM.

My workflow is as follows:

  1. Setup
    1. Setup GitHub repository for mod
    2. Fork development branch
  2. Development
    1. Make change
    2. Test
    3. Update changelog
    4. Commit
    5. Sync
    6. Repeat
  3. Release
    1. Remove white space from AI files
    2. Update modinfo.json identifiers, date, version and build
    3. Add version and date header to changelog
    4. Merge development with master
    5. Create a release on master
    6. Publish release on PAMM using release zip
    7. Update linked forum thread with release notes
  4. Post-release
    1. Switch back to development branch
    2. Restore whitespace
    3. Update modinfo.json identifiers
    4. Return to development stage

References

  1. For Backers Only: AI building stuff
  2. The sorian PA AI thread
  3. The sorian PA: TITANS AI thread

Recommended Reading

About the author

Quitch

Queller AI
Planetary Annihilation AI shared armies
  • 0

Enabling AI Shared Armies

This guide will help you restore AI shared armies, and in addition allow you to play on a shared armies team with the AI. Be warned, the AI doesn’t understand playing shared armies with a human and will regularly steal your stuff. In other words, it’s like the online experience.

Read Guide
    Bsport

    What speed do you run your Ai vs Ai games at, double normal speed or more? Also could you make an AI who only built locust swarms?

    Quitch

    The PA sim will run as fast as it’s capable of, subject to a cap. So in effect what you’re doing is raising that ceiling, and the sim will run at that speed, then slow down as it can no longer maintain it. I tend to raise the cap to about 4x speed if I’m just looking for the game result, and 2x if I want to look at game behaviours.

    The AI is subject to the same rules as a player, so you could set it up to only make Locusts from an Advanced Bot Factory, but it would need to tech up to it first.

    I don’t had a mods directory under %LOCALAPPDATA%Uber EntertainmentPlanetary Annihilation . I have client_mods and server_mods. The client_mods has com.pa.quitch.qQuellerAIPersonalities. I’m not sure why my system has client_mods instead of mods.. but it works and seems naming consistent.

    Quitch

    I think the PAMM naming convention changed later on, but use client_mods if you have that. I’ll update the guide to note this.

    EDIT: done

    Quitch

    Turns out MetMinAdvancedFabberCount is a test that exists, so I’ve added it to the list.

Leave a Reply