• 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.

AI Engage System...

Status
Not open for further replies.
I made an engage system for AI hero or unit, I need some testers and suggestions before I put this in spell's section...

This AI system will assign ANY moving unit to attack enemies in range...

Read the instructions carefully...

JASS:
//System Name: EngageSystem
//Made by: Mckill2009

//===FEATURES:
//- This system will order the AI to engage and search for enemies
//- Allows pick up item or custom item type
//- Allows target random enemy units include creeps
//- Allows follow user hero option
//- Retreat away from attacker or to building when HP goes down
//- Allows to learn/pick custom skill (manually set it up below)
//- Allows target refresh when target dies or duration runs out

//===HOW TO USE:
//- Make a new trigger and convert to custom text via EDIT >>> CONVERT CUSTOM TEXT
//- Copy ALL that is written here (overwrite the existing texts in the trigger)

//===REQUIRES:
//- Jass New Gen Pack (JNGP) by Vexorian

library EngageSystem initializer init

globals
    //===NON-CONFIGURABLES: Meaning, do not touch this!
    private constant hashtable                        HASH = InitHashtable()
    private constant integer            FOUNTAIN_OF_HEALTH = 'nfoh'
    //private constant integer              FOUNTAIN_OF_MANA = 'nmoo' //Reserved
    //private constant integer                          STOP = 851972
    private constant integer                        ATTACK = 851983
    private constant integer                         SMART = 851971
    //private constant integer                 ATTACK_GROUND = 851984
    private constant integer                          MOVE = 851986
    private real CENTER_X
    private real CENTER_Y
    private integer array ITEM_TYPE
    private integer array SKILL
    private trigger RETREAT_TRIGGER
    private rect R
    private unit UNIT_RETREATING
    private unit UNIT_DATA
    private location SEARCH_LOCATION
endglobals
    
globals
    //===CONFIGURABLES:
    private constant real                       AOE_FOLLOW = 4000
    private constant real                    AOE_ITEM_PICK = 500
    private constant real                AOE_TARGET_RANDOM = 5000
    private constant real              AOE_TARGET_BUILDING = 6000
    private constant real                AOE_TARGET_SEARCH = 50000 //This should be big
    private constant real                 AOE_TARGET_CREEP = 6000
    private constant real          AOE_RETREAT_TO_BUILDING = 8000
    private constant real                 RETREAT_DISTANCE = 2700
    private constant real                  ATTACK_DURATION = 30.
    private constant real                  ATTACK_INTERVAL = 3 //Recommended setting
    private constant real               ITEM_PICK_INTERVAL = 1 //Recommended setting
    private constant real                  FOLLOW_INTERVAL = 3 //Recommended setting
    private constant real              MAX_LIFE_PERCENTAGE = 0.5 //Used for retreat
    private constant integer                 MAX_ITEM_TYPE = 3 //Should be match on how many item types created
    private constant integer              MAX_CUSTOM_SKILL = 5 //Should be match on how many skills created
    private constant integer               MAX_ITEM_PICKED = 3 //Warning: MAX is 6
    private constant boolean           ENABLE_PICK_UP_ITEM = true
    private constant boolean      ENABLE_PICK_UP_ITEM_TYPE = false //Set to false to pick every item types
    private constant boolean            ENABLE_FOLLOW_HERO = false //set to true to follow hero
    private constant boolean                ENABLE_RETREAT = true
    private constant boolean    ENABLE_RETREAT_TO_BUILDING = false
endglobals

