• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[AI] Simple system for AI script debugging and passing multiple AI command arguments.

Status
Not open for further replies.
Level 5
Joined
Oct 27, 2007
Messages
158
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
  1. Send a great deal of data without having to worry about AI command timing issues.
  2. No hassle with multiple commands or checking for multiple AI data arguments in the AI script.
  3. Easier to do AI script debugging compared to setting user item/unit data, or setting another player's gold/lumber values.
  4. More detailed and smarter AI behaviour through triggers. Strategy for attacking, building, defending, upgrading etc.
Cons
  1. Very slow compared to just using multiple Command AI sequences to pass the data if you're using 50+ command sequences.
  2. Easy to mess up if you don't know what you're doing.
The cons don't outweigh the pros. The reason being that I haven't found any reason to send more than 10+ AI commands at a time. So the overhead is minimal. It provides a safe way of passing data to an AI.

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:
Status
Not open for further replies.
Top