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

AI - Engage System v3.2

System: Engage System v3.2
Created by: Mckill2009

This system is for AI usage, just like DotA AI.
Read the instructions carefully inside the code.


JASS:
/*
===================EngageSystem v3.2===================
==================Made by: Mckill2009==================

FEATURES:
- This system will order a unit to engage and search for enemies
- Allows attack captain (hero leads the attack)
- Allows pick up item
- Allows target random enemy units include creeps and buildings
- Option to retreat away from attacker or to building when HP is low
- Allows target refresh when 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)
- Make a dummy ability of UNHOLY FRENZY that has no buff, mana cost and replace the SPELL_ID raw code
  written below the THIRD global block if needed, see below the SPELL_ID for more info.

KNOWN ISSUES:
- This system does not support Buy and Selling of items for AI

REQUIRES:
- Jass New Gen Pack (JNGP) by Vexorian
- GetClosestWidget by Spinnaker
- IsUnitChanneling by Magtheridon96
- RegisterPlayerUnitEvent by Magtheridon96

CREDITS: For their contribution
- watermelon_1234
- Bribe

API:
    static method SetEngageUnit takes unit u, boolean attackcaptain returns nothing
    static method SetUnitPickItem takes unit u returns nothing
    static method SetUnitRetreat takes unit u returns nothing
    static method SetUnitLockTargets takes unit u, integer targets, real atkdur returns nothing
    static method SetReturnPoints takes unit u, real xReturn, real yReturn returns nothing
    static method SetHeroToProtect takes unit follower, unit hero returns nothing
    static method RemoveEngagingUnit takes unit u returns nothing
*/

library EngageSystem uses GetClosestWidget, IsUnitChanneling

globals
    //===CONFIGURABLES: All settings below are recommended
    private constant real                      AOE_ITEM_PICK = 700 //range of the targeted item
    private constant real                    AOE_NEAR_TARGET = 500 //targeting nearby enemies while other target is locked
    private constant real                           INTERVAL = 3 //interval for attack
    private constant real                MAX_LIFE_PERCENTAGE = 0.4 //used for retreat (this is 40%)
    private constant real                   OFFSET_FROM_HERO = 300 //used in hero guard/follow   
    private constant integer                 MAX_ITEM_PICKED = 4 //Warning: MAX is 6
    private constant boolean             ENABLE_PICK_UP_ITEM = true
    private constant boolean                  ENABLE_RETREAT = true
    private constant boolean              ENABLE_NEAR_TARGET = true //enables to search/target nearby units
    //If FOUNTAIN_OF_HEALTH doesnt exist, it will retreat to an allied bubilding
    private constant boolean      ENABLE_RETREAT_TO_BUILDING = false //ENABLE_RETREAT must be true
    //SPELL_ID must target "Air, ground, enemy, friend, vulnerable, mechanical, structure, organic, neutral", NO MANA, NO COOLDOWN
    //In other words, it MUST target ALL UNITS excluding invulnerable.
    //SPELL_ID MUST be Unholy Frenzy ability, else it wont work
    private constant integer                        SPELL_ID = 'A001' //Unholy frenzy RAW CODE, replace this if needed
endglobals

//=====================================================
//===============NEVER TOUCH THIS BLOCK!===============
//=====================================================
globals
    private constant hashtable              HASH = InitHashtable()
    private constant integer              ATTACK = 851983
    private constant integer                MOVE = 851986
    private constant integer                STOP = 851972
    private constant integer               SMART = 851971
    private constant integer  FOUNTAIN_OF_HEALTH = 'nfoh'
    private rect R
    private unit DUMMY
endglobals

native UnitAlive takes unit u returns boolean

private function IsUnitEngageable takes unit u returns boolean
    call SetUnitPosition(DUMMY, GetUnitX(u), GetUnitY(u))
    call IssueTargetOrderById(DUMMY, 852209, u) //Unholy Frenzy orderID
    if GetUnitCurrentOrder(DUMMY)==852209 then
        return true
    endif
    call IssueImmediateOrderById(DUMMY, STOP)
    return false
endfunction

private function FilterEngagingUnit takes unit u returns boolean
    return GetUnitMoveSpeed(u) > 0 and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and /*
    */ not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_PEON) and /*
    */ GetPlayerController(GetOwningPlayer(u))==MAP_CONTROL_COMPUTER
endfunction

private function UnitEnemy takes unit u1, unit u2 returns boolean
    return IsUnitEnemy(u1, GetOwningPlayer(u2))
endfunction

private function UnitHero takes unit u returns boolean
    return IsUnitType(u, UNIT_TYPE_HERO)
endfunction

private function UnitStructure takes unit u returns boolean
    return IsUnitType(u, UNIT_TYPE_STRUCTURE)
endfunction

private function GetDistance takes real x1, real y1, real x2, real y2 returns real
    return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)