//===ATTACKING STRUCT:
private struct Engage
    unit hero
    unit target
    real duration
    
    static method onSearchAttack takes nothing returns boolean
        local unit u = GetFilterUnit()
        local boolean b = GetWidgetLife(u) > 0.405 and IsUnitEnemy(u, GetOwningPlayer(UNIT_DATA))
        set u = null
        return b
    endmethod 
    
    static method onRangeAttack takes nothing returns boolean
        local unit u = GetFilterUnit()
        local boolean b = GetWidgetLife(u) > 0.405 and IsUnitEnemy(u, GetOwningPlayer(UNIT_DATA)) and GetOwningPlayer(u)!=Player(PLAYER_NEUTRAL_AGGRESSIVE)
        set u = null
        return b
    endmethod 
    
    static method onCreepAttack takes nothing returns boolean
        local unit u = GetFilterUnit()
        local boolean b = GetWidgetLife(u) > 0.405 and GetOwningPlayer(u)==Player(PLAYER_NEUTRAL_AGGRESSIVE)
        set u = null
        return b
    endmethod
    
    static method onEnemyBuildingLoc takes nothing returns boolean
        local unit u = GetFilterUnit()
        local boolean b = GetWidgetLife(u) > 0.405 and IsUnitEnemy(u, GetOwningPlayer(UNIT_DATA)) and IsUnitType(u, UNIT_TYPE_STRUCTURE)
        set u = null
        return b
    endmethod
    
    static method onAttackNow takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local thistype this = LoadInteger(HASH, GetHandleId(t), 1)
        local unit firsttarget
        local real x
        local real y
       
        if .duration > 0 and GetWidgetLife(.hero) > 0.405 then
            set .duration = .duration - ATTACK_INTERVAL
            if GetWidgetLife(.target) > 0.405 and .target != null then
                call BJDebugMsg(GetUnitName(.hero)+" TARGET STILL ALIVE")
                call IssuePointOrderById(.hero, SMART, GetUnitX(.target), GetUnitY(.target))
                call IssueTargetOrderById(.hero, ATTACK, .target)
            else
                call BJDebugMsg(GetUnitName(.hero)+" SEARCHING")
                set UNIT_DATA = .hero
                call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, Filter(function thistype.onSearchAttack))
                //call GroupEnumUnitsInRange(bj_lastCreatedGroup, CENTER_X, CENTER_Y, AOE_TARGET_SEARCH, Filter(function thistype.onSearchAttack))
                set firsttarget = FirstOfGroup(bj_lastCreatedGroup)
                call IssuePointOrderById(.hero, SMART, GetUnitX(firsttarget), GetUnitY(firsttarget))
                call IssueTargetOrderById(.hero, ATTACK, .target)
                if firsttarget==null then
                    call BJDebugMsg("MOVE RANDOM")
                    call MoveLocation(SEARCH_LOCATION, GetRandomReal(GetRectMinX(bj_mapInitialPlayableArea), GetRectMaxX(bj_mapInitialPlayableArea)), GetRandomReal(GetRectMinY(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea)))
                    call IssuePointOrderByIdLoc(.hero, ATTACK, SEARCH_LOCATION)
                endif
                
            endif
        else
            if GetWidgetLife(.hero) > 0.405 then
                call Engage.onAttackSetup(.hero)
            endif
            call .destroy(this)                
            call PauseTimer(t)
            call DestroyTimer(t)
        endif
        set firsttarget = null
        set t = null
    endmethod
    
    static method onAttackSetup takes unit u returns Engage
        local Engage EN = Engage.create()
        local timer t = CreateTimer()
        local integer randomatk = GetRandomInt(1, 4)
        set UNIT_DATA = u
        
        if randomatk==1 or randomatk==2 then
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_RANDOM, Filter(function thistype.onRangeAttack))
        elseif randomatk==3 then
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_CREEP, Filter(function thistype.onCreepAttack))
        elseif randomatk==4 then
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_BUILDING, Filter(function thistype.onEnemyBuildingLoc))
        endif
        
        set EN.target = FirstOfGroup(bj_lastCreatedGroup)
        set EN.hero = u
        set EN.duration = ATTACK_DURATION
        
        //call BJDebugMsg(GetUnitName(EN.hero)+" "+I2S(EN))
        
        call SaveInteger(HASH, GetHandleId(t), 1, EN)
        call TimerStart(t, ATTACK_INTERVAL, true, function thistype.onAttackNow) 
        
        set t = null
        return EN
    endmethod
    
endstruct

