• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Vote for the theme of Hive's HD Modeling Contest #7! Click here to vote! - Please only vote if you plan on participating❗️

Designing an AI API

Status
Not open for further replies.
Level 21
Joined
Mar 27, 2012
Messages
3,232
NOTE: I have not begun writing the system. I'm only designing it atm and will not start progress on any actual code before the design is solid.

I'm in the process of designing an AI API that could be used both for unit-specific AIs and player-specific ones.
The structure of the system is as follows:
*Each unit and player can have a list of programs.
*Each program has a list of routines, which represent program functionalities. It is perfectly fine to have routines that handle the entirety of a program, but the system is designed to allow recombination of functionality in case someone needs it.(I do)
*The system periodically iterates over all registered units and players, running the programs that are referenced to them.
*The program can iterate in 3 ways:
**Rate - A specific amount of units is checked per tick. (scales well, but the reaction time per unit/player can get sluggish)
**Ratio - A factor of all currently registered units is checked per tick. (scales fairly evenly with number of units and keeps the reaction delay somewhat consistent)
**Total - Every unit is checked every single tick.(scales poorly, but provides a 100% consistent delay)
*Each program can return either true or false. If a program returns true, all subsequent programs on that unit/player are skipped this iteration. (can be toggled off in configuration)

How the system works:
Periodically, all units registered to the system are iterated over.
The programs on each unit are run and the iteration stops if any of them returns true(can be disabled in configuration).
The programs are user-defined and as such, their exact implementation is not handled by the system. The system does handle their registration and running though.



CreateProgram
RegisterRoutine - Creates a new routine type. A routine has some code, which may include calls to subroutines.
CreateRoutine
ProgramAddRoutine
ProgramRemoveRoutine
DestroyRoutine
UnitAddProgram
UnitRemoveProgram
PlayerAddProgram
PlayerRemoveProgram
DestroyProgram


IsUnitIdle(unit)
Checks if the unit currently has any orders other than "stop"
IsUnitIdleCondition - Doesn't take a unit as argument.
Instead assumes that the unit is the one currently running programs
Meant for use along with the If routine
UnitHasProgram
ProgramHasRoutine
GetProgramIterations - Returns the amount of programs that have returned true on this unit in this tick.
PauseProgram - Makes the system skip this program indefinitely
ResumeProgram
PauseUnitPrograms - Makes the system indefinitely skip all programs on this unit
ResumeUnitPrograms
PausePlayerPrograms
UnitRunProgram - Runs a specific program on a unit. Intended to be used with event-based programs.
ProgramRunRoutine - Runs a specific routine. Intended to be used from inside a routine to implement subroutines.
PlayerRunProgram
ResumePlayerPrograms
SetUnitSkip(bool)
SetPlayerSkip(bool)

Predefined routines:
If
*Takes two routines and a condition
*If condition is true, runs the first routine
*Otherwise runs the second routine

Why I am posting this:
*To estimate how much use such a system would have.
*To fix design mistakes without having to redesign the system halfway.
*To get feedback on what features would be desired in addition to the ones listed.
*To open up the field of WC3 AI by providing some insights into how things can be done.
*Because I intend to write this system, even if for myself.(duh)
 
My first experience in "programming" was building the "AI" in the Final Fantasy 12 Gambit System. User-customization is the hallmark of AI in my opinion. League of Legends would be better if the 4 allies were AI-controlled (if the AI were better than the crappy kind Riot built).

Needless to say, there are many approached to AI and replicating a lot of the functionality of the following resource would be good practice (ie. if ally health lower than 50% cast Holy Light)

Reference: finalfantasy.wikia.com/wiki/Gambits
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
The gambit system looks like a good reference on what routines could be useful. Bookmarked it.

Also, I agree that many games would benefit from having programmable AI. For instance, I've thought for a long time that building cities in minecraft would be far more fun if I could populate them with my own programmed characters. Heck, it could even be some kind of endgame extension to villages that requires a staff-of-doing-awesome-shiz.
 
The API and structure so far looks good. So programs are dynamic on a per-unit base and routines are static? I prefer the struct syntax, but that's just me.

This could be extremely interesting, especially when combined with my threat system, as it provides some neat synergy like defining routines based on the position in each unit's threat list, etc..

Here's some things that came to my mind, though:
- I like the way you can customize the iteration process; I used the "rate" approach in many of my systems that rely on heavy enumeration or loops to even out calculation load and can only recommend it for a system like that; however, it would be a good idea to provide a custom filter function for units to be considered by the periodic check; for example, only considering units within range of a player units in RPGs or units that are in combat if a combat tracking system is in place.

- you should definitely implement a way to seperate in-system orders from user- or trigger-issued orders; preferrably a priority system; this way, you can basicly have a "basic" AI even on user-controlled units (like autocasting certain spells) without it interferring with user-issued orders; an ideal priority system needs at least 3 layers:
low priority) takes precedence only over "idle" state of units; but has lower priority than user-issued orders (i.e. auto-cast of spells) ... all AI-issued orders should go into this layer by default.
user priority) takes precedence over low priority orders; user-issued orders (or triggered orders outside of programs) should go into this layer by default. If a user priority order is in place, low priority orders will not be issued.
top priority) takes precedence over user-priority orders; this is useful for example if you want to code a confusion or mind-control AI; or a morale system for units or anything that makes the user lose control over the unit.

- idle units that have no order need to be destinguished between "true idle" as in standing still, doing nothing and "pseudo idle", as in have no order, but actually do something (like auto-engage or returning to their guard position after auto-engage)
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
The API and structure so far looks good. So programs are dynamic on a per-unit base and routines are static? I prefer the struct syntax, but that's just me.