endfunction
//=====================================================
//===========END OF NEVER TOUCH THIS BLOCK!============
//=====================================================

//========================================
//=============ENGAGE CODE:===============
//========================================
//! textmacro EScreatetimer
    private static timer t = CreateTimer()
    private static integer instance = 0
    private static integer array instanceAr
//! endtextmacro

//! textmacro deallocate
    call .destroy()
    set instanceAr[i] = instanceAr[instance]
    set instanceAr[instance] = this
    set instance = instance - 1   
    set i = i - 1
    if instance==0 then
        call PauseTimer(t)
    endif
//! endtextmacro

//===NON-HERO UNITS ARE ALWAYS THE FOLLOWER
private struct Follower
    unit follower
    unit hero
    unit mainhero
    unit target
    private static integer DATA
    private static unit filU
    //! runtextmacro EScreatetimer()
   
    private method destroy takes nothing returns nothing
        set .follower = null
        set .hero = null
        set .mainhero = null
        set .target = null
        call .deallocate()
    endmethod
   
    static method filterHero takes nothing returns boolean
        local thistype this = DATA
        set filU = GetFilterUnit()
        return UnitAlive(filU) and not UnitEnemy(filU,.follower) and UnitHero(filU) and IsUnitEngageable(filU) and GetOwningPlayer(filU)==GetOwningPlayer(.follower)
    endmethod       
       
    private static method engagingTarget takes nothing returns nothing
        local thistype this
        local real xAttacker
        local real yAttacker
        local real xHero
        local real yHero
        local real xOffset
        local real yOffset
        local real DistX
        local real angle
        local real facing
        local integer uID
        local integer orderIDU
        local integer orderIDH
        local integer i = 0
        loop
            set i = i + 1 
            set this = instanceAr[i]
            set uID = GetHandleId(.follower)
            if LoadBoolean(HASH,uID,1) and UnitAlive(.follower) then
                set xAttacker = GetUnitX(.follower)
                set yAttacker = GetUnitY(.follower)
                if UnitAlive(.hero) then 
                    set orderIDH = GetUnitCurrentOrder(.hero)
                    if not IsUnitChanneling(.hero) or orderIDH==ATTACK then
                        set xHero = GetUnitX(.hero)
                        set yHero = GetUnitY(.hero)
                        if UnitAlive(.target) then
                            set facing = GetUnitFacing(.hero)*bj_DEGTORAD
                            set angle = GetRandomReal(-3,3)
                            set xOffset = xHero+OFFSET_FROM_HERO*Cos(facing+angle)
                            set yOffset = yHero+OFFSET_FROM_HERO*Sin(facing+angle)
                            set DistX = GetDistance(xAttacker,yAttacker,xHero,yHero)
                            set orderIDU = GetUnitCurrentOrder(.follower)
                            if not IsUnitChanneling(.follower) then
                                if DistX < (600*600) then
                                    if IsUnitType(.target,UNIT_TYPE_SLEEPING) then
                                        call IssueTargetOrderById(.follower,ATTACK,.target)
                                    else
                                        call IssuePointOrderById(.follower,ATTACK,GetUnitX(.target),GetUnitY(.target))
                                    endif
                                else
                                    call IssuePointOrderById(.follower,ATTACK,xOffset,yOffset)
                                endif
                            endif
                        else
                            set .target = LoadUnitHandle(HASH,GetHandleId(.hero),7)
                            call IssuePointOrderById(.follower,ATTACK,xOffset,yOffset)
                        endif 
                    endif
                else
                    set .hero = .mainhero
                    if .hero==null then
                        set DATA = this
                        set .hero = GetClosestUnit(xAttacker,yAttacker,Filter(function thistype.filterHero))
                        call IssuePointOrderById(.follower, ATTACK, LoadReal(HASH, uID, 4), LoadReal(HASH, uID, 5))
                    endif
                endif
            else
                call FlushChildHashtable(HASH,uID)
                //! runtextmacro deallocate()
            endif 
            exitwhen i==instance
        endloop
    endmethod
   
    static method addFollower takes unit u returns nothing
        local thistype this
        if instance < 8190 then
            set this = thistype.allocate()
            set .follower = u
            set .mainhero = LoadUnitHandle(HASH, GetHandleId(u), 8)
            set .hero = null
            set .target = null
            if instance==0 then
                call TimerStart(t, 2.5, true, function thistype.engagingTarget)
            endif
            set instance = instance + 1
            set instanceAr[instance] = this       
        else
            call DestroyTimer(t)
            debug call BJDebugMsg("ERROR: Limit of 8190 reached!, cannot allocate anymore instance: DESTROYING TIMER. ")
        endif     
    endmethod
endstruct