//===ITEM PICK STRUCT:
private struct ItemPick
    unit hero
    
    private static thistype DATA
    
    static method onPickItem takes nothing returns boolean
        local thistype this = DATA
        local item it = GetFilterItem()
        local integer i = 0
        if ENABLE_PICK_UP_ITEM_TYPE then    
            loop
                set i = i+1
                if GetItemTypeId(it)==ITEM_TYPE[i] then
                    call IssueTargetOrderById(.hero, SMART, it)
                endif
                exitwhen i >= MAX_ITEM_TYPE
            endloop
        else
            call IssueTargetOrderById(.hero, SMART, it)        
        endif
        set it = null
        return false
    endmethod    

    static method enumerateItem takes nothing returns nothing
        local timer t = GetExpiredTimer()        
        local thistype this = LoadInteger(HASH, GetHandleId(t), 1)
        local real x
        local real y
        local integer index = 0
        local integer count = 0
        loop
            if (UnitItemInSlot(.hero, index) != null) then
                set count = count + 1
            endif
            set index = index + 1
            exitwhen index >= bj_MAX_INVENTORY
        endloop
        
        if GetWidgetLife(.hero) > 0.405 and count < MAX_ITEM_PICKED then
            set x = GetUnitX(.hero)
            set y = GetUnitY(.hero)
            set DATA = this
            call MoveRectTo(R, x, y)
            call EnumItemsInRect(R, function thistype.onPickItem, null)
        endif
        set t = null
    endmethod
    
    static method onenumerateItem takes unit hero returns thistype
        local thistype this = thistype.create()
        local timer t = CreateTimer()
        set .hero = hero
        call SaveInteger(HASH, GetHandleId(t), 1, this)
        call TimerStart(t, ITEM_PICK_INTERVAL, true, function thistype.enumerateItem) 
        set t = null
        return this
    endmethod

endstruct

//===FOLLOW HERO STRUCT:
private struct FollowH
    unit trigunit
    
    private static thistype DATA
    
    static method onFollowFilter takes nothing returns boolean
        local thistype this = DATA
        local unit u = GetFilterUnit()
        local real x
        local real y
        local real offset = GetRandomReal(100, 250)
        local real angle = GetRandomReal(0, 360)
        if not IsUnitEnemy(u, GetOwningPlayer(.trigunit)) and IsUnitType(u, UNIT_TYPE_HERO) and GetWidgetLife(u) > 0.405 then
            set x = GetUnitX(u)+offset*Cos(angle)
            set y = GetUnitY(u)+offset*Sin(angle)
            call IssuePointOrderById(.trigunit, ATTACK, x, y)  
        endif      
        set u = null
        return false
    endmethod

    static method NowFollow takes nothing returns nothing
        local timer t = GetExpiredTimer()        
        local thistype this = LoadInteger(HASH, GetHandleId(t), 1)
        local real x = GetUnitX(.trigunit)
        local real y = GetUnitY(.trigunit)
        set DATA = this
        call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, AOE_FOLLOW, function thistype.onFollowFilter)
        if GetWidgetLife(.trigunit) <= 0.405 then
            call .destroy(this)
            call PauseTimer(t)
            call DestroyTimer(t)            
        endif
        set t = null
    endmethod
    
    static method onFollow takes unit u returns thistype
        local thistype this = thistype.create()
        local timer t = CreateTimer()
        set .trigunit = u
        call SaveInteger(HASH, GetHandleId(t), 1, this)
        call TimerStart(t, FOLLOW_INTERVAL, true, function thistype.NowFollow) 
        set t = null
        return this
    endmethod

endstruct

//===RETREAT:
private function onRetreatForHealth takes nothing returns boolean
    local unit u = GetFilterUnit()
    local boolean b
    if GetRandomInt(1,2)==1 then
        set b = GetUnitTypeId(u)==FOUNTAIN_OF_HEALTH
    else
        set b = not IsUnitEnemy(u, GetOwningPlayer(UNIT_RETREATING)) and GetWidgetLife(u) > 0.405 and IsUnitType(u, UNIT_TYPE_STRUCTURE) or GetUnitTypeId(u)==FOUNTAIN_OF_HEALTH
    endif
    set u = null
    return b
endfunction

