Moderator
M
Moderator
30 Jan 2012
Bribe: A very well-written AI system. Approved 5/5.
Bribe: A very well-written AI system. Approved 5/5.
/*
===================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.
SetUnitLockTargets
added several targets to engage in a dynamic way (see above)SetHeroToProtect
to follow/protect a specific main hero