//===REGISTERS INDEPENDENT UNITS (means, this unit will NOT search and follows a hero)
//=== A HERO WILL ALWAYS BE REGISTERED AS INDEPENDENT UNITS
private struct IndependentEngager
    private unit engager
    private unit target
    private real interval
    private static integer DATA
    private static unit filterU
    //! runtextmacro EScreatetimer()
   
    //16 Total Players, 17==ALL PLAYERS, 18==Structures, 19==Flying, 20==Heroes
    private static method FilterEngage takes nothing returns boolean
        local thistype this = DATA
        local integer targetID
        local integer i = 0
        set filterU = GetFilterUnit()       
        if UnitAlive(filterU) and IsUnitEnemy(.engager,GetOwningPlayer(filterU)) and IsUnitEngageable(filterU) then
            set targetID = LoadInteger(HASH,GetHandleId(.engager),2)
            if targetID < 17 then
                loop
                    set i = i+1
                    if targetID==i then
                        return GetOwningPlayer(filterU)==Player(i-1)
                    endif  
                    exitwhen i==targetID
                endloop
           
            elseif targetID==17 then
                return true
            elseif targetID==18 then
                return IsUnitType(filterU,UNIT_TYPE_STRUCTURE)
            elseif targetID==19 then
                return IsUnitType(filterU,UNIT_TYPE_FLYING)
            elseif targetID==20 then
                return IsUnitType(filterU,UNIT_TYPE_HERO)
            endif
        endif
        return false
    endmethod

    private static method engagingTarget takes nothing returns nothing
        local thistype this
        local real xAttacker
        local real yAttacker
        local real xTarget
        local real yTarget
        local integer orderU
        local integer uID
        local integer i = 0
        loop
            set i = i + 1 
            set this = instanceAr[i]
            set uID = GetHandleId(.engager)
            if LoadBoolean(HASH, uID, 1) and UnitAlive(.engager) then
                set xAttacker = GetUnitX(.engager)
                set yAttacker = GetUnitY(.engager)
                if .interval > 0 then
                    set .interval = .interval - INTERVAL
                    if UnitAlive(.target) and IsUnitEngageable(.target) then   
                        set xTarget = GetUnitX(.target)
                        set yTarget = GetUnitY(.target)
                        set orderU = GetUnitCurrentOrder(.engager)
                        if not IsUnitChanneling(.engager) then
                            if IsUnitType(.target,UNIT_TYPE_SLEEPING) then
                                call IssueTargetOrderById(.engager, ATTACK, .target)
                            else                               
                                call IssuePointOrderById(.engager, ATTACK, xTarget, yTarget)
                            endif
                        endif                         
                    else
                        set DATA = this
                        set .target = GetClosestUnit(xAttacker,yAttacker,Filter(function thistype.FilterEngage))
                        call SaveUnitHandle(HASH,uID,7,.target)
                        if .target==null then
                            call IssuePointOrderById(.engager, ATTACK, LoadReal(HASH, uID, 4), LoadReal(HASH, uID, 5))
                        endif
                    endif                   
                else
                    set DATA = this
                    set .interval = LoadReal(HASH, uID, 3) //resumes the timer
                    set .target = GetClosestUnit(xAttacker,yAttacker,Filter(function thistype.FilterEngage))
                    call SaveUnitHandle(HASH,uID,7,.target)
                endif
            else
                if not UnitHero(.engager) then
                    set .engager = null
                    set .target = null
                    //! runtextmacro deallocate()
                elseif not LoadBoolean(HASH, uID, 1) then
                    //Uses by heroes
                    set .engager = null
                    set .target = null
                    //! runtextmacro deallocate()                 
                endif
            endif           
            exitwhen i==instance
        endloop
    endmethod

    static method addUnit takes unit e returns nothing
        local thistype this
        if instance < 8190 then
            set this = allocate()
            set .engager = e
            set .target = null
            set .interval = 0
            if instance==0 then
                call TimerStart(t, INTERVAL, true, function thistype.engagingTarget)
            endif
            set instance = instance + 1
            set instanceAr[instance] = this  
        else
            call DestroyTimer(t)
            debug call BJDebugMsg("ERROR: Limit of 8190 reached!, cannot allocate anymore instance: DESTROYING TIMER. ")       
        endif
    endmethod
endstruct
//========================================
//=========END OF ENGAGE CODE:============
//========================================

