• Check out the results of the Techtree Contest #19!
  • 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.
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!

Changing an AI Behavior

Level 7
Joined
Oct 7, 2024
Messages
25
So I'm working on this map in my campaign and at one point I want the player to gain control of an allied base (Teal player, all units are transferred to the player), but I am having trouble conceptualizing what to do with the AI. As is, the AI is programmed to attack Teal, but when I do the transfer, I want the attack patterns to be updated and for different wave formations, timings, and such to be on the user (Red). I thought of one solution of just changing ownership of the enemy AI players to a different player slot and starting a new AI script for that new slot (like change Green (7) to Light Blue (10) and write a unique script), but I feel like there is a better way directly in an AI JASS script. I'd imagine it would utilize WaitforSignal() in the AI script, but I'm not sure on the details. Any help would be appreciated!
 
This is how I handle it in my maps, first you run this function (cheers to a tutorial about campaign AI on the Hive)

JASS:
function ConditionLoop takes nothing returns nothing

    call ConditionState()
    call StaggerSleep(1,5)
    loop
        call ConditionState()
        call Sleep(2)
    endloop
 
endfunction

Then above, this other function that detects signals you will be sending via triggers

JASS:
function ConditionState takes nothing returns nothing

    if CommandsWaiting() > 0 then
        set target = GetLastCommand() // target is an integer global
        call PopLastCommand()
        call AttackTarget() // AttackTarget is yet another function
        set target = 0 // not 100% sure it's required but I've got it working that way so I don't touch it anymore lel
    endif
 
endfunction

And finally the function that will send the different waves

JASS:
function AttackTarget takes nothing returns nothing

    if target == 1 then
 
    call InitAssaultGroup()
    //your attack wave units here
  
    call SuicideOnPlayerEx(M2,M2,M2, Player(1))
   // you can add more attack waves

    elseif target == 2 then
     
    call InitAssaultGroup()
    //your attack wave units here
    call SuicideOnPlayerEx(M2,M2,M2, Player(2))
 
   // add other elseif for more targets if needed
    endif

endfunction

It's a handful, and might be easier to proceed like you described if that sounds confusing, but essentially it's all a matter of intercepting a command and using that number to trigger different stuff. Also with that example the functions from top to bottom should be AttackTarget > ConditionState > ConditionLoop > main, and inside the "main" you need "call StartThread(function ConditionLoop)" to get the whole system going
 
Just to make sure I'm getting this, at the start after I load the AI for the first time I would send an AI command (1,0) which would initiate the default behavior attacking the other AI. Then, after the transfer occurs, I send a new signal (2,0) to begin the new attacks? I think I've got the structure, hopefully it works lolol
 
I've tried a bunch of iterations but for some reason none of them actually succeeded in changing the wave system. Here's what my script is:

JASS:
globals
    player user = Player(0)
    player Byron = Player(2)
    integer Warlock = 'nw2w'
    integer Drake = 'nbzk'
    integer target = 0 
endglobals

// ATTACK SYSTEM
function AttackTarget takes nothing returns nothing
    if target == 1 then 
    //Waves to Byron
    call Sleep(15)
    call InitAssaultGroup()
    call CampaignAttackerEx(2, 2, 2, 'ogru')
    call CampaignAttackerEx(1, 1, 1, 'ohun')
    call CampaignAttackerEx(2, 2, 2, 'orai')
    call CampaignAttackerEx(1, 1, 1, Warlock)
    call SuicideOnPlayer(1, Byron)   

    loop
        call InitAssaultGroup()
        call CampaignAttackerEx(2, 2, 2, 'ogru')
        call CampaignAttackerEx(2, 2, 2, 'ohun')
        call CampaignAttackerEx(1, 1, 1, 'orai')
        call CampaignAttackerEx(2, 2, 2, Warlock)
        call SuicideOnPlayer(45, Byron)
        exitwhen target != 1
    
        call InitAssaultGroup()
        call CampaignAttackerEx(1, 1, 1, 'ogru')
        call CampaignAttackerEx(2, 2, 2, 'ohun')
        call CampaignAttackerEx(2, 2, 2, 'orai')
        call CampaignAttackerEx(1, 1, 1, Warlock)
        call SuicideOnPlayer(45, Byron)
        exitwhen target != 1
        
        call InitAssaultGroup()
        call CampaignAttackerEx(3, 3, 3, 'ogru')
        call CampaignAttackerEx(3, 3, 3, 'ohun')
        call CampaignAttackerEx(1, 1, 1, Warlock)
        call SuicideOnPlayer(45, Byron)
        exitwhen target != 1
        
        call InitAssaultGroup()
        call CampaignAttackerEx(2, 2, 2, 'ogru')
        call CampaignAttackerEx(1, 1, 1, 'ohun')
        call CampaignAttackerEx(2, 2, 2, 'orai')
        call CampaignAttackerEx(1, 1, 1, Warlock)
        call SuicideOnPlayer(45, Byron)
        exitwhen target != 1
    endloop
    call AttackTarget() // Double checks the proper wave is running

    elseif target == 2 then
    //Waves to Player
    call InitAssaultGroup()
    call CampaignAttackerEx(2, 2, 2, 'owyv')
    call CampaignAttackerEx(1, 1, 1, Drake)
    call SuicideOnPlayer(M2, user)     
    loop
      call InitAssaultGroup()
      call CampaignAttackerEx(2, 2, 2, 'ogru')
      call CampaignAttackerEx(2, 2, 2, 'ohun')
      call CampaignAttackerEx(1, 1, 1, 'orai')
      call CampaignAttackerEx(2, 2, 2, Warlock)
      call CampaignAttackerEx(1, 1, 1, Drake)
      call SuicideOnPlayer(M3, user)
      
      call InitAssaultGroup()
      call CampaignAttackerEx(3, 3, 3, 'owyv')
      call CampaignAttackerEx(2, 2, 2, 'orai')
      call CampaignAttackerEx(2, 2, 2, 'ohun')
      call SuicideOnPlayer(M4, user)  

      call InitAssaultGroup()
      call CampaignAttackerEx(1, 1, 1, 'owyv')
      call CampaignAttackerEx(3, 3, 3, Drake)
      call SuicideOnPlayer(M3, user)
      
      call InitAssaultGroup()
      call CampaignAttackerEx(3, 3, 3, 'ogru')
      call CampaignAttackerEx(1, 1, 1, 'ohun')
      call CampaignAttackerEx(2, 2, 2, Warlock)
      call CampaignAttackerEx(2, 2, 2, 'owyv')
      call SuicideOnPlayer(M4, user)
          
    endloop    
        
    endif
endfunction

// CONDITION STATE
function ConditionState takes nothing returns nothing
    if CommandsWaiting() > 0 then
        set target = GetLastCommand() // target is an integer global
        call PopLastCommand()
        call AttackTarget() // AttackTarget is yet another function
    endif 
endfunction

// CONDITION LOOP
function ConditionLoop takes nothing returns nothing
    call ConditionState()
    call StaggerSleep(1,5)
    loop
        call ConditionState()
        call Sleep(2)
    endloop 
endfunction


// MAIN FUNCTION
function main takes nothing returns nothing
    call CampaignAI('otrb', null)
    call SetReplacements(100, 100, 100)
    call SetPeonsRepair(true)
    call SetHeroesFlee(false)
    call SetGroupsFlee(false)
    call StartThread(function ConditionLoop)
    
    //Buildings
    call SetBuildUnit(3, 'opeo')    //Peon
    call SetBuildUnit(1, 'ogre')    //Great Hall
    call SetBuildUnit(1, 'obar')    //Barracks
    call SetBuildUnit(5, 'otrb')    //Burrow
    call SetBuildUnit(5, 'owtw')    //Watch Tower
    call SetBuildUnit(1, 'ostr')    //Stronghold
    call SetBuildUnit(1, 'osld')    //Spirit Lodge
    call SetBuildUnit(1, 'obea')    //Beastiary
    call SetBuildUnit(2, 'nzep')    //Zeppelins    
endfunction

My reasoning for the changes is that the loop would continue running indefinitely and override the new command given, so I added both the exitwhen and the recursive call. I've tried so many different small changes but it just isn't switching to the updated attack wave. I double checked and I am sending the command (2, 0). My best guess is that something is stopping ConditionState from running and providing the update, but I'm not familiar enough with the CommandsWaiting, GetLastCommand, or PopLastCommand to make a comment, though from what I do get it would work.
 
Yeah I think the problem is the loops, I recall having the same issue. Have you tested without loops at all? Do one attack wave for target 1, send the command, and one attack wave for target 2, just to see if the target change is registering. If so you might need to find a workaround without loops, e.g. every X seconds you send the command Y, which would be 1 at first then 2 after the transfer
 
I finally got it to work as I wanted. Here is what I have.

JASS:
globals
    player user = Player(0)
    player Byron = Player(2)
    integer Warlock = 'nw2w'
    integer Drake = 'nbzk'
    integer target = 0 
endglobals

// ATTACK SYSTEM
function AttackTarget takes nothing returns nothing
    //This is all encased in a loop so after hitting last wave, the cycle restarts
    loop
    
    if target == 1 then 
            call DisplayTextToPlayer(user, 0, 0, "Target = 1, cycle start")
             
            // Wave 1
            call InitAssaultGroup()
            call CampaignAttackerEx(2, 2, 2, 'ogru')
            call CampaignAttackerEx(1, 1, 1, 'ohun')
            call CampaignAttackerEx(2, 2, 2, 'orai')
            call CampaignAttackerEx(1, 1, 1, Warlock)
            call SuicideOnPlayer(15, Byron)
            
            // Wave 2 — only if target still 1
            if target == 1 then
                call InitAssaultGroup()
                call CampaignAttackerEx(2, 2, 2, 'ogru')
                call CampaignAttackerEx(2, 2, 2, 'ohun')
                call CampaignAttackerEx(1, 1, 1, 'orai')
                call CampaignAttackerEx(2, 2, 2, Warlock)
                call SuicideOnPlayer(45, Byron)
            endif
            
            if target == 1 then
                // Wave 3
                call InitAssaultGroup()
                call CampaignAttackerEx(1, 1, 1, 'ogru')
                call CampaignAttackerEx(2, 2, 2, 'ohun')
                call CampaignAttackerEx(2, 2, 2, 'orai')
                call CampaignAttackerEx(1, 1, 1, Warlock)
                call SuicideOnPlayer(45, Byron)
            endif
            
            if target == 1 then
                // Wave 4
                call InitAssaultGroup()
                call CampaignAttackerEx(3, 3, 3, 'ogru')
                call CampaignAttackerEx(3, 3, 3, 'ohun')
                call CampaignAttackerEx(1, 1, 1, Warlock)
                call SuicideOnPlayer(45, Byron)
            endif
            
            if target == 1 then
                // Wave 5
                call InitAssaultGroup()
                call CampaignAttackerEx(2, 2, 2, 'ogru')
                call CampaignAttackerEx(1, 1, 1, 'ohun')
                call CampaignAttackerEx(2, 2, 2, 'orai')
                call CampaignAttackerEx(1, 1, 1, Warlock)
                call SuicideOnPlayer(45, Byron)
            endif       
     

    elseif target == 2 then
    //Waves to Player
    call DisplayTextToPlayer(user, 0, 0, "Target = 2")
    call InitAssaultGroup()
    call CampaignAttackerEx(2, 2, 2, 'owyv')
    call CampaignAttackerEx(1, 1, 1, Drake)
    call SuicideOnPlayer(M3, user)
    
    loop
        call DisplayTextToPlayer(user, 0, 0, "Target = 2 Loop")
        call InitAssaultGroup()
        call CampaignAttackerEx(2, 2, 2, 'ogru')
        call CampaignAttackerEx(2, 2, 2, 'ohun')
        call CampaignAttackerEx(1, 1, 1, 'orai')
        call CampaignAttackerEx(2, 2, 2, Warlock)
        call CampaignAttackerEx(1, 1, 1, Drake)
        call SuicideOnPlayer(M3, user)
      
        call InitAssaultGroup()
        call CampaignAttackerEx(3, 3, 3, 'owyv')
        call CampaignAttackerEx(2, 2, 2, 'orai')
        call CampaignAttackerEx(2, 2, 2, 'ohun')
        call SuicideOnPlayer(M4, user)  

        call InitAssaultGroup()
        call CampaignAttackerEx(1, 1, 1, 'owyv')
        call CampaignAttackerEx(3, 3, 3, Drake)
        call SuicideOnPlayer(M3, user)
      
        call InitAssaultGroup()
        call CampaignAttackerEx(3, 3, 3, 'ogru')
        call CampaignAttackerEx(1, 1, 1, 'ohun')
        call CampaignAttackerEx(2, 2, 2, Warlock)
        call CampaignAttackerEx(2, 2, 2, 'owyv')
        call SuicideOnPlayer(M4, user) 
    endloop
    
    //For fringe cases where the new command hasn't been registered at the very start of the map loading
    else
    call Sleep(2)
       
    endif
    
    
    call DisplayTextToPlayer(user, 0, 0, "Restart Loop")
    endloop
endfunction

function ConditionState takes nothing returns nothing
    if CommandsWaiting() > 0 then
        call DisplayTextToPlayer(user, 0, 0, "Command received!")
        set target = GetLastCommand()
        call DisplayTextToPlayer(user, 0, 0, "Target =" + I2S(target))
        call PopLastCommand()
    endif 
endfunction

// CONDITION LOOP
function ConditionLoop takes nothing returns nothing
    call ConditionState()
    call StaggerSleep(1,5)
    loop
        call ConditionState()
        call Sleep(2)
    endloop 
endfunction


// MAIN FUNCTION
function main takes nothing returns nothing
    call CampaignAI('otrb', null)
    call SetReplacements(100, 100, 100)
    call SetPeonsRepair(true)
    call SetHeroesFlee(false)
    call SetGroupsFlee(false)
    
    //Buildings
    call SetBuildUnit(3, 'opeo')    //Peon
    call SetBuildUnit(1, 'ogre')    //Great Hall
    call SetBuildUnit(1, 'obar')    //Barracks
    call SetBuildUnit(5, 'otrb')    //Burrow
    call SetBuildUnit(5, 'owtw')    //Watch Tower
    call SetBuildUnit(1, 'ostr')    //Stronghold
    call SetBuildUnit(1, 'osld')    //Spirit Lodge
    call SetBuildUnit(1, 'obea')    //Beastiary
    call SetBuildUnit(2, 'nzep')    //Zeppelins    

    call StartThread(function ConditionLoop)
    call StartThread(function AttackTarget)
endfunction

I don't end up resending new commands via trigger at all, just when I need to. The outside loop handles everything pretty well, and it works well for a one-way switch I have where I never need to worry about any other changes. Thanks for the help!
 
Back
Top