[AI] How AI work

General Jass Discussion

Hello, moyackx.

Now, I am hoping to discuss thing about AI. I really don't know why so many are not interested in it.

Anyways, you were saying something about building up some kind of table, does that mean I can't command the AI to build a certain building, lets say 2 Ancient of War then later increase them to 4 ? because I make something like that and it worked:

Code (jass) Select
function main takes nothing returns nothing
    call CampaignAI( MOON_WELL, function hero_levels )
    call SetWoodPeons(4)
    //call AttackMoveXY(-6500,-6500) // Just a signal to detect if the AI is working or not
        //call PurchaseZeppelin()
   
    call SetBuildUnitEx( 9, 9, 9, WISP         )
    call SetBuildUnitEx( 1, 1, 1, TREE_LIFE    )

    call SetBuildUnitEx( 1, 1, 1, ANCIENT_WAR  )
    call SetBuildUnitEx( 1, 1, 1, ELF_ALTAR    )
    call SetBuildUnitEx( 1, 1, 1, HUNTERS_HALL )
    call SetBuildUnitEx( 2, 2, 2, ANCIENT_WAR  )
    call SetBuildUnitEx( 2, 2, 2, ELF_ALTAR    )
    call SetBuildUnitEx( 2, 2, 2, HUNTERS_HALL )
    call SetBuildUnitEx( 4, 4, 4, ANCIENT_WAR  )
endfunction


That's right. If you check the code behind [lcode=jass]SetBuildUnitEx()[/lcode] you'll see the following:

Code (jass) Select
function SetBuildUnitEx takes integer easy, integer med, integer hard, integer unitid returns nothing
    if difficulty == EASY then
        call SetBuildAll(BUILD_UNIT,easy,unitid,-1)
    elseif difficulty == NORMAL then
        call SetBuildAll(BUILD_UNIT,med,unitid,-1)
    else
        call SetBuildAll(BUILD_UNIT,hard,unitid,-1)
    endif
endfunction


And [lcode=jass]SetBuildAll()[/lcode] does this:

Code (jass) Select
function SetBuildAll takes integer t, integer qty, integer unitid, integer town returns nothing
    if qty > 0 then
        set build_qty[build_length] = qty
        set build_type[build_length] = t
        set build_item[build_length] = unitid
        set build_town[build_length] = town
        set build_length = build_length + 1
    endif
endfunction


In conclusion these functions actually are not giving orders directly, instead they fill a table that the AI internally will be checking and following, the dynamic of this functions is seen in the fact that we add or remove priorities (orders) according to situations.

I've been trying to develop a super AI but the big issue has been this feature, if you change dramatically the table between every [lcode=jass]Sleep()[/lcode], the AI will get unresponsive or arbitrary and it will fail.
So that concludes that campaign AI are critically stable and fragile and special care should be taken when making one.

I have lots of question about AI but lets not put them in one post, most importantly, can I use common.ai defined globals in my triggers to track their values as it seems that the function I2S don't work in AI files.
Quote from: Starquizer on January 14, 2013, 06:38:33 AM
So that concludes that campaign AI are critically stable and fragile and special care should be taken when making one.
And not only campaign AI, melee AI is affected too because they use both procedures.

QuoteI have lots of question about AI but lets not put them in one post, most importantly, can I use common.ai defined globals in my triggers to track their values as it seems that the function I2S don't work in AI files.
Unfortunately not, because these globals are only valid per AI player. But you can get integers from triggers using the Commands functions.
Quote from: moyack on January 14, 2013, 08:16:41 AM
But you can get integers from triggers using the Commands functions.

I made a trigger to send AI signal command to stop an attack when the enemy is destroyed to learn how to use commands and I did something like that but it doesn't work and I don't get the message.

Code (jass) Select
globals
    player MyVictim = Player(2) // Player 3 (Teal)
endglobals

