//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
HERO | unit | No | |
U | unit | No |
//TESH.scrollpos=0
//TESH.alwaysfold=0
ChildID info:
1 - Boolean, removes from system
2 - Integer, targets picking
3 - Real, duration
4 - Real, xReturn
5 - Real, yReturn
6 - Boolean, saves the hero to the followersID
7 - Unit, saves the target of hero, then passed to the follower
8 - Unit, saves the MAIN hero to be recalled by the follower
//TESH.scrollpos=650
//TESH.alwaysfold=0
[COLOR=Yellow][SIZE=4]System: Engage System v3.2[/SIZE][/COLOR]
[B]Created by: Mckill2009[/B]
This system is for AI usage, just like DotA AI.
Read the instructions carefully inside the code.
[hidden=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
[/jass]
[/hidden]
[box=Parameter explanation:]
1) [ljass]static method SetEngageUnit takes unit u, boolean attackcaptain returns nothing[/ljass]
This is the [I][B][COLOR="Yellow"]FIRST[/COLOR][/B][/I] thing you need to do.
[b]attackcaptain[/b] - sets the attack captain which will follow/guard heroes or hero that will lead the attack group
2) [ljass]static method SetUnitPickItem takes unit u returns nothing[/ljass]
3) [ljass]static method SetUnitRetreat takes unit u returns nothing[/ljass]
Enables Hero to pick items and retreats respectively, these are optional
4) [ljass]static method SetUnitLockTargets takes unit u, integer targets, real atkdur returns nothing[/ljass]
This is the [I][B][COLOR="Yellow"]SECOND[/COLOR][/B][/I] thing you need to do, coz engaging must have targets.
[b]targets[/b] - 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 [COLOR="Yellow"][B][I]Targets[/I][/B][/COLOR] reffers to the [COLOR="Yellow"][I][B]MAIN TARGET[/B][/I][/COLOR]
[b]atkdur[/b] - Sets how long an attacker to lock to a particular target
5) [ljass]static method SetReturnPoints takes unit u, real xReturn, real yReturn returns nothing[/ljass]
This is the [I][B][COLOR="Yellow"]THIRD[/COLOR][/B][/I] thing you need to do, the return locations.
Sets and Changes the Return point of the AI if no more targets around
6) [ljass]static method SetHeroToProtect takes unit follower, unit hero returns nothing[/ljass]
This is optional
7) [ljass]static method RemoveEngagingUnit takes unit u returns nothing[/ljass]
Removes the Engager from the system
[I][B][U]NOTE:[/U][/B][/I]
All the [I][B][COLOR="Yellow"]u[/COLOR][/B][/I] is the unit or the AI that's engaging.
[/box]
[B]How to use in GUI?[/B]
[trigger]
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)
[/trigger]
[hidden=Changelogs]
version 3.2
- Added IsUnitChanneling library for faster use
version 3.1
- [ljass]SetUnitLockTargets[/ljass] added several targets to engage in a dynamic way (see above)
- Added 1 function [ljass]SetHeroToProtect[/ljass] 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
[/hidden]
//TESH.scrollpos=215
//TESH.alwaysfold=0
/*****************************************************************************
*
* Get Closest Widget
* by Spinnaker v2.0.0.0
*
* Special thanks to Troll-Brain
*
******************************************************************************
*
* This snippet contains several functions which returns closest
* widget to given coordinates and passed filter.
*
******************************************************************************
*
* Requirements:
* Snippet optionaly requires IsDestructableTree,
* enabling user to choose if enumerated should be all destructables
* or only tree-type ones, therefore gives option to get closest tree.
*
******************************************************************************
*
* Important:
* Performance drop depends on amount of objects currently on the map
* Snippet functions are designed to reduce the search time as much as
* posible, although it's recommended to use non specific functions
* wisely, especialy if your map contains large numbers of widgets.
*
******************************************************************************
*
* Functions:
* function GetClosestItem takes real x, real y, boolexpr filter returns item
* - Gets single item, nearest passed coordinates
* function GetClosestItemInRange takes real x, real y, real radius, boolexpr filter returns item
* - Returns single item, closest to coordinates in given range
*
* function GetClosestDestructable takes real x, real y, boolean treeOnly, boolexpr filter returns destructable
* - Gets single destructable, nearest passed coordinates
* function GetClosestDestructableInRange takes real x, real y, real radius, boolean treeOnly, boolexpr filter returns destructable
* - Retrieves single destructable, closest to coordinates in given range
*
* function GetClosestUnit takes real x, real y, boolexpr filter returns unit
* - Returns closest unit matching specific filter
* function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
* - Gets nearest unit in range matching specific filter
* function GetClosestUnitInGroup takes real x, real y, group g returns unit
* - Retrieves closest unit in given group
*
* function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group g, boolexpr filter returns nothing
* - Returns up to N nearest units in range of passed coordinates
* function GetClosestNUnitsInGroup takes real x, real y, integer n, group sourceGroup, group destGroup returns nothing
* - Retrieves up to N closest units in passed group
*
*****************************************************************************/
library GetClosestWidget uses optional IsDestructableTree
private keyword Init
globals
private unit array Q
private real array V
private integer C=0
endglobals
struct ClosestWidget extends array
readonly static destructable cDest=null
readonly static item cItem=null
readonly static real distance=0
readonly static real cX=0
readonly static real cY=0
readonly static unit cUnit=null
static boolean cTree=false
static rect initRect=null
static method resetData takes real x, real y returns nothing
set cDest=null
set distance=100000
set cItem=null
set cUnit=null
set cX=x
set cY=y
endmethod
static method enumItems takes nothing returns nothing
local item i=GetEnumItem()
local real dx=GetWidgetX(i)-cX
local real dy=GetWidgetY(i)-cY
set dx = (dx*dx+dy*dy)/10000.
if dx<distance then
set cItem=i
set distance=dx
endif
set i =null
endmethod
static method enumDestructables takes nothing returns nothing
local destructable d=GetEnumDestructable()
local real dx
local real dy
static if LIBRARY_IsDestructableTree then
if cTree and not IsDestructableTree(d) then
return
endif
endif
set dx=GetWidgetX(d)-cX
set dy=GetWidgetY(d)-cY
set dx=(dx*dx+dy*dy)/10000.
if dx<distance then
set cDest=d
set distance=dx
endif
set d=null
endmethod
static method enumUnits takes nothing returns nothing
local unit u=GetEnumUnit()
local real dx=GetUnitX(u)-cX
local real dy=GetUnitY(u)-cY
set dx=(dx*dx+dy*dy)/10000.
if dx<distance then
set cUnit=u
set distance=dx
endif
set u=null
endmethod
static method sortUnits takes integer l, integer r returns nothing
local integer i=l
local integer j=r
local real v=V[(l+r)/2]
loop
loop
exitwhen V[i]>=v
set i=i+1
endloop
loop
exitwhen V[j]<=v
set j=j-1
endloop
if i<=j then
set V[0]=V[i]
set V[i]=V[j]
set V[j]=V[0]
set Q[0]=Q[i]
set Q[i]=Q[j]
set Q[j]=Q[0]
set i=i+1
set j=j-1
endif
exitwhen i>j
endloop
if l<j then
call sortUnits(l,j)
endif
if r>i then
call sortUnits(i,r)
endif
endmethod
static method saveGroup takes nothing returns nothing
local real dx
local real dy
set C=C+1
set Q[C]=GetEnumUnit()
set dx=GetUnitX(Q[C])-cX
set dy=GetUnitY(Q[C])-cY
set V[C]=(dx*dx+dy*dy)/10000.
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
set thistype.initRect=Rect(0,0,0,0)
endmethod
endmodule
function GetClosestItem takes real x, real y, boolexpr filter returns item
local real r=800.
call ClosestWidget.resetData(x,y)
loop
if r>3200. then
call EnumItemsInRect(bj_mapInitialPlayableArea, filter, function ClosestWidget.enumItems)
exitwhen true
else
call SetRect(ClosestWidget.initRect,x-r,y-r,x+r,y+r)
call EnumItemsInRect(ClosestWidget.initRect, filter, function ClosestWidget.enumItems)
exitwhen ClosestWidget.cItem!=null
endif
set r=2*r
endloop
return ClosestWidget.cItem
endfunction
function GetClosestItemInRange takes real x, real y, real radius, boolexpr filter returns item
call ClosestWidget.resetData(x,y)
if radius>=0 then
call SetRect(ClosestWidget.initRect,x-radius,y-radius,x+radius,y+radius)
call EnumItemsInRect(ClosestWidget.initRect, filter, function ClosestWidget.enumItems)
endif
return ClosestWidget.cItem
endfunction
function GetClosestDestructable takes real x, real y, boolean treeOnly, boolexpr filter returns destructable
local real r=800.
call ClosestWidget.resetData(x,y)
set ClosestWidget.cTree=treeOnly
loop
if r>3200. then
call EnumDestructablesInRect(bj_mapInitialPlayableArea, filter, function ClosestWidget.enumDestructables)
exitwhen true
else
call SetRect(ClosestWidget.initRect,x-r,y-r,x+r,y+r)
call EnumDestructablesInRect(ClosestWidget.initRect, filter, function ClosestWidget.enumDestructables)
exitwhen ClosestWidget.cDest!=null
endif
set r=2*r
endloop
return ClosestWidget.cDest
endfunction
function GetClosestDestructableInRange takes real x, real y, real radius, boolean treeOnly, boolexpr filter returns destructable
call ClosestWidget.resetData(x,y)
if radius>=0 then
set ClosestWidget.cTree=treeOnly
call SetRect(ClosestWidget.initRect,x-radius,y-radius,x+radius,y+radius)
call EnumDestructablesInRect(ClosestWidget.initRect, filter, function ClosestWidget.enumDestructables)
endif
return ClosestWidget.cDest
endfunction
function GetClosestUnit takes real x, real y, boolexpr filter returns unit
local real r=80000.
call ClosestWidget.resetData(x,y)
loop
if r>3200. then
call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, filter)
exitwhen true
else
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, r, filter)
exitwhen FirstOfGroup(bj_lastCreatedGroup)!=null
endif
set r=2*r
endloop
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.enumUnits)
return ClosestWidget.cUnit
endfunction
function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
call ClosestWidget.resetData(x,y)
if radius>=0 then
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, radius, filter)
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.enumUnits)
endif
return ClosestWidget.cUnit
endfunction
function GetClosestUnitInGroup takes real x, real y, group g returns unit
call ClosestWidget.resetData(x,y)
call ForGroup(g, function ClosestWidget.enumUnits)
return ClosestWidget.cUnit
endfunction
function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group g, boolexpr filter returns nothing
local integer q=n+1
call ClosestWidget.resetData(x,y)
if radius>=0 then
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, radius, filter)
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.saveGroup)
call ClosestWidget.sortUnits(1,C)
loop
exitwhen n==0 or Q[-n+q]==null
call GroupAddUnit(g, Q[-n+q])
set n =n-1
endloop
endif
set C=0
endfunction
function GetClosestNUnitsInGroup takes real x, real y, integer n, group sourceGroup, group destGroup returns nothing
local integer q=n+1
call ClosestWidget.resetData(x,y)
call ForGroup(sourceGroup, function ClosestWidget.saveGroup)
call ClosestWidget.sortUnits(1,C)
loop
exitwhen n==0 or Q[-n+q]==null
call GroupAddUnit(destGroup, Q[-n+q])
set n=n-1
endloop
set C=0
endfunction
endlibrary
//TESH.scrollpos=12
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.0
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 260 + 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
//TESH.scrollpos=27
//TESH.alwaysfold=0
/**************************************
*
* IsUnitChanneling
* v2.1.0.0
* By Magtheridon96
*
* - Tells whether a unit is channeling or not.
*
* Requirements:
* -------------
*
* - RegisterPlayerUnitEvent by Magtheridon96
* - hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*
* Optional:
* ---------
*
* - UnitIndexer by Nestharus
* - hiveworkshop.com/forums/jass-resources-412/system-unit-indexer-172090/
* - Table by Bribe
* - hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*
* API:
* ----
*
* - function IsUnitChanneling takes unit whichUnit returns boolean
* - Tells whether a unit is channeling or not.
* (This function is only available if you have UnitIndexer)
*
* - function IsUnitChannelingById takes integer unitIndex returns boolean
* - Tells whether a unti is channeling or not given the unit index.
*
**************************************/
library IsUnitChanneling requires optional UnitIndexer, optional Table, RegisterPlayerUnitEvent
private struct OnChannel extends array
static if LIBRARY_UnitIndexer then
static boolean array channeling
else
static if LIBRARY_Table then
static key k
static Table channeling = k
else
static hashtable hash = InitHashtable()
endif
endif
private static method onEvent takes nothing returns nothing
static if LIBRARY_UnitIndexer then
local integer id = GetUnitUserData(GetTriggerUnit())
set channeling[id] = not channeling[id]
else
static if LIBRARY_Table then
local integer id = GetHandleId(GetTriggerUnit())
set channeling.boolean[id] = not channeling.boolean[id]
else
local integer id = GetHandleId(GetTriggerUnit())
call SaveBoolean(hash, 0, id, not LoadBoolean(hash, 0, id))
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function thistype.onEvent)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onEvent)
endmethod
endstruct
static if LIBRARY_UnitIndexer then
function IsUnitChannelingById takes integer id returns boolean
return OnChannel.channeling[id]
endfunction
endif
function IsUnitChanneling takes unit u returns boolean
static if LIBRARY_UnitIndexer then
return OnChannel.channeling[GetUnitUserData(u)]
else
static if LIBRARY_Table then
return OnChannel.channeling.boolean[GetHandleId(u)]
else
return LoadBoolean(OnChannel.hash, 0, GetHandleId(u))
endif
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*
===================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