This could be extremely interesting, especially when combined with my threat system, as it provides some neat synergy like defining routines based on the position in each unit's threat list, etc..

Here's some things that came to my mind, though:
- I like the way you can customize the iteration process; I used the "rate" approach in many of my systems that rely on heavy enumeration or loops to even out calculation load and can only recommend it for a system like that; however, it would be a good idea to provide a custom filter function for units to be considered by the periodic check; for example, only considering units within range of a player units in RPGs or units that are in combat if a combat tracking system is in place.

- you should definitely implement a way to seperate in-system orders from user- or trigger-issued orders; preferrably a priority system; this way, you can basicly have a "basic" AI even on user-controlled units (like autocasting certain spells) without it interferring with user-issued orders; an ideal priority system needs at least 3 layers:
low priority) takes precedence only over "idle" state of units; but has lower priority than user-issued orders (i.e. auto-cast of spells) ... all AI-issued orders should go into this layer by default.
user priority) takes precedence over low priority orders; user-issued orders (or triggered orders outside of programs) should go into this layer by default. If a user priority order is in place, low priority orders will not be issued.
top priority) takes precedence over user-priority orders; this is useful for example if you want to code a confusion or mind-control AI; or a morale system for units or anything that makes the user lose control over the unit.

- idle units that have no order need to be destinguished between "true idle" as in standing still, doing nothing and "pseudo idle", as in have no order, but actually do something (like auto-engage or returning to their guard position after auto-engage)

The idea is that routines are typed and can be instanciated. This allows a specific instance of a routine to have parameters assigned to it, while not exactly making it necessary. Some routines simply don't need to use any arguments whatsoever.

Limiting the system to only consider units of a specific set is doable in at least 2 ways. One of them is simply disabling the system for this specific unit(making the system skip it). The other is using an If routine that checks for whatever condition you want.

The priority thing is something that could be done as a plugin. Atm I imagine it being made with some simple If routine checks. To be more specific, I imagine I'd make the If routine so that it returns whatever its subroutine returns, giving the possibility to skip remaining programs on the unit if either of the actions in the If routine runs successfully.

Sounds great.
I'm writting an General AI Behavior. I use struct syntax, It seems good. But with different maptypes, AI must have big changes.
How should we do?

The idea with this design is that it abstracts away the specific details of what an AI does. It only assumes that an AI is assigned to a unit at some point and provides the option to have periodic events handled by the system.

---

Design choices that I've considered:
Making the system ignore units would be most efficient if the units wouldn't even have to be iterated. That is, it would be more efficient to remove them from the linked list. However, if they are added to any specific place on the list upon resuming, it could potentially cause delays with iteration. I'm thinking of making it so that a newly added/resumed unit will always be put to wherever the iteration counter currently is, thus guaranteeing that it gets checked.
However, this can (maybe) increase the delay for other units a lot in some cases.
 
The priority thing is something that could be done as a plugin. Atm I imagine it being made with some simple If routine checks. To be more specific, I imagine I'd make the If routine so that it returns whatever its subroutine returns, giving the possibility to skip remaining programs on the unit if either of the actions in the If routine runs successfully.
The priority thing should be basic functionality, simply because it is required for this to work. Remember that you can not disabled the default AI in the game. This will cause Neutral hostile player to use spells at will, autocast abilities to autocast, regardless of players, etc. ... the default AI will definitely interfere with your system if you don't take care of it.
A robust priority system is the core of any AI system. It should be integrated into the design from the get-go. Making this a plugin will ultimately just make the code more complicated. You have to deal with symptoms of this anyway (even in the basic system), so you should consider it in your design right from the start.

The idea with this design is that it abstracts away the specific details of what an AI does. It only assumes that an AI is assigned to a unit at some point and provides the option to have periodic events handled by the system.
The problem with a high abstraction level is that it increases the amount of code lines for actual content creation.

If a unit requires one program and three routines, you are already at 5 lines for this unit:
- create program
- add routine
- add routine
- add routine
- add program to unit

Just to give you some insights on why that is problematic:
Basicly, I have a hardcoded AI system in place in my map that works in tandem with my threat library. Every unit that has a custom AI calls a seperate function that I can freely code.

However, 95% of these are just one-liners with a simple wrapper that registers order strings, a defined targets (first in threat, second in threat, random, wounded ally, random ally, omni) and the order of priority (if ability #1 is on cooldown, use #2, etc.).
This is the standard use-case in most games. You basicly just want the AI to spam abilities on cooldown after a defined target policy.

Make sure in your design that this standard use-case requires as few lines to set-up as possible.
Remember that you will probably only justify a custom AI system if you have many different unit types in your map. A custom AI system only makes sense when setting up units takes very few lines of code. Otherwise, you could just hardcore all AI and be done with it.

Design choices that I've considered:
Making the system ignore units would be most efficient if the units wouldn't even have to be iterated. That is, it would be more efficient to remove them from the linked list. However, if they are added to any specific place on the list upon resuming, it could potentially cause delays with iteration. I'm thinking of making it so that a newly added/resumed unit will always be put to wherever the iteration counter currently is, thus guaranteeing that it gets checked.
However, this can (maybe) increase the delay for other units a lot in some cases.
Delay is not a problem. AI is about simulating human behaviour after all. A reaction time of 0 as in "perfect game" AI is unrealistic and in most cases undesired.
 
Status
Not open for further replies.
Top