function Retreat takes unit hero, unit attacker returns nothing
    local real maxlife = GetUnitState(hero, UNIT_STATE_MAX_LIFE)
    local real x 
    local real y
    local real x1
    local real y1
    local unit building
    if GetWidgetLife(hero) <= maxlife*MAX_LIFE_PERCENTAGE then
        set x = GetUnitX(hero)
        set y = GetUnitY(hero)
        set x1 = x + RETREAT_DISTANCE * Cos(GetUnitFacing(attacker))
        set y1 = y + RETREAT_DISTANCE * Sin(GetUnitFacing(attacker))
        call IssuePointOrderById(hero, MOVE, x1, y1)
        if ENABLE_RETREAT_TO_BUILDING then
            set UNIT_RETREATING = hero
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, AOE_RETREAT_TO_BUILDING, function onRetreatForHealth)
            set building = FirstOfGroup(bj_lastCreatedGroup)
            if building==null then
                call Retreat(hero, null)
            else
                call IssuePointOrder(hero, "move", GetUnitX(building), GetUnitY(building))
            endif
        endif
    endif
    set building = null
endfunction

private function RetreatCond takes nothing returns boolean
    call Retreat(GetTriggerUnit(), GetAttacker())
    return false
endfunction

//===LEARN SKILL AND PICK UP ITEM:
private function PickSkillNow takes nothing returns boolean
    local integer i
    if GetPlayerController(GetTriggerPlayer())==MAP_CONTROL_COMPUTER then
        set i = 0
        loop
            set i = i + 1
            call SelectHeroSkill(GetTriggerUnit(), SKILL[i])        
            exitwhen i >= MAX_CUSTOM_SKILL
        endloop
    endif
    return false
endfunction

// Set the skills and item types here!
private function SetupSkillsAndItemTypes takes nothing returns nothing
    //You must setup the skills here
    set SKILL[1] = 'A000'
    set SKILL[2] = 'A000'
    set SKILL[3] = 'A000'
    set SKILL[4] = 'A000'
    set SKILL[5] = 'A000'
    //IMPORTANT NOTE: if you increase the item types created, you should increase also the MAX_ITEM_TYPE of the globals
    set ITEM_TYPE[1] = 'will' //Default Wand of Illusion
    set ITEM_TYPE[2] = 'woms' //Default Wand of Mana Stealing
    set ITEM_TYPE[3] = 'wlsd' //Default Wand of Lightning Shield
    //set ITEM_TYPE[4] = 'ADDITIONAL ITEM'
    //set ITEM_TYPE[5] = 'ADDITIONAL ITEM'
endfunction

private function init takes nothing returns nothing
    local trigger t1 = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t1, EVENT_PLAYER_HERO_LEVEL )
    call TriggerAddCondition(t1, function PickSkillNow)
    
    set CENTER_X = GetRectCenterX(bj_mapInitialPlayableArea)
    set CENTER_Y = GetRectCenterY(bj_mapInitialPlayableArea) 
    set SEARCH_LOCATION = Location(GetRandomReal(GetRectMinX(bj_mapInitialPlayableArea), GetRectMaxX(bj_mapInitialPlayableArea)), GetRandomReal(GetRectMinY(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea)))
    set R = Rect(-AOE_ITEM_PICK, -AOE_ITEM_PICK, AOE_ITEM_PICK, AOE_ITEM_PICK)
    call SetupSkillsAndItemTypes()       
    set t1 = null
endfunction

//===SETUP EVERYTHING:========================================
function SetEngageUnit takes unit u returns nothing
    call Engage.onAttackSetup(u)
    if ENABLE_PICK_UP_ITEM and GetPlayerController(GetOwningPlayer(u))==MAP_CONTROL_COMPUTER and IsUnitType(u, UNIT_TYPE_HERO) then
        call ItemPick.onenumerateItem(u)
    endif
    
    if ENABLE_FOLLOW_HERO then
        call FollowH.onFollow(u)
    endif
    
    if ENABLE_RETREAT then
        set RETREAT_TRIGGER = CreateTrigger()
        call TriggerRegisterUnitEvent(RETREAT_TRIGGER, u, EVENT_UNIT_ATTACKED)
        call TriggerAddCondition(RETREAT_TRIGGER, Condition(function RetreatCond))
    endif 
endfunction

endlibrary


credits to watermelon_1234 for helping me to improve things...
 

Attachments

  • AI Engage System - Test.w3x
    328.2 KB · Views: 133
Last edited:
Level 14
Joined
Nov 18, 2007
Messages
1,084
You could have attached a map that would have made it easier to test the system.

Anyway, be prepared:


Specific Stuff
JASS:
        if randomatk==1 or randomatk==2 then
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_RANDOM, Filter(function thistype.onRangeAttack))
            set target = FirstOfGroup(bj_lastCreatedGroup)
        elseif randomatk==3 then
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_CREEP, Filter(function thistype.onCreepAttack))
            set target = FirstOfGroup(bj_lastCreatedGroup)
        elseif randomatk==4 then
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_BUILDING, Filter(function thistype.onEnemyBuildingLoc))
            set target = FirstOfGroup(bj_lastCreatedGroup)        
        endif
->
JASS:
        if randomatk <= 2 then
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_RANDOM, Filter(function thistype.onRangeAttack))
        elseif randomatk==3 then
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_CREEP, Filter(function thistype.onCreepAttack))
        else 
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AOE_TARGET_BUILDING, Filter(function thistype.onEnemyBuildingLoc))   
        endif
        set target = FirstOfGroup(bj_lastCreatedGroup)
I don't like how onAttackSetup works. It would be better if the AI had some kind of priority, first checking to attack a random target, then a creep, and then buildings rather than repeatedly calling onAttackSetup if there's no target for a particular group.

The duration member of Engage can be initially set to ATTACK_DURATION, like how globals can be initialized.

When picking up items, the hero should check if it's inventory is full or if the item is just a powerup to avoid trying to pick an item it can't. Also, you should stop issuing orders to the hero when it finds an item to pick up.

JASS:
            loop
                set i = i+1
                if GetItemTypeId(it)==ITEM_TYPE[i] then
                    call IssueTargetOrder(.hero, "smart", it)
                endif
                exitwhen i >= MAX_ITEM_TYPE
            endloop
You should store the item type id of that item and exit the loop (with exitwhen true) when the hero is issued that order.

Your Rect R leaks since you're creating a rect each time; create it once only when you declare it and use SetRect to move the rect around.

The way you've done onRetreatForHealth is really bizarre. I don't see the need for randomizing it and the condition that checks when random is equal to 1.
There is really no need for the random variable. You could have just done this:
JASS:
if GetRandomInt(1, 2) == 1 then
    ...
else
    ...
endif


JASS:
            set building = FirstOfGroup(bj_lastCreatedGroup)
            call IssuePointOrder(hero, "move", GetUnitX(building), GetUnitY(building))
            if building==null then
                call Retreat(hero, null)
            endif
->
JASS:
            set building = FirstOfGroup(bj_lastCreatedGroup)
            if building==null then
                call Retreat(hero, null)
            else
                call IssuePointOrder(hero, "move", GetUnitX(building), GetUnitY(building))
            endif
set building = null should be in the else of what I did above. Also, why do you keep calling Retreat if the building is null? The hero would move anyway by the earlier order.

Don't order the hero to retreat only when it's about to be attacked; order it when it gets damaged.

You should make the SKILL configuration easier to modify. You should also make it support multiple hero types.

General Stuff
Whenever you're trying to issue an order to the unit, check if the unit is alive first. Example: making the hero pick items.

Cos and Sin only takes radians; you should fix that whenever you use them.

You should be using static ifs for those constant booleans, like ENABLE_PICK_UP_ITEM.

You should separate the configurables from the ones used internally by the system instead of mixing them both. Use two different global blocks.

There is really no need to destroy timers. Use TimerUtils. Or use a global timer to loop through all struct instances.

FOUNTAIN_OF_MANA was never used.

RETREAT_TRIGGER should not be initialized like that since the user may never want the AI to retreat. Set it only when ENABLE_RETREAT is true.
 
Last edited:
A good review deserves a +rep, thank you...

the AI has already a priority list via onAttackSetup >>> randomatk, but if the condition is not met in onAttackNow method, then it will pick another priority...

When picking up items, the hero should check if it's inventory is full or if the item is just a powerup to avoid trying to pick an item it can't. Also, you should stop issuing orders to the hero when it finds an item to pick up.

I'll fix that...

You should store the item type id of that item and exit the loop (with exitwhen true ) when the hero is issued that order.

Your Rect R leaks since you're creating a rect each time; create it once only when you declare it and use SetRect to move the rect around.

The way you've done onRetreatForHealth is really bizarre. I don't see the need for randomizing it and the condition that checks when random is equal to 1.
There is really no need for the random variable. You could have just done this:

as you can see, the item type Id is MUST be setup manually in the initialization part, else the AI will pick up any item...