//===ITEM PICK:
private struct ItemPick
    unit hero
    private static thistype DATA
    //! runtextmacro EScreatetimer()
   
    private static method onPickItem takes nothing returns boolean
        local thistype this = DATA
        local integer orderID = GetUnitCurrentOrder(.hero)
        if orderID==MOVE or orderID==ATTACK then
            call IssueTargetOrderById(.hero, SMART, GetFilterItem())
        endif
        return false
    endmethod   

    private static method enumerateItem takes nothing returns nothing
        local thistype this
        local real x
        local real y
        local integer index
        local integer count
        local integer orderID
        local integer i = 0
        loop
            set i = i + 1
            set this = instanceAr [i]
            if LoadBoolean(HASH, GetHandleId(.hero), 1) then
                if UnitAlive(.hero) then
                    set count = 0
                    set index = 0
                    loop
                        if (UnitItemInSlot(.hero, index) != null) then
                            set count = count + 1
                        endif
                        set index = index + 1
                        exitwhen index==6
                    endloop
               
                    if count < MAX_ITEM_PICKED then
                        set x = GetUnitX(.hero)
                        set y = GetUnitY(.hero)
                        set DATA = this
                        call MoveRectTo(R, x, y)
                        call EnumItemsInRect(R, null, function thistype.onPickItem)
                    endif
                endif
            else
                set .hero = null
                //! runtextmacro deallocate()
            endif
            exitwhen i==instance
        endloop
    endmethod
   
    static method create takes unit hero returns thistype
        local thistype this = thistype.allocate()
        set .hero = hero
        if instance==0 then
            call TimerStart(t, 1.0, true, function thistype.enumerateItem)
        endif
        set instance = instance + 1
        set instanceAr[instance] = this
        return this
    endmethod
endstruct

//===RETREAT:
private struct Retreat
    unit hero
    unit retunit
    real minlife
    //! runtextmacro EScreatetimer()
   
    private static method searchtarget takes nothing returns nothing
        local thistype this
        local unit first
        local real xUnit
        local real yUnit
        local real distX
        local real distEx
        local real xBase
        local real yBase
        local integer orderID
        local integer uID
        local integer i = 0
        loop
            set i = i + 1
            set this = instanceAr [i]
            if LoadBoolean(HASH, GetHandleId(.hero), 1) then
                if UnitAlive(.hero) then
                    set orderID = GetUnitCurrentOrder(.hero)
                    if not IsUnitChanneling(.hero) and orderID==ATTACK or orderID==MOVE then
                        if GetWidgetLife(.hero) < .minlife then
                            set uID = GetHandleId(.hero)
                            set xUnit = GetUnitX(.hero)
                            set yUnit = GetUnitY(.hero)                           
                            static if ENABLE_RETREAT_TO_BUILDING then
                                if .retunit==null then
                                    //searches a place to retreat
                                    call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, null)
                                    loop
                                        set first = FirstOfGroup(bj_lastCreatedGroup)
                                        exitwhen first==null
                                        if UnitAlive(first) and not UnitEnemy(first, .hero) then
                                            if GetUnitTypeId(first)==FOUNTAIN_OF_HEALTH then
                                                set .retunit = first
                                                exitwhen true
                                            elseif UnitStructure(first) and not (GetOwningPlayer(first)==Player(PLAYER_NEUTRAL_PASSIVE)) then
                                                set .retunit = first                                
                                                exitwhen true
                                            endif
                                        endif
                                        call GroupRemoveUnit(bj_lastCreatedGroup, first)
                                    endloop
                                else
                                    if .retunit != null then
                                        set distEx = GetDistance(xUnit,yUnit,GetUnitX(.retunit),GetUnitY(.retunit))
                                        //searching fountain of health
                                        call IssuePointOrderById(.hero, MOVE, GetUnitX(.retunit), GetUnitY(.retunit))
                                    else
                                        set xBase = LoadReal(HASH, uID, 4)
                                        set yBase = LoadReal(HASH, uID, 5)
                                        set distX = GetDistance(xUnit,yUnit,xBase,yBase)
                                        if distX > (1300*1300) then
                                            //if no more allies, the AI will return to where is it assigned
                                            call IssuePointOrderById(.hero,MOVE,xBase,yBase)
                                        endif
                                    endif
                                endif                   
                            else
                                set xBase = LoadReal(HASH, uID, 4)
                                set yBase = LoadReal(HASH, uID, 5)
                                set distX = GetDistance(xUnit,yUnit,xBase,yBase)
                                if distX > (1300*1300) then
                                    //if no more allies, the AI will return to where is it assigned
                                    call IssuePointOrderById(.hero,MOVE,xBase,yBase)                
                                endif
                            endif              
                        endif   
                    endif
                endif
            else
                set .hero = null
                set .retunit = null
                //! runtextmacro deallocate()               
            endif
            exitwhen i==instance
        endloop
    endmethod
   
    static method create takes unit u, real x, real y returns thistype
        local thistype this = thistype.allocate()
        set .hero = u
        set .minlife = GetUnitState(u, UNIT_STATE_MAX_LIFE)*MAX_LIFE_PERCENTAGE
        set .retunit = null
        if instance==0 then
            call TimerStart(t, 1.0, true, function thistype.searchtarget)
        endif
        set instance = instance + 1
        set instanceAr[instance] = this
        return this   
    endmethod
endstruct