function hero_levels takes nothing returns integer
    local integer hero  = GetHeroId()
    local integer level = GetHeroLevelAI()
    local integer a = 0
    if hero == DEMON_HUNTER then
        if level == 1 or level == 3 or level == 5 then
            set a = IMMOLATION
        endif
        if level == 2 or level == 4 or level == 7 then
            set a = EVASION
        endif
        if level >= 8 then
            set a = MANA_BURN
        endif
        if level == 6 then
            set a = METAMORPHOSIS
        endif
    endif
    if hero == MOON_CHICK then
        if level == 1 or level == 3 or level == 5 then
            set a = TRUESHOT
        endif
        if level == 2 or level == 4 or level == 7 then
            set a = SEARING_ARROWS
        endif
        if level >= 8 then
            set a = SCOUT
        endif
        if level == 6 then
            set a = STARFALL
        endif
    endif
    return a
endfunction

function stopAttack takes nothing returns boolean
    local integer cmd = GetLastCommand()
    call PopLastCommand()
    if cmd == 2 then
        return true
    endif
    return false
endfunction

function main takes nothing returns nothing
    call CampaignAI( MOON_WELL, function hero_levels )
    call DisplayTextToPlayer(Player(0),0,0,"AI Started")
    call SetBuildUnitEx( 1, 1, 1, TREE_LIFE    )
    call SetBuildUnitEx( 8, 8, 8, ANCIENT_WAR  )
    call SetBuildUnitEx( 2, 2, 2, ELF_ALTAR    )
    call SetBuildUnitEx( 1, 1, 1, HUNTERS_HALL )
    //•••••••••••••••••••••••••••••••••••••••••
    //•               DEFENDERS               •
    //•••••••••••••••••••••••••••••••••••••••••
    call CampaignDefenderEx( 1, 1, 1, MOON_CHICK )
    call CampaignDefenderEx( 10, 10, 10, ARCHER     )
    call CampaignDefenderEx( 10, 10, 10, HUNTRESS   )
    //call Sleep(10)   
    //•     WAVE #5+    •\\
    loop
        call InitAssaultGroup()
        call CampaignAttackerEx(1,1,1,MOON_CHICK)
        call CampaignAttackerEx(10,10,10,ARCHER)
        call CampaignAttackerEx(10,10,10,HUNTRESS)
        call CampaignAttackerEx(4,4,4,BALLISTA)
        call SuicideOnPlayerEx(10,10,10,MyVictim)
        exitwhen stopAttack()
    endloop
    call DisplayTextToPlayer(Player(0),0,0,"AI Ended")
endfunction


and the trigger that send the command is like this:

[trigger]Undead Destroyed AI Signal
    Events
        Time - Every 1.00 seconds of game time
    Conditions
    Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            If - Conditions
                (All units of (Units in (Playable map area) owned by Player 3 (Teal)) are dead) Equal to True
            Then - Actions
                Trigger - Turn off (This trigger)
                Game - Display to (All players) the text: All Undead Destroye...
                AI - Send Player 2 (Blue) the AI Command (2, 0)
            Else - Actions[/trigger]

The group is created don't worry about that and I get "All Undead Destroyed." but I don't get "AI Ended" which mean that we are still in the loop.

So what's wrong with my code ?
While you where away I messed alot with AI function until I made a strange script,

Code (jass) Select

globals
    player MyVictim = Player(2) // Player 3 (Teal)
endglobals

function hero_levels takes nothing returns integer
    local integer hero  = GetHeroId()
    local integer level = GetHeroLevelAI()
    local integer a = 0
    if hero == DEMON_HUNTER then
        if level == 1 or level == 3 or level == 5 then
            set a = IMMOLATION
        endif
        if level == 2 or level == 4 or level == 7 then
            set a = EVASION
        endif
        if level >= 8 then
            set a = MANA_BURN
        endif
        if level == 6 then
            set a = METAMORPHOSIS
        endif
    endif
    if hero == MOON_CHICK then
        if level == 1 or level == 3 or level == 5 then
            set a = TRUESHOT
        endif
        if level == 2 or level == 4 or level == 7 then
            set a = SEARING_ARROWS
        endif
        if level >= 8 then
            set a = SCOUT
        endif
        if level == 6 then
            set a = STARFALL
        endif
    endif
    return a
endfunction