The rect I already fixed that, the next version will show it...

About the random retreat, I did that coz what if the map doesnt have a fountain of health but only buildings?...

set building = null should be in the else of what I did above. Also, why do you keep calling Retreat if the building is null? The hero would move anyway by the earlier order.

set building, noted, fixed...

Don't order the hero to retreat only when it's about to be attacked; order it when it gets damaged.

fine, perhaps, Ima use a simple damate detection...

You should make the SKILL configuration easier to modify. You should also make it support multiple hero types.

I already did in the initialization part like the items, it MUST set it manually...

Whenever you're trying to issue an order to the unit, check if the unit is alive first. Example: making the hero pick items.

You should separate the configurables from the ones used internally by the system instead of mixing them both. Use two different global blocks.

Fixed...

There is really no need to destroy timers. Use TimerUtils. Or use a global timer to loop through all struct instances.

Nah, I think of it :)...

FOUNTAIN_OF_MANA was never used.

RETREAT_TRIGGER should not be initialized like that since the user may never want the AI to retreat. Set it only when ENABLE_RETREAT is true.

fountain of mana, I forgot to delete it, :)...

retreat trigger >>> fixed...


In all thanks for your review, a test map will be attached with some of your suggestions implemented...
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
the AI has already a priority list via onAttackSetup >>> randomatk, but if the condition is not met in onAttackNow method, then it will pick another priority...
I know that your function will call onAttackSetup again if no units were found for a particular group. But what would happen if all 3 groups were empty? This would result in an infinite loop.
The function can also be wasteful because it's based on randomization. For example, there are no enemy structures around the hero, but the function might keep checking for enemy structures if randomatk keeps being 4.

as you can see, the item type Id is MUST be setup manually in the initialization part, else the AI will pick up any item...
I meant store the item type id of the item being enumerated in a local variable. :p

About the random retreat, I did that coz what if the map doesnt have a fountain of health but only buildings?...
Then shouldn't
JASS:
set b = not IsUnitEnemy(u, GetOwningPlayer(UNIT_RETREATING)) and GetWidgetLife(u) > 0.405 and IsUnitType(u, UNIT_TYPE_STRUCTURE) or GetUnitTypeId(u)==FOUNTAIN_OF_HEALTH
be enough? I don't get the point of the condition when it's 1.

I already did in the initialization part like the items, it MUST set it manually...
I meant something like this:
JASS:
globals
    // Your configurable globals, also include the integer array ITEM_TYPE and SKILL
endglobals

// Set the skills and item types here!
function SetupSkillsAndItemTypes takes nothing returns nothing
    set SKILL[1] = 'A000'
    set SKILL[2] = 'A000'
    set SKILL[3] = 'A000'
    set SKILL[4] = 'A000'
    set SKILL[5] = 'A000'
    //IMPORTANT NOTE: if you increase the item types created, you should increase also the MAX_ITEM_TYPE of the globals
    set ITEM_TYPE[1] = 'will' //Default Wand of Illusion
    set ITEM_TYPE[2] = 'woms' //Default Wand of Mana Stealing
    set ITEM_TYPE[3] = 'wlsd' //Default Wand of Lightning Shield
endfunction

...

private function init takes nothing returns nothing 
    ...
    call SetupSkillsAndItemTypes()
    ...
endfunction
I think that's more user-friendly than putting it at the bottom.

Well, good luck with your system. ^_^
 
Last edited:
yeah that's more user friendly :)...

about the infinite loop, I've been experimenting for 3 hrs how to avoid it but failed...

My last resort would be to remove the call onAttackSetup(u) on the onAttackSetup method, and put it in the loop itself, checking evey time, like this...

JASS:
if .duration > 0 and GetWidgetLife(.hero) > 0.405 then
   set .duration = .duration - ATTACK_INTERVAL
   if .target != null and GetWidgetLife(.target) > 0.405 then
      call IssuePointOrder(.hero, "attack", GetUnitX(.target), GetUnitY(.target))
   else
       //find another target or patrol on the map area
   endif
else
    call onAttackSetup(.hero) //this will be infinite as well but at least not every seconds...
    call .destroy(this)
    call PauseTimer(t)
    call DestroyTimer(t)
endif
 
Status
Not open for further replies.
Top