//===INITIALIZER:
private struct EngageInit
    private static method onInit takes nothing returns nothing
        local trigger t
        static if ENABLE_PICK_UP_ITEM then
            set R = Rect(-AOE_ITEM_PICK, -AOE_ITEM_PICK, AOE_ITEM_PICK, AOE_ITEM_PICK)
        endif
        set DUMMY = CreateUnit(Player(15), 'hmpr', 0,0,0)
        call UnitAddAbility(DUMMY, SPELL_ID)
        call UnitAddAbility(DUMMY, 'Aloc') 
        call ShowUnit(DUMMY, false)
        set t = null
    endmethod
endstruct

struct EngageSystem
    //===SETUP EVERYTHING: SYSTEM API
    //attackcaptain - sets the attack captain which will find and guard heroes or hero that will lead the attack group
    static method SetEngageUnit takes unit u, boolean attackcaptain returns nothing
        local integer unitID = GetHandleId(u)
        if FilterEngagingUnit(u) then
            //Removes the guard position so that AI's dont go back to their original location
            if not LoadBoolean(HASH,unitID,1) then
                call RemoveGuardPosition(u)
                call SaveBoolean(HASH, unitID, 1, true)
                if UnitHero(u) or not attackcaptain then
                    call SaveBoolean(HASH, unitID, 6, true)
                    call IndependentEngager.addUnit(u)
                else
                    call Follower.addFollower(u)
                endif
            else
                debug call BJDebugMsg("ERROR: SetEngageUnit, "+GetUnitName(u)+" has already been registered!")
            endif
        endif
    endmethod

    //Works only for computer controlled unit and heroes
    static method SetUnitPickItem takes unit u returns nothing
        static if ENABLE_PICK_UP_ITEM then
            if FilterEngagingUnit(u) and UnitHero(u) then
                call ItemPick.create(u)
            endif
        else
            debug call BJDebugMsg("ERROR: ENABLE_PICK_UP_ITEM is false, set it to true!")
        endif
    endmethod
   
    static method SetUnitRetreat takes unit u returns nothing
        static if ENABLE_RETREAT then
            if FilterEngagingUnit(u) then
                call Retreat.create(u, GetUnitX(u), GetUnitY(u)) 
            endif
        else
            debug call BJDebugMsg("ERROR: ENABLE_RETREAT is false, set it to true!")
        endif
    endmethod
   
    /* Targets;
        1 to 16 = Targets a specific ENEMY PLAYER
        17 = Targets ALL ENEMY PLAYERS
        18 = Targets only enemy STRUCTURES
        19 = Targets only enemy FLYING UNITS
        20 = Targets only enemy HEROES
    */
   
    static method SetUnitLockTargets takes unit u, integer targets, real atkdur returns nothing
        local integer unitID = GetHandleId(u)
        if IsUnitType(u, UNIT_TYPE_HERO) or LoadBoolean(HASH,unitID,6) and FilterEngagingUnit(u) then
            if targets < 17 and targets > 0 then
                if GetOwningPlayer(u)==Player(targets-1) then
                    debug call BJDebugMsg("ERROR: Player "+I2S(targets)+" can't target his own units!")
                else
                    call SaveInteger(HASH, unitID, 2, targets)
                endif
       
            elseif targets > 16 and targets < 21 then
                call SaveInteger(HASH, unitID, 2, targets)       
            else
                debug call BJDebugMsg("ERROR: Please input only from 1 to 20!")
            endif
            call SaveReal(HASH,unitID,3,atkdur)
        endif
    endmethod
   
    //Sets the specific hero to protect, if main hero is dead, it will search for a new hero
    static method SetHeroToProtect takes unit follower, unit hero returns nothing
        if not LoadBoolean(HASH, GetHandleId(follower), 6) and FilterEngagingUnit(follower) then
            call SaveUnitHandle(HASH, GetHandleId(follower), 8, hero)
        endif
    endmethod
   
    static method SetReturnPoints takes unit u, real xReturn, real yReturn returns nothing
        call SaveReal(HASH, GetHandleId(u), 4, xReturn)
        call SaveReal(HASH, GetHandleId(u), 5, yReturn)
    endmethod
   
    static method RemoveEngagingUnit takes unit u returns nothing
        call FlushChildHashtable(HASH,GetHandleId(u))
    endmethod

endstruct

endlibrary


Parameter explanation:

1) static method SetEngageUnit takes unit u, boolean attackcaptain returns nothing
This is the FIRST thing you need to do.
attackcaptain - sets the attack captain which will follow/guard heroes or hero that will lead the attack group

2) static method SetUnitPickItem takes unit u returns nothing
3) static method SetUnitRetreat takes unit u returns nothing
Enables Hero to pick items and retreats respectively, these are optional

4) static method SetUnitLockTargets takes unit u, integer targets, real atkdur returns nothing
This is the SECOND thing you need to do, coz engaging must have targets.
targets - Locks a particular ENEMY target
1 to 16 = Targets a specific ENEMY PLAYER
17 = Targets ALL ENEMY PLAYERS
18 = Targets only enemy STRUCTURES
19 = Targets only enemy FLYING UNITS
20 = Targets only enemy HEROES

Take note that the Targets reffers to the MAIN TARGET

atkdur - Sets how long an attacker to lock to a particular target

5) static method SetReturnPoints takes unit u, real xReturn, real yReturn returns nothing
This is the THIRD thing you need to do, the return locations.
Sets and Changes the Return point of the AI if no more targets around

6) static method SetHeroToProtect takes unit follower, unit hero returns nothing
This is optional

7) static method RemoveEngagingUnit takes unit u returns nothing
Removes the Engager from the system

NOTE:
All the u is the unit or the AI that's engaging.

How to use in GUI?
  • Demo
    • Events
      • Time - Elapsed game time is 1.00 seconds
    • Conditions
    • Actions
      • Unit Group - Pick every unit in (Units owned by Player 2 (Blue)) and do (Actions)
        • Loop - Actions
          • Set U = (Picked unit)
          • Custom script: call EngageSystem.SetEngageUnit(udg_U,true)
          • Custom script: call EngageSystem.SetUnitPickItem(udg_U)
          • Custom script: call EngageSystem.SetUnitRetreat(udg_U)
          • Custom script: call EngageSystem.SetReturnPoints(udg_U, GetUnitX(udg_U),GetUnitY(udg_U))
          • -------- Player 3's target is 17, so his main targets are ALL ENEMIES --------
          • Custom script: call EngageSystem.SetUnitLockTargets(udg_U, 17, 30)
      • Unit Group - Pick every unit in (Units owned by Player 3 (Teal)) and do (Actions)
        • Loop - Actions
          • Set U = (Picked unit)
          • Custom script: call EngageSystem.SetEngageUnit(udg_U,true)
          • Custom script: call EngageSystem.SetUnitPickItem(udg_U)
          • Custom script: call EngageSystem.SetUnitRetreat(udg_U)
          • Custom script: call EngageSystem.SetReturnPoints(udg_U, GetUnitX(udg_U),GetUnitY(udg_U))
          • -------- Player 3's target is 2, so his main targets are Player 2 --------
          • Custom script: call EngageSystem.SetUnitLockTargets(udg_U, 2, 30)
          • -------- sets all non-hero engaging units to protext/follow the hero --------
          • Custom script: call EngageSystem.SetHeroToProtect(udg_U,udg_HERO)

version 3.2
- Added IsUnitChanneling library for faster use

version 3.1
- SetUnitLockTargets added several targets to engage in a dynamic way (see above)
- Added 1 function SetHeroToProtect to follow/protect a specific main hero
- Fixed engager cannot attack when near his base

version 3.0
- Fixed ItemPick and Retreat struct coz it doesnt work on version 2.9

version 2.9 (This version is very buggy)
- TimerUtils removed and replaced by one timer for each struct
- GetClosestWidget system added
- Functions converted to struct static methods
- Added 3 more methods
- Removes AI from system manually
- xReturn, yReturn, atkdur and targets can now be modified dynamically

version 2.8
- Bribes suggestions applied.
- Now it cant lock invisible, invulnerable and burrowed targets.
- Fix a bug that AI will not attack sleeping unit.

version 2.7
- Added TimerUtils library.
- Made 3 separate functions instead of one.
- LearnNormalSkillLib, arg rects removed.
- Uses FirstOfGroup loops for faster enumeration.

version 2.6
- Added 3 parameters for individual timer for engage, pickup item and retreat
- Initializer replaced by struct onInit
- Added 4 function calling
- Added 1 function to check if the AI is channeling or not
- Added AI engage for summoned units to protect the hero
- Fixed minor retreat point as pointed out by bugers

version 2.4
- Retreat converted to struct (Retreat.create)
- Struct intances replaced by term "create"
- Added boolean parameter for SetEngageUnit for optional guard/follow hero
- Locked retreat to created point added
- Locked retreat to fountain of health or building added
- Codes more efficient and reduced
- GetWidgetLife replaced by IsUnitType DEAD
- Engage.create added 4 additional parameters
- AI will not move if less than health% is set (works only if ENABLE_RETREAT is true)
- RemoveGuardPost added

version 2.2
- AI retreats to any ally base if no more enemies
- Codes searching is reduced from 5 to 3
- Random search is done by the Filter, not at the setup
- Most AOE setup is replaced by Map playable area for better area search
- New test map

version 2.0
- Timer loops are replaced by trigger loops
- Single Hashtable usage
- Fixed a bug that caused the units stay in the center
- Added condition structure type not included in the Engage call
- Attacked is replaced by Damage detection for the retreat trigger
- Configurables are placed on top
- Test map added waygates to test if unit will search enemy in that way


Keywords:
AI, attack, engage, DotA, AoS, item, buy, sell, lock, spell, diablo, fire, mckill2009
Contents

EngageSystem (Map)

Reviews
30 Jan 2012 Bribe: A very well-written AI system. Approved 5/5.

Moderator

M

Moderator

30 Jan 2012
Bribe: A very well-written AI system. Approved 5/5.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,454
Setting the target always to the first of the group is going to always pick the unit with the lowest y-coordinate. You should probably use a "GetClosestUnit" function (found under JASS Resources) to make this more modular.

You should also move the "SetupSkillsAndItemTypes" to the top of the script because it is configurable.

You also just need 1 hashtable for this, just use the "-parentKey" or "-childKey" trick I showed you.
 
Level 22
Joined
Feb 3, 2009
Messages
3,292
I have a few questions:

1. Will the AI cast spells based on channel?
2. Will the AI work on a map in which players control 1 hero and fight each other, there are also a lot of creeps and there is no base (structures) (there are fountains of regeneration though)?
3. Will the AI use the waygate if that's the only way to enter & leave base?
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
The point of libraries is to have the option to require other needed systems, which should be applied here.

And my eyes hurt every time i see the line "InitHashtable()" - this time twice!

And I also do wonder why this - very important function - is at the bottom of the code? :)

JASS:
// 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
 
Level 22
Joined
Feb 3, 2009
Messages
3,292
I added it to the map and it seems I'm in luck, the AIs automatically went to the path where the way gate is and so they went outside of base on their own.

Guess I've found the perfect solution.

EDIT:

I think I found a bug, the units are moving towards the middle of the map every time, and soon it turns into a middle of map massacre and then it repeats.
 
Last edited:
Level 29
Joined
Mar 10, 2009
Messages
5,016
@baassee
OK, ok Ima do that, Bribe already said that btw :)...

@Ironside
- I dunnu, maybe AI is programmed to function to find gateways?, in that case good!...
- Ima fix the middle of the map massacre, before I already added a search
and move location but I removed that function, I guess Ima put it again...

EDIT:
===UPDATE===
version 2.0
- Timer loops are replaced by trigger loops
- Single Hashtable usage
- Fixed a bug that caused the units stay in the center
- Added condition structure type not included in the Engage call
- Attacked is replaced by Damage detection for the retreat trigger
- Configurables are placed on top
- Test map added waygates to test if unit will search enemy in that way
 
Last edited:
Level 7
Joined
May 23, 2011
Messages
179
Very Good now its even more better and better but mckill2009 can you add that to kill neutral creeps and if it really needs money??............. like in DOTA?? and also kill allies if HP is lower than 15%.......... I think it was the previous system that i requested from you....
 
Wait! I got it :D
We could create a library that tracks player states (PLAYER_NEEDS_GOLD, PLAYER_NEEDS_ITEM, PLAYER_NEEDS_LUMBER, PLAYER_STATUS_IMBA, PLAYER_STATUS_NOOB, PLAYER_STATUS_NEUTRAL, PLAYER_NEEDS_BUILDING, PLAYER_AGGRESSIVE, PLAYER_DEFENSIVE, PLAYER_RETREATING, etc....)

I could design it when I feel like it :p

We could also create functions like:
call AttemptToBuyItemForHero(whichPlayer,whichHero,whichItemId)
This function would check:
- Position of hero
- Distance from the shop where the item is located
- Gold
etc..

If he's far away and has no gold, the PLAYER_NEEDS_GOLD state would be added
to the QueueQueue right under PLAYER_NEEDS_ITEM
The hero would be issued an order to go farm a bit in some area given in the configuration.
When he has enough gold, his state 'poped' off the QueueQueue, and then, we'd have to issue an order to the shop where this item could be bought.

He'll buy the item, and his player state would be set to something like: "PLAYER_STATE_SCANNING"
Then, we would issue the hero an order depending on his status: PLAYER_STATUS_IMBA, PLAYER_STATE_NOOB, etc..
If he's Imba, we'll execute code written by the user for an imba player :p
If he's Nooby, we'll execute code written by the user for a noob player.
We can give samples too.

I think I'll write this when I feel like it :p
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
You still need to update this further more. Example Engage towards point?

-Use Bribe's Table system.

-Search hero method doesnt use your UnitAlive function.

-Neither in searchtarget method.

-Use a static if here:

JASS:
if ENABLE_NEAR_TARGET then

-And here

JASS:
ENABLE_PICK_CUSTOM_SKILL
ENABLE_PICK_UP_ITEM
if ENABLE_RETREAT then

-You declare this variable in the init method.

JASS:
local trigger t

and nulls it but still doesnt use it at all.

-Use GroupUtils. Just a tip.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
- Engage towards, point?, to the point of the target, if there are no more enemies, it will return to xyReturn...
- I'll apply that table when it has no more issues, although it's not needed...
- Search hero, I didnt notice that :)...
- searchtarget, no need...
- Static ifs next update...
- GroupUtils, nah, no need coz Im not destroying the group nor releasing it...

There are a lot of things which I did not update such as removing the x & y
local from the searchtarget method, and remove this part...
JASS:
if .follow and not IsUnitType(u, UNIT_TYPE_HERO) then
   call GroupEnumUnitsInRect(bj_lastCreatedGroup, .rct, Filter(function thistype.searchhero))
else
   call GroupEnumUnitsInRect(bj_lastCreatedGroup, .rct, Filter(function thistype.searchFarTarget))
endif
set .target = FirstOfGroup(bj_lastCreatedGroup)

and just replace it with ".target=null" since it's useless coz that will be enumerated when target=null, but Ima test it first coz the .follow may have flaws if I do this...

also, this >>> u!=.target is wrong, it should be .target != .target, since "u" picks many, not only one...

I have issues regarding invisible units, the AI will stick lock to that target,
IDK why, but since .target != .target is there, it will lock a new target...
 
Level 7
Joined
Sep 2, 2011
Messages
350
I imported this system to my map. And for realistic terms, the AI is just
attacking random enemy heroes even with fogs. I know they're computers but
I think its unfair to have that advantage.

Is it possible that AI roams the map until it detects enemies then it will attack?
 
Level 2
Joined
Dec 28, 2010
Messages
21
Nice AI system. I am now creating clever AI for my map and use this system as a ground for futher work. But there were some problems also) I think, that it would be useful to add 1 function, which will turn off AI, even when the unit is still alive. May be some mistakes, but i did it so:

JASS:
public function StopAI takes unit u returns nothing
        call GroupRemoveUnit(ENGAGE_GROUP, u)
        set GROUP_COUNTER_ENGAGE = GROUP_COUNTER_ENGAGE - 1
        call FlushChildHashtable(HASH, GetHandleId(u))
endfunction

And 1 more issue. Is it possible to order them to atack ally unit?
AH, finaly 5/5 :D
 

sentrywiz

S

sentrywiz

Gotta love someone finally pointing out how to make as awesome AI as in Dota. But on the other hand its all Jass, which I sadly expected.
I'm a great fan of others who make AI, but any chance this to be if nothing else, explained in GUI terms?
 
Level 2
Joined
Dec 28, 2010
Messages
21
hmmm... It must be so?
JASS:
static method create takes unit u, real x, real y, rect r returns thistype
        local thistype this = thistype.allocate()
        set .hero = u
        set .xReturn = x
        set .xReturn = x
As it is in your code or:
JASS:
static method create takes unit u, real x, real y, rect r returns thistype
        local thistype this = thistype.allocate()
        set .hero = u
        set .xReturn = x
        set .yReturn = y
 
Level 2
Joined
Dec 28, 2010
Messages
21
There is a little mistake:
Should be:
JASS:
set .xReturn = x
set .yReturn = y
here:
JASS:
static method create takes unit u, real x, real y, rect r returns thistype
        local thistype this = thistype.allocate()
        set .hero = u
        set .xReturn = x
        set .xReturn = x
Or sometimes retreat is not working...)
 
Level 7
Joined
Sep 2, 2011
Messages
350
I'm pretty much happy because of you updating the map once in a while.
I'm using this system to my map. In the previous versions, the timer seem to collide
with my other time systems, but It is now fixed I think.

I still don't get it why my AI doesn't cast any spell :(
 
Level 7
Joined
Sep 2, 2011
Messages
350
Thank you for posting that link and for the quick reply! :D
Oh. I have one question. This is going to be off topic.

~ My jasshelper doesn't run. It just saves my map like a normal editor.
That's why I can't test my map and continue updating it.
 
Level 2
Joined
Dec 28, 2010
Messages
21
I still don't get it why my AI doesn't cast any spell :(
If you want your units to cast spells, you have to make them do it manually. And also you have to stop AI, as I wrote it higher. I can write here 3 functions to add in the system to stop AI and start it again, but spells conditions you will have to create by yourself.
 
Level 7
Joined
Sep 2, 2011
Messages
350
I'm really a noob in codes but I kinda understand GUI. I guess I really
have to make a GUI trigger to make my AI cast some custom spells of mine
that is because I'm no good with codes. That is an alternative.
 
Level 7
Joined
Sep 2, 2011
Messages
350
Sorry about that. I tried my map again without updating it or creating triggers to make the AI cast custom abilities.

My AI do learn those abilities including passive ones. They automatically cast skills
and I'm really surprised because it would change the whole gameplay as if you were
playing with a real player.
 
Level 2
Joined
Dec 28, 2010
Messages
21
I'm really a noob in codes but I kinda understand GUI. I guess I really
have to make a GUI trigger to make my AI cast some custom spells of mine
that is because I'm no good with codes. That is an alternative.

It is unable to make them cast spells while AI in on. You have to switch off it for some time and renew then.
Update: or it works?o_O By me it wasn't working...so that i had to write new code(
 
Last edited:
Top