System for AI script debugging and passing AI command data
Seeing as AI script debugging isn't available, and the limited means of passing command AI data, I made a simple system to work around those limitations. I don't know how many people write AI scripts in Jass, but those who do may find this use full. The system uses gamecache to read AI debugging data from, and store AI command data into. It supports boolean, integer and real data types. Strings aren't needed, and units can be simply passed by using the return bug, and passing it through an AI command. Yes, gamecache is slow, compared to simply passing stuff to the the AI by using the native CommandAI. So why use gamecache then?
Pros
THE SYSTEM
The following are code sections and how it should be setup in a map and an AI script. The system uses the mission key to categorize data into AI debugging and AI command data. The key is used to uniquely identify the data.
Code needed in the map script
Map initialization trigger
It's extremely important to wait more than one command cycle (the speed at which your AI script processes commands) if you have other AI commands that are processed at map initialization.
Code needed in the AI script
The handle to the gamecache is given to the AI script by using the handle return bug. The check for debugging could be done outside the function which prevents unneeded function calls. However that would give the hassle of always having to check if the debugging flag is on. The overhead of the extra function calls isn't worth mentioning unless you have thousands of those in your AI script which is very unlikely.
HOW TO USE
This will explain in short which functions to use for passing AI data and/or debugging. It's important to always follow an AI data sequence with an AI command. If you don't then the unique key indexes for a certain category can't be reused and it will cause the system to fail when that unique index is reused.
Debugging functions
The AI script uses the following functions to set debugging data
Example:
The map script uses the following functions to get debugging data
Example:
AI data functions
The map script uses the following functions to set data
Example:
The AI script uses the following functions to get data
Example:
One last example shows how you can send multiple arguments to the AI with a unique key string. Important here is to let the AI know how many arguments its going to receive. It shows how you can add multiple data sequences by appending unique identifiers to a key string.
MAP TRIGGER CODE
AI SCRIPT CODE
The processing function will be called in the command processing thread or polling function. The Processing function appends the unique indexing number used in the map trigger code to retrieve the different data sequences. The function calls used are just examples.
TESTING
I tested the system by using the following trigger to repeatedly send different types of data followed by commands. All data checks were ok. This is just meant as a simple system which does what it needs to do.
Debugging data was tested by using a timer to repeatedly display debugging data on screen.
FINAL WORDS
Criticism, suggestions and/or comments are welcome as long as they are constructive. I can say that I find the system very use full myself when doing AI coding. If you need to do AI debugging and be able to pass multiple data types (booleans, integers and reals), and send more than one data value at a time, then I can recommend using this.
Seeing as AI script debugging isn't available, and the limited means of passing command AI data, I made a simple system to work around those limitations. I don't know how many people write AI scripts in Jass, but those who do may find this use full. The system uses gamecache to read AI debugging data from, and store AI command data into. It supports boolean, integer and real data types. Strings aren't needed, and units can be simply passed by using the return bug, and passing it through an AI command. Yes, gamecache is slow, compared to simply passing stuff to the the AI by using the native CommandAI. So why use gamecache then?
Pros
- Send a great deal of data without having to worry about AI command timing issues.
- No hassle with multiple commands or checking for multiple AI data arguments in the AI script.
- Easier to do AI script debugging compared to setting user item/unit data, or setting another player's gold/lumber values.
- More detailed and smarter AI behaviour through triggers. Strategy for attacking, building, defending, upgrading etc.
- Very slow compared to just using multiple Command AI sequences to pass the data if you're using 50+ command sequences.
- Easy to mess up if you don't know what you're doing.
THE SYSTEM
The following are code sections and how it should be setup in a map and an AI script. The system uses the mission key to categorize data into AI debugging and AI command data. The key is used to uniquely identify the data.
Code needed in the map script
JASS:
globals
constant integer SET_CACHE = 0
gamecache gc = InitGameCache("AIdata.w3v")
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function GetBooleanDebugAI takes string data_type returns boolean
return GetStoredBoolean(gc, "debug", data_type)
endfunction
function GetIntegerDebugAI takes string data_type returns integer
return GetStoredInteger(gc, "debug", data_type)
endfunction
function GetRealDebugAI takes string data_type returns real
return GetStoredReal(gc, "debug", data_type)
endfunction
function BooleanDataAI takes string data_type, boolean value returns nothing
loop
exitwhen not HaveStoredBoolean(gc, "data", data_type)
call TriggerSleepAction(.1)
endloop
call StoreBoolean(gc, "data", data_type, value)
endfunction
function IntegerDataAI takes string data_type, integer value returns nothing
loop
exitwhen not HaveStoredInteger(gc, "data", data_type)
call TriggerSleepAction(.1)
endloop
call StoreInteger(gc, "data", data_type, value)
endfunction
function RealDataAI takes string data_type, real value returns nothing
loop
exitwhen not HaveStoredReal(gc, "data", data_type)
call TriggerSleepAction(.1)
endloop
call StoreReal(gc, "data", data_type, value)
endfunction
Map initialization trigger
It's extremely important to wait more than one command cycle (the speed at which your AI script processes commands) if you have other AI commands that are processed at map initialization.
JASS:
function Trig_Init_Actions takes nothing returns nothing
call CommandAI(cpu, SET_CACHE, H2I(gc)) //Provide the AI with the gamecache to read and store data.
call TriggerSleepAction(.3) //Making sure the AI receives the gamecache before reading or writing data.
// Other map init code
call DestroyTrigger(gg_trg_Init)
set gg_trg_Init = null
endfunction
function InitTrig_Init takes nothing returns nothing
set gg_trg_Init = CreateTrigger()
call TriggerAddAction(gg_trg_Init, function Trig_Init_Actions)
endfunction
Code needed in the AI script
The handle to the gamecache is given to the AI script by using the handle return bug. The check for debugging could be done outside the function which prevents unneeded function calls. However that would give the hassle of always having to check if the debugging flag is on. The overhead of the extra function calls isn't worth mentioning unless you have thousands of those in your AI script which is very unlikely.
JASS:
globals
constant integer SET_CACHE = 0
constant real command_cycle = .2 //This is the command cycle I referred to when you send the gamecache to the AI script.
constant boolean debugging = true
gamecache gc = null
endglobals
function I2GC takes integer i returns gamecache
return i
return null
endfunction
function GetBooleanData takes string data_type returns boolean
local boolean value = GetStoredBoolean(gc, "data", data_type)
call FlushStoredBoolean(gc, "data", data_type)
return value
endfunction
function GetIntegerData takes string data_type returns integer
local integer value = GetStoredInteger(gc, "data", data_type)
call FlushStoredInteger(gc, "data", data_type)
return value
endfunction
function GetRealData takes string data_type returns real
local real value = GetStoredReal(gc, "data", data_type)
call FlushStoredReal(gc, "data", data_type)
return value
endfunction
function BooleanDebugAI takes string data_type, boolean value returns nothing
if debugging and gc != null then
call StoreBoolean(gc, "debug", data_type, value)
endif
endfunction
function IntegerDebugAI takes string data_type, integer value returns nothing
if debugging and gc != null then
call StoreInteger(gc, "debug", data_type, value)
endif
endfunction
function RealDebugAI takes string data_type, real value returns nothing
if debugging and gc != null then
call StoreReal(gc, "debug", data_type, value)
endif
endfunction
function ProcessCommands takes nothing returns nothing
local integer cmd
local integer data
loop
loop
exitwhen CommandsWaiting() == 0
set cmd = GetLastCommand()
set data = GetLastData()
call PopLastCommand()
if cmd == SET_CACHE then
set gc = I2GC(data) // Storing the gamecache sent by the map initialization code
elseif
// other command handling here.
endif
endloop
call Sleep(command_cycle)
endloop
endfunction
function main takes nothing returns nothing
call StartThread(function ProcessCommands)
// other code comes here
endfunction
HOW TO USE
This will explain in short which functions to use for passing AI data and/or debugging. It's important to always follow an AI data sequence with an AI command. If you don't then the unique key indexes for a certain category can't be reused and it will cause the system to fail when that unique index is reused.
Debugging functions
The AI script uses the following functions to set debugging data
JASS:
function BooleanDebugAI takes string data_type, boolean value returns nothing
function IntegerDebugAI takes string data_type, integer value returns nothing
function RealDebugAI takes string data_type, real value returns nothing
Example:
JASS:
call IntegerDebugAI("gold", gold)
call IntegerDebugAI("lumber", lumber)
The map script uses the following functions to get debugging data
JASS:
function GetBooleanDebugAI takes string data_type returns boolean
function GetIntegerDebugAI takes string data_type returns integer
function GetRealDebugAI takes string data_type returns real
Example:
JASS:
call DisplayTextToPlayer(user, 0, 0, "Gold: " + I2S(GetIntegerDebugAI("gold")))
call DisplayTextToPlayer(user, 0, 0, "Lumber: " + I2S(GetIntegerDebugAI("lumber")))
AI data functions
The map script uses the following functions to set data
JASS:
function BooleanDataAI takes string data_type, boolean value returns nothing
function IntegerDataAI takes string data_type, integer value returns nothing
function RealDataAI takes string data_type, real value returns nothing
Example:
JASS:
call RealDataAI("x", -950)
call RealDataAI("y", -280)
call CommandAI(cpu, SET_CAPTAIN, WHICH_CAPTAIN)
The AI script uses the following functions to get data
JASS:
function GetBooleanData takes string data_type returns boolean
function GetIntegerData takes string data_type returns integer
function GetRealData takes string data_type returns real
Example:
JASS:
call SetCaptainHome(ATTACK_CAPTAIN, GetRealData("x"), GetRealData("y"))
One last example shows how you can send multiple arguments to the AI with a unique key string. Important here is to let the AI know how many arguments its going to receive. It shows how you can add multiple data sequences by appending unique identifiers to a key string.
MAP TRIGGER CODE
JASS:
function Trig_Attack_Actions takes nothing returns nothing
call IntegerDataAI("args", 5) // Sending 5 different data sequences
call IntegerDataAI("id", 'ushd') // First sequence
call IntegerDataAI("qty", 1)
call IntegerDataAI("id1", 'umtw') // Second sequence
call IntegerDataAI("qty1", 3)
call IntegerDataAI("id2", 'uabo') // Third sequence
call IntegerDataAI("qty2", 2)
call IntegerDataAI("id3", 'ngh1') // Fourth sequence
call IntegerDataAI("qty3", 4)
call IntegerDataAI("id4", 'unec') // Fifth sequence
call IntegerDataAI("qty4", 4)
call CommandAI(cpu, DATA, ATTACK_UNIT) // Process the data sequences, in this case attack wave units.
call IntegerDataAI("start", 5)
call IntegerDataAI("send", 5)
call CommandAI(cpu, DATA, SEND_WAVE) // Send the just created attack wave
call DisplayTextToPlayer(user, 0, 0, "Starting the requested attack wave in 5 seconds.")
endfunction
//===========================================================================
function InitTrig_Attack takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, user, "-attack", true)
call TriggerAddAction(t, function Trig_Attack_Actions)
set t = null
endfunction
AI SCRIPT CODE
The processing function will be called in the command processing thread or polling function. The Processing function appends the unique indexing number used in the map trigger code to retrieve the different data sequences. The function calls used are just examples.
JASS:
globals
string array str_key
endglobals
function ProcessCommandData takes integer args, integer data_type returns nothing
local integer index = 0
loop
exitwhen index == args
if data_type == TOWN_UNIT then
call BuildTownUnits(GetIntegerData("id" + str_key[index]), GetIntegerData("qty" + str_key[index]), -1)
elseif data_type == UPGRADE then
call BuildUpgrade(GetIntegerData("id" + str_key[index]), GetIntegerData("level" + str_key[index]))
elseif data_type == POST_GUARD then
call AddGuardPost(GetIntegerData("id" + str_key[index]), GetRealData("x" + str_key[index]), GetRealData("y" + str_key[index]))
elseif data_type == DEFENSE_UNIT then
call BuildDefenseUnits(GetIntegerData("id" + str_key[index]), GetIntegerData("qty" + str_key[index]))
elseif data_type == ATTACK_UNIT then
call BuildAttackUnits(GetIntegerData("id" + str_key[index]), GetIntegerData("qty" + str_key[index]))
endif
set index = index + 1
endloop
endfunction
function main takes nothing returns nothing
set str_key[0] = ""
set str_key[1] = "1"
set str_key[2] = "2"
set str_key[3] = "3"
set str_key[4] = "4"
set str_key[5] = "5"
set str_key[6] = "6"
set str_key[7] = "7"
set str_key[8] = "8"
set str_key[9] = "9"
set str_key[10] = "10"
set str_key[11] = "11"
set str_key[12] = "12"
set str_key[13] = "13"
set str_key[14] = "14"
set str_key[15] = "15"
set str_key[16] = "16"
set str_key[17] = "17"
set str_key[18] = "18"
set str_key[19] = "19"
// Other init stuff and code here
endfunction
TESTING
I tested the system by using the following trigger to repeatedly send different types of data followed by commands. All data checks were ok. This is just meant as a simple system which does what it needs to do.
JASS:
function Trig_TestCache_Actions takes nothing returns nothing
local integer index = 0
call DisplayTextToPlayer(user, 0, 0, "Starting test...")
loop
exitwhen index == 100
call BooleanDataAI("boolean_test", true)
call CommandAI(cpu, 11, 0)
call IntegerDataAI("integer_test", 1048000)
call CommandAI(cpu, 11, 1)
call RealDataAI("real_test", 3.14)
call CommandAI(cpu, 11, 2)
set index = index + 1
endloop
call DisplayTextToPlayer(user, 0, 0, "Testing done.")
endfunction
//===========================================================================
function InitTrig_TestCache takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, user, "-testcache", true)
call TriggerAddAction(t, function Trig_TestCache_Actions)
set t = null
endfunction
Debugging data was tested by using a timer to repeatedly display debugging data on screen.
JASS:
function Trig_AI_Debug_Actions takes nothing returns nothing
local string str = "false"
if GetBooleanDebugAI("attack") then
set str = "true"
endif
call DisplayTextToPlayer(user, 0, 0, "Is AI attacking: " + str)
call DisplayTextToPlayer(user, 0, 0, "Gold: " + I2S(GetIntegerDebugAI("gold")))
call DisplayTextToPlayer(user, 0, 0, "Lumber: " + I2S(GetIntegerDebugAI("lumber")))
call DisplayTextToPlayer(user, 0, 0, "Attack wave prepare time: " + R2S(GetRealDebugAI("time")) + "|r")
set str = null
endfunction
//===========================================================================
function InitTrig_AI_Debug takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterTimerEvent(t, .5, true)
call TriggerAddAction(t, function Trig_AI_Debug_Actions)
set t = null
endfunction
FINAL WORDS
Criticism, suggestions and/or comments are welcome as long as they are constructive. I can say that I find the system very use full myself when doing AI coding. If you need to do AI debugging and be able to pass multiple data types (booleans, integers and reals), and send more than one data value at a time, then I can recommend using this.
Last edited: