• 🏆 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!

How Does AI difficulty influence AI micro and unit controls?

Status
Not open for further replies.
Level 16
Joined
May 2, 2011
Messages
1,345
Hello,

I am wondering,

  1. Is insane and Hard difficulty the same? i.e. there are AI scripts having condition if difficulty = hard, does that make it happen if AI is set to insane?
  2. How does unit control differ if it was insane or hard? is insane behaviour same as hard in the campaign?
  3. in the campaign, how different does AI behave if he was set to hard? I already know that the code below:
    call CampaignAttackerEx ( 3,3,4, HEAD_HUNTER )
    but does that in hard it makes 4 head hunters, while in normal/easy it makes 3. but are there other side effects? e.g. targetting repairing peasnts, targetting heroes, etc. i.e. assume I have pulling all the codes and changed from the code above to:
    call CampaignAttackerEx ( 4,4,4, HEAD_HUNTER )
    will that make AI behave same way as if I selected hard? or will hard AI still do SOME things that normal AI do not. let's say I have done such edit, and I have put the AI in normal map, and set AI to insane difficulty, would that make AI behave exactly like a hard campaign AI? or are there different things from campaign Hard and melee insane in terms of unit control behaviour?
 
Level 21
Joined
Dec 4, 2007
Messages
1,473
I'm suspecting it doesn't alter tactics/counter measures or the likes, from what limited playing versus comps i did.
 
Level 12
Joined
Jun 15, 2016
Messages
472
1. Hard and insane difficulties are not the same. For the AI users can create and control, difficulty is just a number to refer to, as you can see if you go to the common.ai file:

JASS:
    constant integer EASY               = 1
    constant integer NORMAL             = 2
    constant integer HARD               = 3
    constant integer INSANE             = 4 // not used

This can also be seen inside the call CampaignAttackerEx ( 3,3,4, HEAD_HUNTER ) function:

JASS:
function CampaignAttackerEx takes integer easy, integer med, integer hard, integer unitid returns nothing
    if difficulty == EASY then
        call CampaignAttacker(EASY,easy,unitid)
    elseif difficulty == NORMAL then
        call CampaignAttacker(NORMAL,med,unitid)
    else
        call CampaignAttacker(HARD,hard,unitid)
    endif
endfunction

As you can see inside the functions, the if statement differs between easy, medium and other difficulties (hard and insane - without any difference between them).