function stopAttack takes nothing returns nothing
    local integer cmd
    //loop
    call DisplayTextToPlayer(Player(0),0,0,"Waiting for Signal")
        set cmd = WaitForSignal()
        call DisplayTextToPlayer(Player(0),0,0,"Signal Reieved")
        if cmd == 2 then
            call DisplayTextToPlayer(Player(0),0,0,"Stop the attack")
            set allow_signal_abort = TRUE
            set sleep_seconds = -300
        endif
    //endloop
endfunction

function main takes nothing returns nothing
    call CampaignAI( MOON_WELL, function hero_levels )
    call DisplayTextToPlayer(Player(0),0,0,"AI Started")
    debug call Trace("DEBUG MODE")
    call SetBuildUnitEx( 1, 1, 1, TREE_LIFE    )
    call SetBuildUnitEx( 1, 1, 1, TREE_AGES    )
    call SetBuildUnitEx( 1, 1, 1, TREE_ETERNITY)
    call SetBuildUnitEx( 8, 8, 8, ANCIENT_WAR  )
    call SetBuildUnitEx( 2, 2, 2, ELF_ALTAR    )
    call SetBuildUnitEx( 2, 2, 2, HUNTERS_HALL )
    //•••••••••••••••••••••••••••••••••••••••••
    //•               DEFENDERS               •
    //•••••••••••••••••••••••••••••••••••••••••
    call CampaignDefenderEx( 1, 1, 1, MOON_CHICK )
    call CampaignDefenderEx( 10, 10, 10, ARCHER     )
    call CampaignDefenderEx( 10, 10, 10, HUNTRESS   )   
    call StartThread(function stopAttack)
    //call stopAttack()
   
    //•     WAVE #5+    •\\
    loop
        call DisplayTextToPlayer(Player(0),0,0,"Prepairing an attack wave")
        //call InitAssaultGroup()
        set harass_length = 0
        call CampaignAttackerEx(1,1,1,MOON_CHICK)
        call CampaignAttackerEx(10,10,10,ARCHER)
        call CampaignAttackerEx(10,10,10,HUNTRESS)
        call CampaignAttackerEx(4,4,4,BALLISTA)
        call SuicideOnPlayerEx(10,10,10,MyVictim)
        exitwhen allow_signal_abort
    endloop
    call DisplayTextToPlayer(Player(0),0,0,"AI Ended")
endfunction


The AI ended with the destruction of the enemy but I am not sure of the method as I am using a function to start another thread (line 70); is this function is ok to use more than one time (as it is already used by the function CampaignAI) because with it I could run more than loop at a time but it may bug another AIs if used so is it safe to use it.
The trick is not using too much the function [lcode]StartThread()[/lcode], it puts too much load to the system. AIs don't exceed from 3 threads at the same time.
Quote from: moyack on January 15, 2013, 05:31:11 AM
The trick is not using too much the function [lcode]StartThread()[/lcode], it puts too much load to the system. AIs don't exceed from 3 threads at the same time.

I have been busy all yesterday running lots of tests and found something very useful, which is the global variable: [lcode]allow_signal_abort[/lcode], it is somehow similar to the way you use [lcode]bj_wantDestroyGroup[/lcode].

I have modified the script so that I no longer use another thread.

Code (jass) Select

globals
    player MyVictim = Player(2) // Player 3 (Teal)
endglobals

function hero_levels takes nothing returns integer
    local integer hero  = GetHeroId()
    local integer level = GetHeroLevelAI()
    local integer a = 0
    if hero == DEMON_HUNTER then
        if level == 1 or level == 3 or level == 5 then
            set a = IMMOLATION
        endif
        if level == 2 or level == 4 or level == 7 then
            set a = EVASION
        endif
        if level >= 8 then
            set a = MANA_BURN
        endif
        if level == 6 then
            set a = METAMORPHOSIS
        endif
    endif
    if hero == MOON_CHICK then
        if level == 1 or level == 3 or level == 5 then
            set a = TRUESHOT
        endif
        if level == 2 or level == 4 or level == 7 then
            set a = SEARING_ARROWS
        endif
        if level >= 8 then
            set a = SCOUT
        endif
        if level == 6 then
            set a = STARFALL
        endif
    endif
    return a
