[AI] How AI work

General Jass Discussion

13 years
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
1
function main takes nothing returns nothing
2
    call CampaignAI( MOON_WELL, function hero_levels )
3
    call SetWoodPeons(4)
4
    //call AttackMoveXY(-6500,-6500) // Just a signal to detect if the AI is working or not
5
        //call PurchaseZeppelin()
6
    
7
    call SetBuildUnitEx( 9, 9, 9, WISP         )
8
    call SetBuildUnitEx( 1, 1, 1, TREE_LIFE    )
9
10
    call SetBuildUnitEx( 1, 1, 1, ANCIENT_WAR  )
11
    call SetBuildUnitEx( 1, 1, 1, ELF_ALTAR    )
12
    call SetBuildUnitEx( 1, 1, 1, HUNTERS_HALL )
13
    call SetBuildUnitEx( 2, 2, 2, ANCIENT_WAR  )
14
    call SetBuildUnitEx( 2, 2, 2, ELF_ALTAR    )
15
    call SetBuildUnitEx( 2, 2, 2, HUNTERS_HALL )
16
    call SetBuildUnitEx( 4, 4, 4, ANCIENT_WAR  )
17
endfunction
18


13 years
That's right. If you check the code behind SetBuildUnitEx() you'll see the following:

Code (jass) Select
1
function SetBuildUnitEx takes integer easy, integer med, integer hard, integer unitid returns nothing
2
    if difficulty == EASY then
3
        call SetBuildAll(BUILD_UNIT,easy,unitid,-1)
4
    elseif difficulty == NORMAL then
5
        call SetBuildAll(BUILD_UNIT,med,unitid,-1)
6
    else
7
        call SetBuildAll(BUILD_UNIT,hard,unitid,-1)
8
    endif
9
endfunction


And SetBuildAll() does this:

Code (jass) Select
1
function SetBuildAll takes integer t, integer qty, integer unitid, integer town returns nothing
2
    if qty > 0 then
3
        set build_qty[build_length] = qty
4
        set build_type[build_length] = t
5
        set build_item[build_length] = unitid
6
        set build_town[build_length] = town
7
        set build_length = build_length + 1
8
    endif
9
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 Sleep(), the AI will get unresponsive or arbitrary and it will fail.
13 years
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.
13 years
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.
13 years
edited 13 years
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
1
globals
2
    player MyVictim = Player(2) // Player 3 (Teal)
3
endglobals
4
5
function hero_levels takes nothing returns integer
6
    local integer hero  = GetHeroId()
7
    local integer level = GetHeroLevelAI()
8
    local integer a = 0
9
    if hero == DEMON_HUNTER then
10
        if level == 1 or level == 3 or level == 5 then
11
            set a = IMMOLATION
12
        endif
13
        if level == 2 or level == 4 or level == 7 then
14
            set a = EVASION
15
        endif
16
        if level >= 8 then
17
            set a = MANA_BURN
18
        endif
19
        if level == 6 then
20
            set a = METAMORPHOSIS
21
        endif
22
    endif
23
    if hero == MOON_CHICK then
24
        if level == 1 or level == 3 or level == 5 then
25
            set a = TRUESHOT
26
        endif
27
        if level == 2 or level == 4 or level == 7 then
28
            set a = SEARING_ARROWS
29
        endif
30
        if level >= 8 then
31
            set a = SCOUT
32
        endif
33
        if level == 6 then
34
            set a = STARFALL
35
        endif
36
    endif
37
    return a
38
endfunction
39
40
function stopAttack takes nothing returns boolean
41
    local integer cmd = GetLastCommand()
42
    call PopLastCommand()
43
    if cmd == 2 then
44
        return true
45
    endif
46
    return false
47
endfunction
48
49
function main takes nothing returns nothing
50
    call CampaignAI( MOON_WELL, function hero_levels )
51
    call DisplayTextToPlayer(Player(0),0,0,"AI Started")
52
    call SetBuildUnitEx( 1, 1, 1, TREE_LIFE    )
53
    call SetBuildUnitEx( 8, 8, 8, ANCIENT_WAR  )
54
    call SetBuildUnitEx( 2, 2, 2, ELF_ALTAR    )
55
    call SetBuildUnitEx( 1, 1, 1, HUNTERS_HALL )
56
    //•••••••••••••••••••••••••••••••••••••••••
57
    //•               DEFENDERS               •
58
    //•••••••••••••••••••••••••••••••••••••••••
59
    call CampaignDefenderEx( 1, 1, 1, MOON_CHICK )
60
    call CampaignDefenderEx( 10, 10, 10, ARCHER     )
61
    call CampaignDefenderEx( 10, 10, 10, HUNTRESS   )
62
    //call Sleep(10)    
63
    //•     WAVE #5+    •\\
64
    loop
65
        call InitAssaultGroup()
66
        call CampaignAttackerEx(1,1,1,MOON_CHICK)
67
        call CampaignAttackerEx(10,10,10,ARCHER)
68
        call CampaignAttackerEx(10,10,10,HUNTRESS)
69
        call CampaignAttackerEx(4,4,4,BALLISTA)
70
        call SuicideOnPlayerEx(10,10,10,MyVictim)
71
        exitwhen stopAttack()
72
    endloop
73
    call DisplayTextToPlayer(Player(0),0,0,"AI Ended")
74
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 ?
13 years
While you where away I messed alot with AI function until I made a strange script,

Code (jass) Select
1
2
globals
3
    player MyVictim = Player(2) // Player 3 (Teal)
4
endglobals
5
6
function hero_levels takes nothing returns integer
7
    local integer hero  = GetHeroId()
8
    local integer level = GetHeroLevelAI()
9
    local integer a = 0
10
    if hero == DEMON_HUNTER then
11
        if level == 1 or level == 3 or level == 5 then
12
            set a = IMMOLATION
13
        endif
14
        if level == 2 or level == 4 or level == 7 then
15
            set a = EVASION
16
        endif
17
        if level >= 8 then
18
            set a = MANA_BURN
19
        endif
20
        if level == 6 then
21
            set a = METAMORPHOSIS
22
        endif
23
    endif
24
    if hero == MOON_CHICK then
25
        if level == 1 or level == 3 or level == 5 then
26
            set a = TRUESHOT
27
        endif
28
        if level == 2 or level == 4 or level == 7 then
29
            set a = SEARING_ARROWS
30
        endif
31
        if level >= 8 then
32
            set a = SCOUT
33
        endif
34
        if level == 6 then
35
            set a = STARFALL
36
        endif
37
    endif
38
    return a
39
endfunction
40
41
function stopAttack takes nothing returns nothing
42
    local integer cmd
43
    //loop
44
    call DisplayTextToPlayer(Player(0),0,0,"Waiting for Signal")
45
        set cmd = WaitForSignal()
46
        call DisplayTextToPlayer(Player(0),0,0,"Signal Reieved")
47
        if cmd == 2 then
48
            call DisplayTextToPlayer(Player(0),0,0,"Stop the attack")
49
            set allow_signal_abort = TRUE
50
            set sleep_seconds = -300
51
        endif
52
    //endloop
53
endfunction
54
55
function main takes nothing returns nothing
56
    call CampaignAI( MOON_WELL, function hero_levels )
57
    call DisplayTextToPlayer(Player(0),0,0,"AI Started")
58
    debug call Trace("DEBUG MODE")
59
    call SetBuildUnitEx( 1, 1, 1, TREE_LIFE    )
60
    call SetBuildUnitEx( 1, 1, 1, TREE_AGES    )
61
    call SetBuildUnitEx( 1, 1, 1, TREE_ETERNITY)
62
    call SetBuildUnitEx( 8, 8, 8, ANCIENT_WAR  )
63
    call SetBuildUnitEx( 2, 2, 2, ELF_ALTAR    )
64
    call SetBuildUnitEx( 2, 2, 2, HUNTERS_HALL )
65
    //•••••••••••••••••••••••••••••••••••••••••
66
    //•               DEFENDERS               •
67
    //•••••••••••••••••••••••••••••••••••••••••
68
    call CampaignDefenderEx( 1, 1, 1, MOON_CHICK )
69
    call CampaignDefenderEx( 10, 10, 10, ARCHER     )
70
    call CampaignDefenderEx( 10, 10, 10, HUNTRESS   )    
71
    call StartThread(function stopAttack)
72
    //call stopAttack()
73
    
74
    //•     WAVE #5+    •\\
75
    loop
76
        call DisplayTextToPlayer(Player(0),0,0,"Prepairing an attack wave")
77
        //call InitAssaultGroup()
78
        set harass_length = 0
79
        call CampaignAttackerEx(1,1,1,MOON_CHICK)
80
        call CampaignAttackerEx(10,10,10,ARCHER)
81
        call CampaignAttackerEx(10,10,10,HUNTRESS)
82
        call CampaignAttackerEx(4,4,4,BALLISTA)
83
        call SuicideOnPlayerEx(10,10,10,MyVictim)
84
        exitwhen allow_signal_abort
85
    endloop
86
    call DisplayTextToPlayer(Player(0),0,0,"AI Ended")
87
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.
13 years
The trick is not using too much the function StartThread(), it puts too much load to the system. AIs don't exceed from 3 threads at the same time.
13 years
Quote from: moyack on January 15, 2013, 05:31:11 AM
The trick is not using too much the function StartThread(), 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: allow_signal_abort, it is somehow similar to the way you use bj_wantDestroyGroup.

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

Code (jass) Select
1
2
globals
3
    player MyVictim = Player(2) // Player 3 (Teal)
4
endglobals
5
6
function hero_levels takes nothing returns integer
7
    local integer hero  = GetHeroId()
8
    local integer level = GetHeroLevelAI()
9
    local integer a = 0
10
    if hero == DEMON_HUNTER then
11
        if level == 1 or level == 3 or level == 5 then
12
            set a = IMMOLATION
13
        endif
14
        if level == 2 or level == 4 or level == 7 then
15
            set a = EVASION
16
        endif
17
        if level >= 8 then
18
            set a = MANA_BURN
19
        endif
20
        if level == 6 then
21
            set a = METAMORPHOSIS
22
        endif
23
    endif
24
    if hero == MOON_CHICK then
25
        if level == 1 or level == 3 or level == 5 then
26
            set a = TRUESHOT
27
        endif
28
        if level == 2 or level == 4 or level == 7 then
29
            set a = SEARING_ARROWS
30
        endif
31
        if level >= 8 then
32
            set a = SCOUT
33
        endif
34
        if level == 6 then
35
            set a = STARFALL
36
        endif
37
    endif
38
    return a
39
endfunction
40
41
function main takes nothing returns nothing
42
    call CampaignAI( MOON_WELL, function hero_levels )
43
    call DisplayTextToPlayer(Player(0),0,0,"AI Started")
44
    call SetBuildUnitEx( 1, 1, 1, TREE_LIFE    )
45
    call SetBuildUnitEx( 1, 1, 1, TREE_AGES    )
46
    call SetBuildUnitEx( 1, 1, 1, TREE_ETERNITY)
47
    call SetBuildUnitEx( 8, 8, 8, ANCIENT_WAR  )
48
    call SetBuildUnitEx( 2, 2, 2, ELF_ALTAR    )
49
    call SetBuildUnitEx( 2, 2, 2, HUNTERS_HALL )
50
    //•••••••••••••••••••••••••••••••••••••••••
51
    //•               DEFENDERS               •
52
    //•••••••••••••••••••••••••••••••••••••••••
53
    call CampaignDefenderEx( 1, 1, 1, MOON_CHICK )
54
    call CampaignDefenderEx( 10, 10, 10, ARCHER     )
55
    call CampaignDefenderEx( 10, 10, 10, HUNTRESS   )    
56
57
    //•     WAVE #5+    •\\
58
    set allow_signal_abort=true
59
    loop
60
        call DisplayTextToPlayer(Player(0),0,0,"Prepairing an attack wave")
61
        call InitAssaultGroup()
62
        call CampaignAttackerEx(1,1,1,MOON_CHICK)
63
        call CampaignAttackerEx(10,10,10,ARCHER)
64
        call CampaignAttackerEx(10,10,10,HUNTRESS)
65
        call CampaignAttackerEx(4,4,4,BALLISTA)
66
        call SuicideOnPlayerEx(10,10,10,MyVictim)
67
        exitwhen CommandsWaiting() != 0
68
    endloop
69
    call PopLastCommand()
70
    set allow_signal_abort = false
71
    call DisplayTextToPlayer(Player(0),0,0,"AI Ended")
72
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.
13 years
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.
13 years
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
1
call CreateUnitDef( 'nwgs', 2, 'nnsa', 0     , 0, 0) // COUALT
2
    call CreateUnitDef( 'nnmg', 1, T[1]  , 0     , 0, 0) // MURGUL REAVER
3
    call CreateUnitDef( 'nnsw', 2, 'nnsa', 0     , 0, 0) // NAGA SIREN
4
    call CreateUnitDef( 'nsnp', 1, 'nnsg', 0     , 0, 0) // SNAP DRAGON
5
    call CreateUnitDef( 'nmyr', 3, 'nnsg', 0     , 0, 0) // NAGA MYRMIDON
6
    call CreateUnitDef( 'nhyc', 2, 'nnsg', 0     , 0, 0) // DRAGON TURTLE
7
    call CreateUnitDef( WORK  , 1, T[1],   0     , 0, 0) // MURGUL SLAVE
8
    call CreateUnitDef( 'n00L', 3, 'n01B', 'n000', 0, 0) // NAGA HYDRA
9
    call CreateUnitDef( 'n00K', 2, 'nnsa', 0     , 0, 0) // MURGUL WIZARD
10
    call CreateUnitDef( 'n002', 1, 'n01B', 0     , 0, 0) // NAGA ARTIFACTER
11
    call CreateUnitDef( 'n00J', 2, 'n01B', 0     , 0, 0) // TRANSPORT TURTLE
12
    call CreateUnitDef( hero_id, 1, 'nnad', 0    , 0, 0) // HERO 1
13
    call CreateUnitDef( hero_id2, 2, 'nnad', 0   , 0, 0) // HERO 2
14
    call CreateUnitDef( hero_id3, 3, 'nnad', 0   , 0, 0) // HERO 3
15
    //NAGA BUILDINGS
16
    call CreateUnitDef( 'nnad', 1, 0, 0, 0, 0) // ALTAR OF DEPTHS
17
    call CreateUnitDef( FARM  , 1, 0, 0, 0, 0) // CORAL BED
18
    call CreateUnitDef( 'nnsa', 2, 0, 0, 0, 0) // SHRINE OF AZSHARA
19
    call CreateUnitDef( 'nnsg', 1, 0, 0, 0, 0) // SPAWNING GROUNDS
20
    call CreateUnitDef( T[1], 1, 0, 0, 0, 0) // TEMPLE OF TIDES
21
    call CreateUnitDef( 'nntg', 1, 'n000', 0, 0, 0) // TIDAL GUARDIAN
22
    call CreateUnitDef( 'n000', 1, 0, 0, 0, 0) // RUINS OF RELICS
23
    call CreateUnitDef( T[2], 2, T[1], 0, 0, 0) // HIGH TEMPLE OF TIDES
24
    call CreateUnitDef( 'n01B', 1, 'n000', 0, 0, 0) // REPTILIAN
25
    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
1
    call BuildUnit(1, 'nmpe', -1 )
2
    call BuildUnit(2, 'nmpe', -1 )
3
    call BuildUnit(3, 'nmpe', -1 )
4
    call BuildUnit(4, 'nmpe', -1 )
5
    call BuildUnit(5, 'nmpe', -1 )
6
    call BuildUnit(1, hero_id, -1 )
7
    call BuildUnit(6, 'nmpe', -1 )
8
    call BuildUnit(7, 'nmpe', -1 )
9
    call BuildUnit(1, 'nnmg', -1 )
10
    call BuildUnit(8, 'nmpe', -1 )
11
    call BuildUnit(2, 'nnmg', -1 )
12
    call BuildUnit(1, 'nsnp', -1 )
13
    call BuildUnit(9, 'nmpe', -1 )
14
    call BuildUnit(3, 'nnmg', -1 )
15
    call BuildUnit(4, 'nnmg', -1 )
16
    call BuildUnit(10, 'nmpe', -1 )
17
    call BuildUnit(1, 'n002', -1 )
18
    call BuildUnit(2, 'nsnp', -1 )
19
    call BuildUnit(2, 'n002', -1 )
20
    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.
13 years
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, GetTownUnitCount ?
Code (jass) Select
1
globals
2
    integer n = 0
3
endglobals
4
5
function main takes nothing returns nothing
6
    call DisplayTextToPlayer(Player(0),0,0,"start"+I2S(1))
7
    call CampaignAI(MOON_WELL,null)
8
    call Sleep(1)
9
    set n = GetTownUnitCount( ARCHER , 1 , true )
10
    call Sleep(0.1)
11
    if n == 0 then
12
        call DisplayTextToPlayer(Player(0),0,0,"0")
13
    elseif n == 1 then
14
        call DisplayTextToPlayer(Player(0),0,0,"1")
15
    elseif n == 2 then
16
        call DisplayTextToPlayer(Player(0),0,0,"2")
17
    elseif n == 3 then
18
        call DisplayTextToPlayer(Player(0),0,0,"3")
19
    elseif n == 4 then
20
        call DisplayTextToPlayer(Player(0),0,0,"4")
21
    else
22
        call DisplayTextToPlayer(Player(0),0,0,"5+")
23
    endif
24
25
endfunction
13 years
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.