On a different instance (and I can speak only for campaign AI here, I don't have melee AI experience), at the start of a campaign AI script you call this configuration function:

JASS:
function CampaignAI takes integer farms, code heroes returns nothing
    if GetGameDifficulty() == MAP_DIFFICULTY_EASY then
        set difficulty = EASY

        call SetTargetHeroes(false)
        call SetUnitsFlee(false)

    elseif GetGameDifficulty() == MAP_DIFFICULTY_NORMAL then
        set difficulty = NORMAL

        call SetTargetHeroes(false)
        call SetUnitsFlee(false)

    elseif GetGameDifficulty() == MAP_DIFFICULTY_HARD then
        set difficulty = HARD

        call SetPeonsRepair(true)
    else
        set difficulty = INSANE
    endif

    call InitAI()
    call InitBuildArray()
    call InitAssaultGroup()
    call CreateCaptains()

    call SetNewHeroes(false)
    if heroes != null then
        call SetHeroLevels(heroes)
    endif

    call SetHeroesFlee(false)
    call SetGroupsFlee(false)
    call SetSlowChopping(true)
    call GroupTimedLife(false)
    call SetCampaignAI()
    call Sleep(0.1)

    set racial_farm = farms
    call StartThread(function CampaignBasics)
    call StartBuildLoop()
endfunction

You'll note that right at the beginning, the script declares some behavior patterns like targeting heroes, repairing building, etc. You'll also note that it doesn't declare any behavior for insane difficulty.

To wrap up your first question: insane is not the same as hard difficulty. It is possible to use it, but it is not supported by the normal AI functions, so it will act pretty much like hard difficulty unless the user makes the proper script changes.

For your second question, I don't think the difficulty changes "unit control" (I assume you mean micro behavior by that). And you can check this very good tutorial on how the computer uses unit abilities.
 
Level 12
Joined
Jun 15, 2016
Messages
472
Could be, I'd check it out if I were you. Basically if the AI is the normal attack waves ai, your best way to tell is if the AI player rebuilds it's structures, because that works sorta like the attack ways:

JASS:
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
 
Level 16
Joined
May 2, 2011
Messages
1,345
@Nowow

there are 2 AI scripts in the chapter I am talking about. One only has attack waves and upgrades, and the other one does rebuild units always (weather on hard or normal)

//============================================================================
// Orc 3 -- light blue player -- AI Script
//============================================================================
globals
player user = Player(0)
endglobals

//============================================================================
// main
//============================================================================
function main takes nothing returns nothing
call CampaignAI(HOUSE,null)
call SetReplacements(1,1,3)

call SetBuildUnit( 8, PEASANT )

call CampaignDefenderEx( 1,1,2, FOOTMEN )
call CampaignDefenderEx( 1,1,1, PRIEST )
call CampaignDefenderEx( 0,0,1, PALADIN )

call WaitForSignal()

// *** WAVE 1 ***
call InitAssaultGroup()
call CampaignAttackerEx( 6,6,8, FOOTMAN )
call CampaignAttackerEx( 1,1,1, PALADIN )
call SuicideOnPlayer(0,user)

call SetBuildUpgrEx( 0,0,1, UPG_MELEE )
call SetBuildUpgrEx( 0,0,1, UPG_ARMOR )
call SetBuildUpgrEx( 0,0,2, UPG_MASONRY )
call SetBuildUpgrEx( 1,1,1, UPG_DEFEND )
call SetBuildUpgrEx( 0,0,1, UPG_PRAYING )

// *** WAVE 2 ***
call InitAssaultGroup()
call CampaignAttackerEx( 6,6,7, FOOTMAN )
call CampaignAttackerEx( 2,2,3, PRIEST )
call SuicideOnPlayerEx(M8,M8,M7,user)

// *** WAVE 3 ***
call InitAssaultGroup()
call CampaignAttackerEx( 6,6,8, FOOTMAN )
call CampaignAttackerEx( 1,1,1, PALADIN )
call SuicideOnPlayerEx(M8,M8,M7,user)

call SetBuildUpgrEx( 1,1,2, UPG_MELEE )
call SetBuildUpgrEx( 1,1,2, UPG_ARMOR )
call SetBuildUpgrEx( 1,1,2, UPG_PRAYING )

loop
// *** WAVE 4 ***
call InitAssaultGroup()
call CampaignAttackerEx( 6,6,8, FOOTMAN )
call CampaignAttackerEx( 2,2,2, PRIEST )
call CampaignAttackerEx( 1,1,1, PALADIN )
call SuicideOnPlayerEx(M8,M8,M7,user)

// *** WAVE 5 ***
call InitAssaultGroup()
call CampaignAttackerEx( 6,6,7, FOOTMAN )
call CampaignAttackerEx( 2,2,3, PRIEST )
call CampaignAttackerEx( 1,1,1, PALADIN )
call SuicideOnPlayerEx(M8,M8,M7,user)

endloop
endfunction

//============================================================================
// Orc 3 -- Grom Ally -- AI Script
//============================================================================
globals
constant integer GO_AGRO = 1 // no data
constant integer GO_KILL = 2 // no data
constant integer PLAYER_DIED = 3 // data = player ID
constant integer PLAYER_ASS = 4 // no data
constant integer CLEAR_AGRO = 5 // data = player ID

constant integer USER = 0
constant integer BLUE = 1
constant integer GRAY = 8
constant integer LIGHT_BLUE = 9
constant integer GREEN = 10

constant integer EASY_AGRO = 120
constant integer NORMAL_AGRO = 120
constant integer HARD_AGRO = 120

integer grom_target = -1
integer wave_index = 0
integer strength = 1
boolean agro_mode = true

boolean array alive
boolean array needs_agro
endglobals

//============================================================================
// set_build_units
//============================================================================
function set_build_units takes boolean fplayer returns nothing
if not fplayer then
call SetBuildUnit( 1, PEON )
call SetBuildUnit( 1, GREAT_HALL )
call SetBuildUnit( 1, ORC_BARRACKS )
call SetBuildUnit( 1, STRONGHOLD )
call SetBuildUnit( 1, ORC_ALTAR )
call SetBuildUnit( 1, FORGE )
call SetBuildUnit( 1, BESTIARY )
call SetBuildUnit( 7, PEON )
else
call SetBuildUnit( 2, ORC_BARRACKS )
call SetBuildUnit( 2, BESTIARY )
call SetBuildUnit( 4, ORC_WATCH_TOWER )
endif
endfunction

//============================================================================
// set_defenders
//============================================================================
function set_defenders takes boolean fplayer returns nothing
if not fplayer then
call CampaignDefenderEx( 1,1,1, GROM )
call CampaignDefenderEx( 2,2,2, GRUNT )
call CampaignDefenderEx( 2,2,2, HEAD_HUNTER )
call CampaignDefenderEx( 4,4,4, RAIDER )
else
call CampaignDefenderEx( 2,2,2, GRUNT )
call CampaignDefenderEx( 1,1,1, HEAD_HUNTER )
call CampaignDefenderEx( 1,1,2, RAIDER )
endif
endfunction

//============================================================================
// assault_wave
//============================================================================
function assault_wave takes nothing returns nothing
//------------------------------------------------------------------------
if grom_target == USER then
//------------------------------------------------------------------------
call CampaignAttackerEx ( 3,3,4, GRUNT )
call CampaignAttackerEx ( 3,3,4, HEAD_HUNTER )
call CampaignAttackerEx ( 1,1,2, CATAPULT )
call CampaignAttackerEx ( 2,2,4, RAIDER )

call SuicideOnPlayer(M5,Player(grom_target))

//------------------------------------------------------------------------
elseif strength == 1 then
//------------------------------------------------------------------------
call CampaignAttackerEx ( 4,4,5, GRUNT )

call SuicideOnPlayer(M5,Player(grom_target))
set strength = 2

//------------------------------------------------------------------------
elseif strength == 2 then
//------------------------------------------------------------------------
call CampaignAttackerEx ( 3,3,4, GRUNT )
call CampaignAttackerEx ( 2,2,2, HEAD_HUNTER )

call SuicideOnPlayer(M5,Player(grom_target))
set strength = 3

//------------------------------------------------------------------------
else // strength >= 3
//------------------------------------------------------------------------
call CampaignAttackerEx ( 3,3,4, RAIDER )
call CampaignAttackerEx ( 2,2,2, HEAD_HUNTER )

call SuicideOnPlayer(M5,Player(grom_target))
set strength = 1
endif
endfunction

//============================================================================
// agro_wave
//============================================================================
function agro_wave takes nothing returns nothing
//------------------------------------------------------------------------
if strength==1 then
//------------------------------------------------------------------------
call CampaignAttackerEx ( 4,4,5, GRUNT )

call SuicideOnPlayer(0,Player(grom_target))

//------------------------------------------------------------------------
elseif strength==2 then
//------------------------------------------------------------------------
call CampaignAttackerEx ( 3,3,4, GRUNT )
call CampaignAttackerEx ( 3,3,4, HEAD_HUNTER )

call SuicideOnPlayer(M3,Player(grom_target))

//------------------------------------------------------------------------
else // strength >= 3
//------------------------------------------------------------------------
call CampaignAttackerEx ( 3,3,4, GRUNT )
call CampaignAttackerEx ( 3,3,4, HEAD_HUNTER )
call CampaignAttackerEx ( 3,3,4, RAIDER )

call SuicideOnPlayer(M3,Player(grom_target))
endif
endfunction

//============================================================================
// init_arrays
//============================================================================
function init_arrays takes nothing returns nothing
local integer index = 0
loop
set alive [index] = false
set needs_agro [index] = false

set index = index + 1
exitwhen index == 11
endloop

set alive [ BLUE ] = true
set alive [ GRAY ] = true
set alive [ LIGHT_BLUE ] = true
set alive [ GREEN ] = true

set needs_agro [ BLUE ] = true
set needs_agro [ GRAY ] = true
set needs_agro [ LIGHT_BLUE ] = true
set needs_agro [ GREEN ] = true
endfunction

//============================================================================
// wait_for_start
//============================================================================
function wait_for_start takes nothing returns nothing
loop
call Trace("waiting for first command...\n")
exitwhen CommandsWaiting() != 0
call Sleep(5)
endloop
call TraceI("...first command (%d) received.\n",GetLastCommand())
endfunction

//============================================================================
// possible_agro
//============================================================================
function possible_agro takes integer target returns nothing
if grom_target == -1 and alive[target] and needs_agro[target] then
set grom_target = target
set needs_agro[target] = false
call TraceI("NOTICE: SET NEEDS_AGRO[%d] = FALSE\n",target)
endif
endfunction

//============================================================================
// next_alive
//============================================================================
function next_alive takes nothing returns nothing
loop
set grom_target = wave_index
set wave_index = wave_index + 1

if wave_index == 11 then
set wave_index = 0
call Sleep(1)
endif

exitwhen alive[grom_target]
endloop
call TraceI("Grom setting normal attack wave target = %d\n",grom_target)
endfunction

//============================================================================
// go_agro
//============================================================================
function go_agro takes nothing returns nothing
if grom_target != -1 then

call Trace("Grom successful, sleeping for a while\n")

if difficulty==EASY then
call Sleep(EASY_AGRO)
elseif difficulty==NORMAL then
call Sleep(NORMAL_AGRO)
else
call Sleep(HARD_AGRO)
endif

set grom_target = -1
set strength = 1
endif

call possible_agro( BLUE )
call possible_agro( GRAY )
call possible_agro( LIGHT_BLUE )
call possible_agro( GREEN )

call TraceI("changing agro target to %d\n",grom_target)
endfunction

//============================================================================
// process_commands
//============================================================================
function process_commands takes nothing returns nothing
local integer cmd
local integer data
loop
exitwhen CommandsWaiting() == 0
set cmd = GetLastCommand()
set data = GetLastData()
call PopLastCommand()

call TraceI("COMMAND = %d\n",cmd)
call TraceI("DATA = %d\n",data)

//====================================================================
if cmd == GO_AGRO then
//====================================================================
call go_agro()

//====================================================================
elseif cmd == GO_KILL then
//====================================================================
call Trace("agro waves complete, starting assault waves\n")

set agro_mode = false
set strength = 1

//====================================================================
elseif cmd == PLAYER_DIED then
//====================================================================
call TraceI("NOTICE: TOWN %d JUST DIED\n",data)

set alive[data] = false

//====================================================================
elseif cmd == PLAYER_ASS then
//====================================================================
call Trace("player gonna get punished now!\n")

call set_build_units(true)
call set_defenders(true)

set alive[USER] = true // ha ha!
set wave_index = USER

//====================================================================
elseif cmd == CLEAR_AGRO then
//====================================================================
if data == grom_target then
call TraceI("player agro'd Grom's target (%d) first\n",data)
call go_agro()
else
call TraceI("player agro'd %d (not Grom's current target)\n",data)
endif

call TraceI("NOTICE: SET NEEDS_AGRO[%d] = FALSE\n",data)

set needs_agro[data] = false

//====================================================================
else // UNKNOWN COMMAND
//====================================================================
call TraceI("WARNING: UNKNOWN COMMAND (%d)\n",cmd)
endif
endloop
endfunction

//============================================================================
// agro_loop
//============================================================================
function agro_loop takes nothing returns nothing
loop
call process_commands()
exitwhen not agro_mode

if grom_target == -1 then
call Trace("ERROR: Grom has no agro target!\n")
return
endif

call InitAssaultGroup()
call CampaignAttacker( EASY, 1, GROM )
call agro_wave()

set strength = strength + 1
endloop
endfunction

//============================================================================
// wave_loop
//============================================================================
function wave_loop takes nothing returns nothing
loop
call process_commands()
call next_alive()
call InitAssaultGroup()
call CampaignAttacker( EASY, 1, GROM )
call assault_wave()
endloop
endfunction

//============================================================================
// main
//============================================================================
function main takes nothing returns nothing
call CampaignAI(BURROW,null)

call init_arrays()
call set_build_units(false)
call set_defenders(false)

call wait_for_start()
call agro_loop()
call wave_loop()
endfunction
 
Level 12
Joined
Jun 15, 2016
Messages
472
In both scripts you can see that the building part is different. For example in the waver:


There is no building part. The script only sets defenders and attackers.


On the other hand, the building part for builder script looks like this:


JASS:
if not fplayer then
call SetBuildUnit( 1, PEON )
call SetBuildUnit( 1, GREAT_HALL )
call SetBuildUnit( 1, ORC_BARRACKS )
call SetBuildUnit( 1, STRONGHOLD )
call SetBuildUnit( 1, ORC_ALTAR )
call SetBuildUnit( 1, FORGE )
call SetBuildUnit( 1, BESTIARY )
call SetBuildUnit( 7, PEON )
else
call SetBuildUnit( 2, ORC_BARRACKS )
call SetBuildUnit( 2, BESTIARY )
call SetBuildUnit( 4, ORC_WATCH_TOWER )
endif


It uses the function SetBuildUnit, which tells the AI player to build and rebuild in any difficulty. Compare that to SetBuildUnitEx as in my previous post to see the difference.
 
Level 12
Joined
Jun 15, 2016
Messages
472
That is correct. The reason the waver only attacks is because he has no instructions to build any structures in the AI script. The reason the builder always builds is because the instructions in his AI script tell him to build no matter the difficulty.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
Higher AI levels might also have some more advanced tactical AI flags enabled. This is the case with SC2 where they will focus spawners rather than spawned units and repairing workers rather than the unit being repaired. Not sure if WC3 has this though, but I would hope it does.
 
Level 16
Joined
May 2, 2011
Messages
1,345
Higher AI levels might also have some more advanced tactical AI flags enabled. This is the case with SC2 where they will focus spawners rather than spawned units and repairing workers rather than the unit being repaired. Not sure if WC3 has this though, but I would hope it does.
AI does attack workers in campaign in WC3, but Melee AI I never noticed anything like that
 
Status
Not open for further replies.
Top