endfunction

function main takes nothing returns nothing
    call CampaignAI( MOON_WELL, function hero_levels )
    call DisplayTextToPlayer(Player(0),0,0,"AI Started")
    call SetBuildUnitEx( 1, 1, 1, TREE_LIFE    )
    call SetBuildUnitEx( 1, 1, 1, TREE_AGES    )
    call SetBuildUnitEx( 1, 1, 1, TREE_ETERNITY)
    call SetBuildUnitEx( 8, 8, 8, ANCIENT_WAR  )
    call SetBuildUnitEx( 2, 2, 2, ELF_ALTAR    )
    call SetBuildUnitEx( 2, 2, 2, HUNTERS_HALL )
    //•••••••••••••••••••••••••••••••••••••••••
    //•               DEFENDERS               •
    //•••••••••••••••••••••••••••••••••••••••••
    call CampaignDefenderEx( 1, 1, 1, MOON_CHICK )
    call CampaignDefenderEx( 10, 10, 10, ARCHER     )
    call CampaignDefenderEx( 10, 10, 10, HUNTRESS   )   

    //•     WAVE #5+    •\\
    set allow_signal_abort=true
    loop
        call DisplayTextToPlayer(Player(0),0,0,"Prepairing an attack wave")
        call InitAssaultGroup()
        call CampaignAttackerEx(1,1,1,MOON_CHICK)
        call CampaignAttackerEx(10,10,10,ARCHER)
        call CampaignAttackerEx(10,10,10,HUNTRESS)
        call CampaignAttackerEx(4,4,4,BALLISTA)
        call SuicideOnPlayerEx(10,10,10,MyVictim)
        exitwhen CommandsWaiting() != 0
    endloop
    call PopLastCommand()
    set allow_signal_abort = false
    call DisplayTextToPlayer(Player(0),0,0,"AI Ended")
endfunction


Now I no longer need another thread, just send the signal and the AI stop. And also with this boolean you could exit most of the sleep functions.

Now, I am planning to make an AI manual to describe what each native do and if it work or not but that will take lots of work and time but I think with your aid things might go a bit faster.
Ok, I have been testing the functions for 2 day now and everything that is weird is becoming even weirder. So far I unlocked 10 natives and how to use them and their uses.
Hey!!! that's awesome!!! I'm feeling like extending the jass highlighthing to common.si natives and functions, so we can develop a documentation about them.

From my part, I'm working in a risky enterprise :P : I'm deleoping a kind of "AI system" where you can easily define a strategy. You just have to Define the unit requirements and you just have to writ something like this:

First, you define the unit requirements at code init:
Code (jass) Select
call CreateUnitDef( 'nwgs', 2, 'nnsa', 0     , 0, 0) // COUALT
    call CreateUnitDef( 'nnmg', 1, T[1]  , 0     , 0, 0) // MURGUL REAVER
    call CreateUnitDef( 'nnsw', 2, 'nnsa', 0     , 0, 0) // NAGA SIREN
    call CreateUnitDef( 'nsnp', 1, 'nnsg', 0     , 0, 0) // SNAP DRAGON
    call CreateUnitDef( 'nmyr', 3, 'nnsg', 0     , 0, 0) // NAGA MYRMIDON
    call CreateUnitDef( 'nhyc', 2, 'nnsg', 0     , 0, 0) // DRAGON TURTLE
    call CreateUnitDef( WORK  , 1, T[1],   0     , 0, 0) // MURGUL SLAVE
    call CreateUnitDef( 'n00L', 3, 'n01B', 'n000', 0, 0) // NAGA HYDRA
    call CreateUnitDef( 'n00K', 2, 'nnsa', 0     , 0, 0) // MURGUL WIZARD
    call CreateUnitDef( 'n002', 1, 'n01B', 0     , 0, 0) // NAGA ARTIFACTER
    call CreateUnitDef( 'n00J', 2, 'n01B', 0     , 0, 0) // TRANSPORT TURTLE
    call CreateUnitDef( hero_id, 1, 'nnad', 0    , 0, 0) // HERO 1
    call CreateUnitDef( hero_id2, 2, 'nnad', 0   , 0, 0) // HERO 2
    call CreateUnitDef( hero_id3, 3, 'nnad', 0   , 0, 0) // HERO 3
    //NAGA BUILDINGS
    call CreateUnitDef( 'nnad', 1, 0, 0, 0, 0) // ALTAR OF DEPTHS
    call CreateUnitDef( FARM  , 1, 0, 0, 0, 0) // CORAL BED
    call CreateUnitDef( 'nnsa', 2, 0, 0, 0, 0) // SHRINE OF AZSHARA
    call CreateUnitDef( 'nnsg', 1, 0, 0, 0, 0) // SPAWNING GROUNDS
    call CreateUnitDef( T[1], 1, 0, 0, 0, 0) // TEMPLE OF TIDES
    call CreateUnitDef( 'nntg', 1, 'n000', 0, 0, 0) // TIDAL GUARDIAN
    call CreateUnitDef( 'n000', 1, 0, 0, 0, 0) // RUINS OF RELICS
    call CreateUnitDef( T[2], 2, T[1], 0, 0, 0) // HIGH TEMPLE OF TIDES
    call CreateUnitDef( 'n01B', 1, 'n000', 0, 0, 0) // REPTILIAN
    call CreateUnitDef( T[3], 3, T[2], 0, 0, 0) // SUPREME TEMPLE OF TIDES


Then you define the training order like this:
Code (jass) Select
    call BuildUnit(1, 'nmpe', -1 )
    call BuildUnit(2, 'nmpe', -1 )
    call BuildUnit(3, 'nmpe', -1 )
    call BuildUnit(4, 'nmpe', -1 )
    call BuildUnit(5, 'nmpe', -1 )
    call BuildUnit(1, hero_id, -1 )
    call BuildUnit(6, 'nmpe', -1 )
    call BuildUnit(7, 'nmpe', -1 )
    call BuildUnit(1, 'nnmg', -1 )
    call BuildUnit(8, 'nmpe', -1 )
    call BuildUnit(2, 'nnmg', -1 )
    call BuildUnit(1, 'nsnp', -1 )
    call BuildUnit(9, 'nmpe', -1 )
    call BuildUnit(3, 'nnmg', -1 )
    call BuildUnit(4, 'nnmg', -1 )
    call BuildUnit(10, 'nmpe', -1 )
    call BuildUnit(1, 'n002', -1 )
    call BuildUnit(2, 'nsnp', -1 )
    call BuildUnit(2, 'n002', -1 )
    call BuildUnit(3, 'nsnp', -1 )


As you can see you only worry for setting the units, and the code will build all that it needs. Unfortunately I'm still having bad issues.
Do you know how town system work (-1: main town, 0: ??, 1: first expansion, 2:second, etc)

Also I don't know why this function is always returning zero, [lcode]GetTownUnitCount[/lcode] ?
Code (jass) Select
globals
    integer n = 0
endglobals

function main takes nothing returns nothing
    call DisplayTextToPlayer(Player(0),0,0,"start"+I2S(1))
    call CampaignAI(MOON_WELL,null)
    call Sleep(1)
    set n = GetTownUnitCount( ARCHER , 1 , true )
    call Sleep(0.1)
    if n == 0 then
        call DisplayTextToPlayer(Player(0),0,0,"0")
    elseif n == 1 then
        call DisplayTextToPlayer(Player(0),0,0,"1")
    elseif n == 2 then
        call DisplayTextToPlayer(Player(0),0,0,"2")
    elseif n == 3 then
        call DisplayTextToPlayer(Player(0),0,0,"3")
    elseif n == 4 then
        call DisplayTextToPlayer(Player(0),0,0,"4")
    else
        call DisplayTextToPlayer(Player(0),0,0,"5+")
    endif

endfunction
AS far as I understood, main is -1, next is 0, second is 1, etc... so  in theory you're asking with this function how many archers are in town 2, I think the issue is that this function is valid with buildings only.