Name | Type | is_array | initial_value |
h | group | Yes | |
hero | unit | No | |
PASS_DATA | integer | No |
//TESH.scrollpos=1176
//TESH.alwaysfold=0
[B][SIZE="3"][COLOR="Yellow"]StrikeForce v1.6[/COLOR][/SIZE][/B]
- This is a sequel of my [url=http://www.hiveworkshop.com/forums/spells-569/ai-engage-system-v3-2-a-201355/]EnageSystem[/url]
- Documentation is inside the code
[simpletable=Features]
- Creates an attack group (for AI)
- Waits for lost units (optional) and depends on how much time it waits
- Returns to base when no more targets around
- Attacks closest target when engaging on the main target (optional, StrikeForceEx only)
- The hero and its army will not engage if attack size is not complete (StrikeForce only)
- The army will not engage before the attack delay (StrikeForceEx only)
- If hero is dead, all units will regroup at base
- Retreat to base when life is less is optional
[/simpletable]
[simpletable=Note]
- It is recommended to set your AI to a computer player so that they can cast spells independently
- Never Run melee AI scripts nor Start a custom Melee/Capaign AI scripts coz that will conflict with your hero AI
- Read the IsUnitEngageable library very carefully, else this will not work
[/simpletable]
[jass]
library StrikeForce /* v1.6
*************************************************************************************
*
* by mckill2009
* Used by AI or user player to group units for attacking purposes
*
*
*************************************************************************************
*
* */uses/*
*
* */ Table /* www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
* */ GetClosestWidget /* www.hiveworkshop.com/forums/jass-resources-412/snippet-getclosestwidget-204217/
* */ IsUnitEngageable /* www.hiveworkshop.com/forums/jass-resources-412/isunitengageable-220436/
* */ IsUnitChanneling /* www.hiveworkshop.com/forums/jass-resources-412/snippet-isunitchanneling-211254/
*
************************************************************************************
*
* Installation:
* - Copy and Paste the above mentioned libraries and the StrikeForce trigger to your map
* - Follow and read carefully all the instructions in the 'IsUnitEngageable' library
* - Follow the Demo instructions
*
*************************************************************************************
*
* Credits:
* Table by Bribe
* GetClosestWIdget by Spinnaker
* IsUnitChanneling by Magtheridon96
*
*************************************************************************************
*
* StrikeForce API
*
*************************************************************************************
* static method create takes unit hero, boolean retreat, integer maxAttackSize, real attackDelay returns thistype
* - Assigns a hero captain, he will lead the attack of the army
* - Set the retreat to true so that if hero dies all units will return to base (if base is set)
* - If the amount of added unit is less than maxAttackSize, hero will not engage
* - maxAttackSize is the size quantity of the group that's engaging
* - attackDelay is the delay of the next attack
*
* method setHeroTargets takes integer targetID returns nothing
* - 16 Total Players (0-15), see global constants for refference
* - If you want the AI to target only player 1 then >>> call structInstanceName.setHeroTargets(0)
* - If you want the AI to target only heroes then >>> call structInstanceName.setHeroTargets(ATTACK_ENEMY_HEROES_ONLY)
*
* method addUnit takes unit follower returns nothing
* - Adds a unit to the group which increases the maxAttackSize
* - Must be done manually for the attack to happen
*
* method setBase takes real x, real y returns nothing
* - Sets the base which the AI will retreat if the hero dies or when retreat is true
*
* method setMaxAttackSize takes integer maxatksize returns nothing
* - Changes/Sets the maxAttackSize of the group
*
* method setAttackDelay takes real attackDelay returns nothing
* - Changes/Sets the delay of the next attack
*
* method heroWaitForToops takes boolean wait, real waitDuration returns nothing
* - This allows the hero to wait for lost units for (x) seconds
*
* method removeUnit takes unit u returns nothing
* - Removes a unit from the engaging group
*
* method remove takes nothing returns nothing
* - Removes all units in the group that's engaging
*
*************************************************************************************
*
* StrikeForceEx API
*
*************************************************************************************
* static method create takes unit captain, real maxDelay, boolean killCaptainWhenGroupIsEmpty returns thistype
* - Assigns a dummy captain, he will lead the attack but he cant attack
* - It is advised that the dummy walks and doesnt have a locust ability
* - Dummy captain must be a non-hero
* - The dummy can be killed OR not when group is empty
* - This captain be recycled by the next creation of the force
*
* static method createEx takes player p, real xCaptain, real yCaptain, real maxDelay returns thistype
* - Creates a dummy captain automatically at XY coordinate in the form of a peasant ('hpea')
* - Take note that this captain will die automatically once the group is empty
* - This captain cannot be recycled
*
* static method setUnitBase takes unit follower, real xNewBase, real yNewBase returns nothing
* - Sets/changes the retreat/base of individual unit
*
* method addUnit takes unit follower, real xReturn, real yReturn returns nothing
* - Adds a unit to the group and sets the return/retreat/base of the follower
* - Must be done manually for the attack to happen
*
* method setCaptainBase takes real xBase, real yBase returns nothing
* - Sets the group formation at XY of captain when preparing an attack
*
* method delayReset takes real delay returns nothing
* - Change/Sets the delay of the next attack
*
* method captainWaitForTroops takes boolean wait, real waitDuration returns nothing
* - This allows the dummy to wait or stop lost units for (x) seconds
*
* method setTargets takes integer targetID returns nothing
* - 16 Total Players (0-15), see global constants for refference
* - If you want the AI to target only player 2 then >>> set structInstanceName.targets = 2
* - If you want the AI to target all enemies then >>> set structInstanceName.targets = ATTACK_ALL_ENEMIES
*
* method enableRetreat takes boolean b returns nothing
* - Enables the retreat option for units
*
* method remove takes boolean killCaptain returns nothing
* - Removes all units from the attackgroup and kills the dummy (optional)
*
**************************************************************************************
*
* Settings: These Globals are configurables, but the recommended setting
*
**************************************************************************************/
globals
/*******************************************************************
* Used by StrikeForce and StrikeForceEx
********************************************************************/
//Distance offset of following unit and captain
private constant real OFFSET_FROM_CAPTAIN = 250
//Temporary retreats at base if target can't be attacked
private constant real TEMPORARY_RETREAT = 5
//Distance of attacking unit and target, if less than indicated, the attacker will engage without waits
private constant real IS_TARGET_FAR = 490000 //squareroot 700, recommended
//Unit will keep on retreating (or go back to captain) until less than indicated
private constant real RETURN_OFFSET = 360000 //squareroot 600, recommended
//Used for retreating, retreat must be true in both systems
private constant real LIFE = 0.4 //40%
/********************************************************************
* Used only by StrikeForceEx, by NORMAL UNITS
********************************************************************/
//The dummy unit that will lead the attack, default footman, this should be non-hero
private constant integer DUMMY_CAPTAIN_ID = 'hfoo'
//Allows attacker to target closest unit in CLOSEST_TARGET range
private constant boolean ENABLE_CLOSEST_TARGET = false
//Engages closest target, ENABLE_CLOSEST_TARGET must be true
private constant real CLOSEST_TARGET = 600
/********************************************************************
* Global configurables
********************************************************************/
//This setting is recommended and shoudn't be changed
private constant real MOVE_DELAY = 3.0
/********************************************************************
* You may now use these constants to reffer attack priorities e.g.
* For StrikeForce >>> call heroX.setHeroTargets(ATTACK_ALL_ENEMIES)
* For StrikeForceEx >>> set Army.targets = ATTACK_ENEMY_HEROES_ONLY
*
* WARNING: These constants must not be modified!
********************************************************************/
constant integer ATTACK_ALL_ENEMIES = 16
constant integer ATTACK_ENEMY_STRUCTURES_ONLY = 17
constant integer ATTACK_ENEMY_FLYING_ONLY = 18
constant integer ATTACK_ENEMY_HEROES_ONLY = 19
endglobals
/**************************************************************************************
* End of configurable globals
***************************************************************************************/
//=====NON-CONFIGURABLES: NEVER TOUCH THESE GLOBAL BLOCK!=====
globals
private constant integer ATTACK = 851983
private constant integer MOVE = 851986
private constant integer SMART = 851971
private constant integer STOP = 851972
private constant real INTERVAL = 1.0
private unit TempUnit = null
private group TempG = CreateGroup()
private integer countUnit = 0 //counts how many units in a group
//used only by StrikeForce
private Table chk //used if hero is registered already
//used only by StrikeForceEx
private TableArray tba //used only by StrikeForceEx
endglobals
//===Simple group recycling
globals
private group array Grp
private integer indexGrp = 0
endglobals
private function NGroup takes nothing returns group
set indexGrp = indexGrp + 1
if Grp[indexGrp]==null then
set Grp[indexGrp] = CreateGroup()
endif
return Grp[indexGrp]
endfunction
private function RGroup takes group g returns nothing
set Grp[indexGrp] = g
call GroupClear(g)
set indexGrp = indexGrp - 1
endfunction
//==========
//! textmacro EngageS takes tID, heroU
private static method engageSpecificPlayerUnit takes nothing returns boolean
local thistype this = DATA
local integer i = 0
set TempUnit = GetFilterUnit()
if UnitAlive(TempUnit) and IsUnitEnemy($heroU$,GetOwningPlayer(TempUnit)) and IsUnitEngageable(TempUnit) then
if $tID$ < 16 then
loop
if $tID$==i then
return GetOwningPlayer(TempUnit)==Player(i)
endif
exitwhen i==$tID$
set i = i+1
endloop
elseif $tID$==16 then
return true
elseif $tID$==17 then
return IsUnitType(TempUnit,UNIT_TYPE_STRUCTURE)
elseif $tID$==18 then
return IsUnitType(TempUnit,UNIT_TYPE_FLYING)
elseif $tID$==19 then
return IsUnitType(TempUnit,UNIT_TYPE_HERO)
endif
endif
return false
endmethod
//! endtextmacro
//! textmacro Targets takes targetID, tar, unitU
if $tar$ > -1 and $tar$ < 16 then
if GetOwningPlayer($unitU$)==Player($tar$) or not IsUnitEnemy($unitU$, Player($tar$)) then
debug call BJDebugMsg("ERROR: can't target this Player!")
else
set $targetID$ = $tar$
endif
elseif $tar$ > 15 and $tar$ < 20 then
set $targetID$ = $tar$
else
debug call BJDebugMsg("ERROR: Please input only from 0 to 19!")
endif
//! endtextmacro
private function AttackMoveOrders takes integer order returns boolean
return (order==ATTACK or order==MOVE)
endfunction
private function UnitAlive takes unit u returns boolean
return not IsUnitType(u, UNIT_TYPE_DEAD) and u!=null
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
private function IsTargetFar takes unit u, unit u2 returns boolean
return (GetDistance(GetUnitX(u), GetUnitY(u), GetUnitX(u2), GetUnitY(u2))) > (IS_TARGET_FAR) //this is 700 range
endfunction
private function AttackNow takes unit u, unit target, real x, real y returns nothing
if not AttackMoveOrders(GetUnitCurrentOrder(u)) then
if IsUnitType(u, UNIT_TYPE_SLEEPING) then
call IssueTargetOrderById(u, ATTACK, target)
else
call IssuePointOrderById(u, ATTACK, x, y)
endif
endif
endfunction
private function EngageCloseTargets takes nothing returns boolean
return UnitAlive(GetFilterUnit()) and IsUnitEnemy(TempUnit,GetOwningPlayer(GetFilterUnit())) and IsUnitEngageable(GetFilterUnit())
endfunction
private function RandomAngle takes nothing returns real
return GetRandomReal(0,6.28)
endfunction
private function LifeLessThanMaxLife takes unit u returns boolean
return GetWidgetLife(u) < (GetUnitState(u, UNIT_STATE_MAX_LIFE) * LIFE)
endfunction
private function MoveToCaptain takes unit u, real x, real y, real offset returns nothing
call IssuePointOrderById(u, ATTACK, x+offset*Cos(RandomAngle()), y+offset*Sin(RandomAngle()))
endfunction
private function ReturnToBase takes unit u, real xBase, real yBase returns nothing
if (GetDistance(GetUnitX(u), GetUnitY(u), xBase, yBase)) > (RETURN_OFFSET) then
call IssuePointOrderById(u, MOVE, xBase, yBase)
endif
endfunction
private function NoEnemyUnits takes unit u, real radius returns boolean
local unit first
call GroupEnumUnitsInRange(TempG, GetUnitX(u), GetUnitY(u), radius, null)
loop
set first = FirstOfGroup(TempG)
exitwhen first==null
if UnitAlive(first) and IsUnitEnemy(first, GetOwningPlayer(u)) and not UnitIsSleeping(first) then
return false
endif
call GroupRemoveUnit(TempG, first)
endloop
return true
endfunction
private function GroupRetreat takes unit u, real captainXX, real captainYY, real baseXX, real baseYY returns nothing
if LifeLessThanMaxLife(u) then
call ReturnToBase(u, baseXX, baseYY)
else
call MoveToCaptain(u, captainXX, captainYY, OFFSET_FROM_CAPTAIN)
endif
endfunction
private function CountUnits takes nothing returns nothing
if UnitAlive(GetEnumUnit()) then
set countUnit = countUnit + 1
endif
endfunction
private function UnitsInGroup takes group grp returns integer
call ForGroup(grp, function CountUnits)
return countUnit
endfunction
//=====END OF NON-CONFIGURABLE BLOCK=====
private struct init
private static method onInit takes nothing returns nothing
set chk = Table.create()
set tba = TableArray[0x2000]
endmethod
endstruct
/*************************************************************************************
* StrikeForce: used by HEROES, assigns as captain who leads
**************************************************************************************/
struct StrikeForce
private unit hero
private unit target
private unit closeTarget
private real baseX
private real baseY
private real moveDelay
private real minAttackDelay //starts with 0, max is attackDelay
private real maxAttackDelay
private real heroWaitInterval
private real heroEngageTime //used only if heroWaitsTroop is true
private real heroWaitTroopDuration
private real tempRetreat
private real heroStopGroup
private integer minAtkSize
private integer maxAtkSize
private integer handleID
private integer targetPlayerID
private boolean retreat
private boolean onSF
private boolean heroWaitsTroop
private boolean heroWaitOneTime
private group heroGroup
private static thistype DATA
private static timer t = CreateTimer()
private static integer instance = 0
private static integer array insAR
//! runtextmacro EngageS(".targetPlayerID",".hero")
private method destroy takes nothing returns nothing
call RGroup(.heroGroup)
set .closeTarget = null
set .hero = null
set .target = null
set .heroGroup = null
call .deallocate()
endmethod
//runs every INTERVAL, units return to base when hero is dead or target cannot be attacked
private static method groupReturnToBase takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this
if UnitAlive(u) then
set this = DATA
call ReturnToBase(u, .baseX, .baseY)
endif
set u = null
endmethod
//runs every INTERVAL, for re-grouping if hero finds a new target or hero waits troops or maxAttackSize is not met
private static method groupFormation takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this
local real hX
local real hY
if UnitAlive(u) then
set this = DATA
set hX = GetUnitX(.hero)
set hY = GetUnitY(.hero)
if .retreat then
call GroupRetreat(u, hX, hY, .baseX, .baseY)
else
call MoveToCaptain(GetEnumUnit(), hX, hY, OFFSET_FROM_CAPTAIN)
endif
endif
set u = null
endmethod
//runs every MOVE_DELAY
private static method groupAttack takes nothing returns nothing
local unit u = GetEnumUnit() //u is the follower of the hero
local thistype this
local real hX
local real hY
local real xTar
local real yTar
local real distHeroFollower
local real distTargetFollower
local real xFollower
local real yFollower
if UnitAlive(u) then
if not IsUnitChanneling(u) then
set this = DATA //DATA taken from looper
set hX = GetUnitX(.hero)
set hY = GetUnitY(.hero)
set xTar = GetUnitX(.target)
set yTar = GetUnitY(.target)
set xFollower = GetUnitX(u)
set yFollower = GetUnitY(u)
set distTargetFollower = GetDistance(xTar, yTar, xFollower, yFollower)
set distHeroFollower = GetDistance(hX, hY, xFollower, yFollower)
if .retreat then
if LifeLessThanMaxLife(u) then
call ReturnToBase(u, .baseX, .baseY)
else
if distHeroFollower > RETURN_OFFSET then
call MoveToCaptain(u, hX, hY, OFFSET_FROM_CAPTAIN)
else
call AttackNow(u, .target, xTar, yTar)
endif
endif
else
if distHeroFollower > RETURN_OFFSET then
call MoveToCaptain(u, hX, hY, OFFSET_FROM_CAPTAIN)
else
call AttackNow(u, .target, xTar, yTar)
endif
endif
endif
else
set .minAtkSize = .minAtkSize - 1
call GroupRemoveUnit(.heroGroup, u)
endif
set u = null
endmethod
//runs every INTERVAL
private method heroEngaging takes nothing returns nothing
if not IsUnitChanneling(.hero) then
//hero attacks
call AttackNow(.hero, .target, GetUnitX(.target), GetUnitY(.target))
//hero group attack, not the hero
call ForGroup(.heroGroup,function thistype.groupAttack)
endif
endmethod
//runs every INTERVAL
private method heroMovement takes nothing returns nothing
set .moveDelay = .moveDelay + INTERVAL
if .moveDelay > MOVE_DELAY then
set .moveDelay = 0
//movement of hero and group, default every 3 seconds
//===Hero retreats, this should be true if hero wants to retreat
if .retreat then
if LifeLessThanMaxLife(.hero) then
call ReturnToBase(.hero, .baseX, .baseY)
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
else
call .heroEngaging()
endif
else
call .heroEngaging()
endif
endif
endmethod
//runs every INTERVAL
private method findTarget takes nothing returns nothing
set .target = GetClosestUnit(GetUnitX(.hero), GetUnitY(.hero), Filter(function thistype.engageSpecificPlayerUnit))
set .minAttackDelay = 0 //resets the delay when engaging the next target
set .heroWaitInterval = 0
if .target==null then
call ReturnToBase(.hero, .baseX, .baseY)
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
else
call ForGroup(.heroGroup, function thistype.groupFormation)
endif
endmethod
//this is the hero moving, not the units, runs every INTERVAL
private static method looper takes nothing returns nothing
local thistype this
local integer i = 0
loop
set i = i+1
set this = insAR[i]
if .onSF then
if UnitAlive(.hero) then
if .minAtkSize >= .maxAtkSize then
set .minAttackDelay = .minAttackDelay + INTERVAL
if .minAttackDelay > .maxAttackDelay then
set DATA = this
if UnitAlive(.target) then
if IsUnitEngageable(.target) then
if .heroWaitsTroop then
set .heroWaitInterval = .heroWaitInterval + INTERVAL
if .heroEngageTime > .heroWaitInterval then
if .heroWaitTroopDuration > .heroWaitInterval then
call .heroMovement()
else
if not IsUnitChanneling(.hero) then
if IsTargetFar(.hero, .target) and NoEnemyUnits(.hero, 600) then
if .heroWaitOneTime then
call IssueImmediateOrderById(.hero, STOP)
call ForGroup(.heroGroup, function thistype.groupFormation)
set .heroWaitOneTime = false
endif
endif
endif
endif
else
set .heroWaitOneTime = true
set .heroWaitInterval = 0
endif
else
call .heroMovement()
endif
else
//if unit cant be attacked, hero and the group will return to base
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
call ReturnToBase(.hero, .baseX, .baseY)
set .tempRetreat = .tempRetreat + INTERVAL
if .tempRetreat > TEMPORARY_RETREAT then
set .tempRetreat = 0
call .findTarget() //this finds another target
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
endif
endif
else
//if target is dead or null, hero will return to base or find another target
//from UnitAlive(.target)
call .findTarget()
endif
endif //no else from .minAttackDelay > .maxAttackDelay
else
//from .minAtkSize >= .maxAtkSize
set .heroStopGroup = .heroStopGroup + INTERVAL
if .heroStopGroup > 3 then
set .heroStopGroup = 0
if not IsUnitChanneling(.hero) then
if IsUnitEngageable(.target) then
call IssuePointOrderById(.hero, ATTACK, GetUnitX(.hero), GetUnitY(.hero))
call ForGroup(.heroGroup, function thistype.groupFormation)
else
call ReturnToBase(.hero, .baseX, .baseY)
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
endif
endif
endif
endif
else
//Hero is dead, from UnitAlive(.hero)
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
endif
else
call .destroy()
set insAR[i] = insAR[instance]
set insAR[instance] = this
set i = i - 1
set instance = instance - 1
if instance==0 then
call PauseTimer(t)
endif
endif
exitwhen i==instance
endloop
endmethod
//StrikeForce API:
static method create takes unit hero, boolean retreat, integer maxAttackSize, real attackDelay returns thistype
local thistype this
if chk.has(GetHandleId(hero)) then
debug call BJDebugMsg("StrikeForce.create ERROR: "+GetUnitName(hero)+" is already registered!")
else
if IsUnitType(hero,UNIT_TYPE_HERO) then
set this = allocate()
set chk[GetHandleId(hero)] = this
set .heroGroup = NGroup()
set .hero = hero
set .handleID = GetHandleId(hero)
set .target = null
set .closeTarget = null
set .moveDelay = 0
set .minAttackDelay = 0
set .maxAttackDelay = attackDelay
set .minAtkSize = 0
set .maxAtkSize = maxAttackSize
set .retreat = retreat
set .onSF = true
set .heroWaitInterval = 0
set .heroEngageTime = 0
set .tempRetreat = 0
set .heroWaitTroopDuration = 0
set .heroStopGroup = 0
set .heroWaitsTroop = false
set .heroWaitOneTime = true
if instance==0 then
call TimerStart(t,INTERVAL,true,function thistype.looper)
endif
set instance = instance + 1
set insAR[instance] = this
call RemoveGuardPosition(hero)
else
debug call BJDebugMsg("StrikeForce.create ERROR: Please register a Hero Type unit!")
endif
endif
return this
endmethod
method setHeroTargets takes integer targetID returns nothing
//! runtextmacro Targets(".targetPlayerID","targetID",".hero")
endmethod
method addUnit takes unit follower returns nothing
if follower!=.hero then
call GroupAddUnit(.heroGroup,follower)
call RemoveGuardPosition(follower)
call MoveToCaptain(follower, GetUnitX(.hero), GetUnitY(.hero), OFFSET_FROM_CAPTAIN)
set .minAtkSize = .minAtkSize + 1
endif
endmethod
method setBase takes real x, real y returns nothing
set .baseX = x
set .baseY = y
endmethod
method setMaxAttackSize takes integer maxatksize returns nothing
set .maxAtkSize = maxatksize
endmethod
method setAttackDelay takes real attackDelay returns nothing
set .maxAttackDelay = attackDelay
endmethod
method heroWaitForToops takes boolean wait, real waitDuration returns nothing
set .heroWaitsTroop = wait
set .heroEngageTime = waitDuration*3
set .heroWaitTroopDuration = .heroEngageTime-waitDuration
endmethod
method removeUnit takes unit u returns nothing
if IsUnitInGroup(u, .heroGroup) then
call GroupRemoveUnit(.heroGroup, u)
set .minAtkSize = .minAtkSize - 1
call IssuePointOrderById(u, MOVE, .baseX, .baseY)
else
debug call BJDebugMsg("StrikeForce.removeUnit ERROR: "+GetUnitName(u)+" is not in this group")
endif
endmethod
method remove takes nothing returns nothing
set .onSF = false
call chk.remove(GetHandleId(.hero))
endmethod
endstruct
/*************************************************************************************
* StrikeForceEx: used for NORMAL UNITS, assigns a dummy captain
**************************************************************************************/
struct StrikeForceEx
private unit captain
private unit target
private unit closestTarget
private real maxDelay
private real minDelay
private real xBack
private real yBack
private real moveWait //use 3.0
private real dummyWaitInterval
private real captainEngageTime
private real captainWaitTime
private real tempRetreat
private integer targetPlayerID
private group dummyGroup
private boolean on
private boolean killCaptain
private boolean retreat
private boolean captainWaitsTroop
private static thistype DATA
private static integer index = 0
private static integer array indexAR
private static timer t = CreateTimer()
//! runtextmacro EngageS(".targetPlayerID",".captain")
private method destroy takes nothing returns nothing
if .killCaptain then
call KillUnit(.captain)
endif
call RGroup(.dummyGroup)
set .captain = null
set .target = null
set .closestTarget = null
set .dummyGroup = null
call .deallocate()
endmethod
//this is only used if the group is about to be destroyed
private static method grpReturn takes nothing returns nothing
local unit u = GetEnumUnit()
local real xRet
local real yRet
if UnitAlive(u) then
set xRet = tba[0].real[GetHandleId(u)]
set yRet = tba[1].real[GetHandleId(u)]
if GetDistance(xRet, yRet, GetUnitX(u), GetUnitY(u)) > RETURN_OFFSET then
call IssuePointOrderById(u, MOVE, xRet, yRet)
endif
endif
set u = null
endmethod
private static method groupFormation takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this
local real x
local real y
if UnitAlive(u) then
set this = DATA
set x = GetUnitX(.captain)
set y = GetUnitY(.captain)
if .retreat then
if LifeLessThanMaxLife(u) then
call ReturnToBase(u, tba[0].real[GetHandleId(u)], tba[1].real[GetHandleId(u)])
else
call MoveToCaptain(u, x, y, OFFSET_FROM_CAPTAIN)
endif
else
call MoveToCaptain(u, x, y, OFFSET_FROM_CAPTAIN)
endif
endif
set u = null
endmethod
//runs every MOVE_DELAY
private static method groupEngagingEx takes unit u, thistype this, real xCaptain, real yCaptain returns nothing
local real xFol
local real yFol
local real xTar
local real yTar
local real distanceFromCaptain
if not IsUnitChanneling(u) then
set xFol = GetUnitX(u)
set yFol = GetUnitY(u)
set xTar = GetUnitX(.target)
set yTar = GetUnitY(.target)
set distanceFromCaptain = GetDistance(xFol, yFol, xCaptain, yCaptain)
if ENABLE_CLOSEST_TARGET then
if UnitAlive(.closestTarget) and IsUnitEngageable(.closestTarget) then
call AttackNow(u, .closestTarget, GetUnitX(.closestTarget), GetUnitY(.closestTarget))
else
//if captain is far, unit will go near captain, else attack the target
if distanceFromCaptain > 600000 then //600
call MoveToCaptain(u, xCaptain, yCaptain, OFFSET_FROM_CAPTAIN)
else
call AttackNow(u, .target, xTar, yTar)
endif
endif
else
//if captain is far, unit will go near captain, else attack the target
if distanceFromCaptain > 600000 then //600
call MoveToCaptain(u, xCaptain, yCaptain, OFFSET_FROM_CAPTAIN)
else
call AttackNow(u, .target, xTar, yTar)
endif
endif
endif
endmethod
//runs every MOVE_DELAY
private static method grpEngaging takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this
local real x
local real y
if UnitAlive(u) then
set this = DATA
set x = GetUnitX(.captain)
set y = GetUnitY(.captain)
if .retreat then
if LifeLessThanMaxLife(u) then
call ReturnToBase(u, tba[0].real[GetHandleId(u)], tba[1].real[GetHandleId(u)])
else
call thistype.groupEngagingEx(u, this, x, y)
endif
else
call thistype.groupEngagingEx(u, this, x, y)
endif
else
call GroupRemoveUnit(.dummyGroup, u)
endif
set u = null
endmethod
//runs every INTERVAL
private method captainEngaging takes nothing returns nothing
local real x
local real y
set .moveWait = .moveWait + INTERVAL
if .moveWait > MOVE_DELAY then
set .moveWait = 0
set x = GetUnitX(.captain)
set y = GetUnitY(.captain)
set DATA = this
if ENABLE_CLOSEST_TARGET then
if UnitAlive(.closestTarget) and IsUnitEngageable(.closestTarget) then
call IssuePointOrderById(.captain, MOVE, GetUnitX(.closestTarget), GetUnitY(.closestTarget))
else
set TempUnit = .captain
set .closestTarget = GetClosestUnitInRange(x, y, CLOSEST_TARGET, Filter(function EngageCloseTargets))
if .closestTarget==null then
call ForGroup(.dummyGroup, function thistype.grpEngaging)
call IssuePointOrderById(.captain, MOVE, GetUnitX(.target), GetUnitY(.target))
endif
endif
else
call ForGroup(.dummyGroup, function thistype.grpEngaging)
call IssuePointOrderById(.captain, MOVE, GetUnitX(.target), GetUnitY(.target))
endif
endif
endmethod
//runs from looper if target is dead or not engageable
private method findCaptainNewTarget takes nothing returns nothing
set .minDelay = 0
set .target = GetClosestUnit(GetUnitX(.captain), GetUnitY(.captain), Filter(function thistype.engageSpecificPlayerUnit))
if .target==null then
//captain goes back to original location
call IssuePointOrderById(.captain, MOVE, .xBack, .yBack)
//individual group go back to their respective locations
call ForGroup(.dummyGroup, function thistype.grpReturn)
else
call ForGroup(.dummyGroup, function thistype.groupFormation)
endif
endmethod
//runs every INTERVAL
private static method looper takes nothing returns nothing
local thistype this
local integer i = 0
loop
set i = i+1
set this = indexAR[i]
if .on and UnitAlive(.captain) then
set .minDelay = .minDelay + INTERVAL
if .minDelay > .maxDelay then
set DATA = this
set countUnit = 0
if UnitsInGroup(.dummyGroup) > 0 then //checks if the group is empty or not
if UnitAlive(.target) then
if IsUnitEngageable(.target) then
if .captainWaitsTroop then
set .dummyWaitInterval = .dummyWaitInterval + INTERVAL
if .captainEngageTime > .dummyWaitInterval then
if .captainWaitTime > .dummyWaitInterval then
call .captainEngaging()
else
if IsTargetFar(.captain, .target) then
call IssueImmediateOrderById(.captain, STOP)
call ForGroup(.dummyGroup, function thistype.groupFormation)
endif
endif
else
set .dummyWaitInterval = 0
endif
else
call .captainEngaging()
endif
else
call ForGroup(.dummyGroup, function thistype.grpReturn)
call IssuePointOrderById(.captain, MOVE, .xBack, .yBack)
set .tempRetreat = .tempRetreat + INTERVAL
if .tempRetreat > TEMPORARY_RETREAT then
set .tempRetreat = 0
call .findCaptainNewTarget()
call ForGroup(.dummyGroup, function thistype.grpReturn)
endif
endif
else //from UnitAlive(.target)
call .findCaptainNewTarget()
endif
else //from UnitsInGroup(.dummyGroup)
if .killCaptain then
set .on = false
else
call SetUnitPosition(.captain, .xBack, .yBack)
endif
endif
endif
else //from .on and UnitAlive(.captain)
//this order returns all units in the group to it's base
set DATA = this
call ForGroup(.dummyGroup, function thistype.grpReturn)
//deallocating instance
call .destroy()
set indexAR[i] = indexAR[index]
set indexAR[index] = this
set index = index - 1
set i = i-1
if index==0 then
call PauseTimer(t)
endif
endif
exitwhen i==index
endloop
endmethod
//StrikeForceEx API:
static method create takes unit captain, real maxDelay, boolean killCaptainWhenGroupIsEmpty returns thistype
local thistype this
if IsUnitType(captain, UNIT_TYPE_HERO) then
debug call BJDebugMsg("StrikeForceEx (create) ERROR: "+GetUnitName(captain)+" must be a non-hero!")
else
set this = allocate()
set .captain = captain
set .target = null
set .closestTarget = null
set .minDelay = 0
set .maxDelay = maxDelay
set .dummyGroup = NGroup() //CreateGroup()
set .on = true
set .killCaptain = killCaptainWhenGroupIsEmpty
set .moveWait = 0
set .targetPlayerID = 0
set .dummyWaitInterval = 0
set .captainEngageTime = 0
set .captainWaitTime = 0
set .tempRetreat = 0
set .captainWaitsTroop = false
set .retreat = false
call SetUnitUseFood(captain, false)
if index==0 then
call TimerStart(t,INTERVAL,true,function thistype.looper)
endif
set index = index + 1
set indexAR[index] = this
call RemoveGuardPosition(captain)
call SetUnitInvulnerable(captain,true)
call ShowUnit(captain,false)
endif
return this
endmethod
static method createEx takes player p, real xCaptain, real yCaptain, real maxDelay returns thistype
local thistype this
local unit u = CreateUnit(p, DUMMY_CAPTAIN_ID, xCaptain, yCaptain, 0)
call UnitAddAbility(u, 'Abun')
set this = create(u, maxDelay, true)
set u = null
return this
endmethod
static method setUnitBase takes unit follower, real xNewBase, real yNewBase returns nothing
set tba[0].real[GetHandleId(follower)] = xNewBase
set tba[1].real[GetHandleId(follower)] = yNewBase
endmethod
method addUnit takes unit follower, real xReturn, real yReturn returns nothing
if follower!=.captain then
set tba[0].real[GetHandleId(follower)] = xReturn
set tba[1].real[GetHandleId(follower)] = yReturn
call GroupAddUnit(.dummyGroup, follower)
call RemoveGuardPosition(follower)
call MoveToCaptain(follower, GetUnitX(.captain), GetUnitY(.captain), OFFSET_FROM_CAPTAIN)
endif
endmethod
method setCaptainBase takes real xBase, real yBase returns nothing
set .xBack = xBase
set .yBack = yBase
endmethod
method delayReset takes real delay returns nothing
set .minDelay = 0
set .maxDelay = delay
endmethod
method captainWaitForTroops takes boolean wait, real waitDuration returns nothing
set .captainWaitsTroop = wait
set .captainEngageTime = waitDuration*3
set .captainWaitTime = .captainEngageTime-waitDuration
endmethod
method setTargets takes integer targetID returns nothing
//! runtextmacro Targets(".targetPlayerID","targetID",".captain")
endmethod
method enableRetreat takes boolean b returns nothing
set .retreat = b
endmethod
method remove takes boolean killCaptain returns nothing
set .killCaptain = killCaptain
set .on = false
endmethod
endstruct
endlibrary
[/jass]
[box=The global constants for easier reading]
[jass]
constant integer ATTACK_ALL_ENEMIES = 16
constant integer ATTACK_ENEMY_STRUCTURES_ONLY = 17
constant integer ATTACK_ENEMY_FLYING_ONLY = 18
constant integer ATTACK_ENEMY_HEROES_ONLY = 19
[/jass]
You may now use these constants to reffer attack priorities e.g.
- For StrikeForce [ljass]call heroX.setHeroTargets(ATTACK_ALL_ENEMIES)[/ljass]
- For StrikeForceEx [ljass]call Army.setTargets(ATTACK_ENEMY_HEROES_ONLY)[/ljass]
[/box]
[hidden=Demo and Tutorials]
[hidden=StrikeFore Demo]
[jass]
scope StrikeForceDemo
globals
//you may declare this also as a local, but recommended as global
//coz you may want your hero to be removed from the system
StrikeForce heroX
private constant player P = Player(1) //player 1 in GUI
endglobals
struct StrikeForceDemo
static method strikeForce takes nothing returns nothing
//this block is NOT related, just creating units
local integer PreplacedUnits = 5
local integer ArmySize = 10
local integer i = PreplacedUnits
local unit bm = CreateUnit(P, 'Hblm', -1632, 1665, 0)
local unit grunt
call SetHeroLevel(bm, 30, true)
//call SetWidgetLife(bm, 2)
//=============================================================
//once the hero is available, let's put him in the system
//create it first >>> unit hero, boolean retreat, integer maxAttackSize, real attackDelay
//attackDelay means the time delay for the next target
//maxAttackSize means how many units in the group
set heroX = heroX.create(bm, false, ArmySize, 5)
//if one or more troop is far from hero the hero may stop and wait for them if the target is still far
//method heroWaitForToops takes boolean wait, real waitDuration returns nothing
call heroX.heroWaitForToops(true, 5)
//setting the hero targets, ATTACK_ALL_ENEMIES is inside the system global constants
call heroX.setHeroTargets(ATTACK_ALL_ENEMIES)
//set the base, when no more enemies the group will return to base
//this is also used for retreating
call heroX.setBase(-1632, 1665)
//creating an army for the hero
loop
exitwhen i==0
set grunt = CreateUnit(P, 'ogru', 300, 2500, 0) //grunt
call SetWidgetLife(grunt, 5)
//creating a grunt and add to the system
//this increases the attack size
call heroX.addUnit(grunt)
set i = i - 1
endloop
//Text display only, NOT related to the system
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "")
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "StrikeForce vJass is ON, this message is NOT related to the system")
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "ArmySize is " + I2S(ArmySize))
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "Added Unit is " + I2S(PreplacedUnits))
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "Train units to be added to the force until AttackSize is met")
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "Heal the orcs then they will join the group")
//NOTE: if you set the maxAttackSize and setAttackDelay to 0, the the hero will keep on engaging
//changes or sets the maxAttackSize of the group
//call heroX.setMaxAttackSize(0) //optional since we already created this above
//sets the delay time to engage the next target, I'm not gonna use this ATM
//call heroX.setAttackDelay(0) //optional since we already created this above
endmethod
static method train takes nothing returns nothing
call heroX.addUnit(GetTrainedUnit())
endmethod
static method onInit takes nothing returns nothing
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_TRAIN_FINISH, function thistype.train)
call TimerStart(CreateTimer(), 0, false, function thistype.strikeForce) //hero captain
endmethod
endstruct
endscope
[/jass]
[/hidden]
[hidden=StrikeForceEx Demo in GUI]
[trigger]
StrikeForceEx Demo GUI
Events
Time - Elapsed game time is 0.00 seconds
Conditions
Actions
-------- let's create the StrikeForceEx first --------
-------- you may use a global instance but Im not gonna explain it here, try the vJass StrikeForce --------
Custom script: local StrikeForceEx AI
-------- creating the dummy captain, setting the player, coordinates and how long the AI will wait to engage the target, in this case 10 seconds --------
-------- you may use also the create but createEx creates a captain automatically --------
Custom script: set AI = AI.createEx(Player(1), -2331, 1572, 10)
-------- setting the targets, the ATTACK_ENEMY_HEROES_ONLY is a constant variable indicated in the srcript --------
Custom script: call AI.setTargets(ATTACK_ENEMY_HEROES_ONLY)
-------- setting where the dummy captain will return when there'e no more heroes in the map --------
-------- sample in the middle of the map --------
Custom script: call AI.setCaptainBase(0, 0)
-------- retreat function is a NEW method enables the unit to retreat when life is low --------
Custom script: call AI.enableRetreat(true)
-------- waiting for troops is a new option, allows the captain to stop in x seconds just to group/wait lost units --------
Custom script: call AI.captainWaitForTroops(true, 7)
-------- udg_PASS_DATA is an optional global integer variable used to pass/preserve the index of the AI data during group itinerations --------
Custom script: set udg_PASS_DATA = AI
Unit Group - Pick every unit in (Units owned by Player 2 (Blue) matching (((Matching unit) is A Hero) Equal to False)) and do (Actions)
Loop - Actions
-------- this is GUI version, so this is normal, creating the local again --------
Custom script: local StrikeForceEx AI
-------- passing the data information to the AI instance --------
Custom script: set AI = udg_PASS_DATA
-------- now we can add members to the group --------
-------- the -1953 and 1299 are x and y of the map, whre individual units return if retreat is on --------
Custom script: call AI.addUnit(GetEnumUnit(), -1953, 1299)
-------- This is only a test if retreat is working, but this is completely optional --------
Custom script: call SetWidgetLife(GetEnumUnit(), 5)
[/trigger]
[/hidden]
[/hidden]
[hidden=Credits]
- Table by Bribe
- GetClosestWidget by Spinnaker
- IsUnitChanneling by Magtheridon96
- RegisterPlayerUnitEvent by Magtheridon96
Others:
- PurgeandFire111 for the distance issue
[/hidden]
[hidden=Changelogs]
v1.6
- Improved documentation
- Fixed dummy captain that used food
- Some constant globals turned to struct method instance
- Added retreat function for StrikeforceEx
- Added wait function for both
v1.5a
- Improved documentation
- FLYIER changed to FLYING
v1.5
- Added global integer constants to reffer attack priority
- Player numbers is now 0-15 instead of 1-16
- group nulled
- createEx has been simplified
v1.4
- Added new function createEx which enables to automatically creates a dummy captain
v1.3
- Added GroupUtils library by Rising_Dusk
- Fixed distance issues pointed out by PurgeandFire111
- Added more documentation
v1.2a
- Fixed a minor bug from v1.2, StrikeForceEx dummy doesnt wait properly
v1.2
- Added IsUnitChanneling for faster execution of non-movements of AI
- Removed the first changelog from v1.1 coz it bugs in my The Harvest map
v1.1
- Added a function that the attacker can attack ground or air units, else he will return to base
- The wait for hero/dummy function is more accurate now
- TargetID's is more efficient
[/hidden]
//TESH.scrollpos=31
//TESH.alwaysfold=0
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 3.1.0.1
One map, one hashtable. Welcome to NewTable 3.1
This library was originally called NewTable so it didn't conflict with
the API of Table by Vexorian. However, the damage is done and it's too
late to change the library name now. To help with damage control, I
have provided an extension library called TableBC, which bridges all
the functionality of Vexorian's Table except for 2-D string arrays &
the ".flush(integer)" method. I use ".flush()" to flush a child hash-
table, because I wanted the API in NewTable to reflect the API of real
hashtables (I thought this would be more intuitive).
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private integer less = 0 //Index generation for TableArrays (below 0).
private integer more = 8190 //Index generation for Tables.
//Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
private hashtable ht = InitHashtable()
private key sizeK
private key listK
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return sizeK
endmethod
static method operator list takes nothing returns Table
return listK
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
implement realm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
//set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key)
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb)
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key)
endmethod
//Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
//local Table tb = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set this = more + 1
set more = this
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this) //Clear hashed memory
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = tb[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set this = less - array_size
set less = this
else
set tb[0] = tb[this] //Set the last destroyed to the last-last destroyed
call tb.remove(this) //Clear hashed memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//This magic method enables two-dimensional[array][syntax] for Tables,
//similar to the two-dimensional utility provided by hashtables them-
//selves.
//
//ta[integer a].unit[integer b] = unit u
//ta[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; I assume you call .flush()
//if you want it flushed too. This is a public method so that you don't
//have to loop through all TableArray indices to flush them if you don't
//need to (ie. if you were flushing all child-keys as you used them).
//
method destroy takes nothing returns nothing
local Table tb = dex.size[this.size]
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if tb == 0 then
//Create a Table to index recycled instances with their array size
set tb = Table.create()
set dex.size[this.size] = tb
endif
call dex.size.remove(this) //Clear the array size from hash memory
set tb[this] = tb[0]
set tb[0] = this
endmethod
private static Table tempTable
private static integer tempEnd
//Avoids hitting the op limit
private static method clean takes nothing returns nothing
local Table tb = .tempTable
local integer end = tb + 0x1000
if end < .tempEnd then
set .tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = .tempEnd
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen tb == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
set .tempTable = this
set .tempEnd = this + this.size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call this.destroy()
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//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=800.
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=0
//TESH.alwaysfold=0
library IsUnitEngageable /* v1.2
************************************************************************************
* by mckill2009
************************************************************************************
*
* Installation:
* - Copy this script to your trigger editor via custom text
* - Copy the custom abilty from the object editor named 'IsUnitEngageable' to your map
* - Make sure you change also the rawID below (SPELL_ID) if you change the rawID of your imported ability
*
************************************************************************************
*
* function IsUnitEngageable takes unit u returns boolean
* Undetects units that is:
* - Invulnerable
* - Burrowed
* - Invisible
* - Ethereal
* - Have locust
*
************************************************************************************/
globals
/************************************************************************************
*
* Change the SPELL_ID according to the ability object named 'IsUnitEngageable'
*
*************************************************************************************/
private constant integer SPELL_ID = 'A000' //based on chain lightning
/************************************************************************************
*
* Never touch anything below this block
*
*************************************************************************************/
private constant integer DUMMY_ID = 'hmpr'
private unit DUMMY
endglobals
struct onInit
static method onInit takes nothing returns nothing
set DUMMY = CreateUnit(Player(15), DUMMY_ID, 0,0,0)
call UnitAddAbility(DUMMY, SPELL_ID)
call UnitAddAbility(DUMMY, 'Aloc')
call UnitRemoveAbility(DUMMY, 'Amov')
call ShowUnit(DUMMY, false)
endmethod
endstruct
function IsUnitEngageable takes unit u returns boolean
call SetUnitPosition(DUMMY, GetUnitX(u), GetUnitY(u))
return IssueTargetOrderById(DUMMY, 852119, u) and not IsUnitType(u,UNIT_TYPE_ETHEREAL)
endfunction
endlibrary
//TESH.scrollpos=0
//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=15
//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=6
//TESH.alwaysfold=0
library StrikeForce /* v1.6
*************************************************************************************
*
* by mckill2009
* Used by AI or user player to group units for attacking purposes
*
*
*************************************************************************************
*
* */uses/*
*
* */ Table /* www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
* */ GetClosestWidget /* www.hiveworkshop.com/forums/jass-resources-412/snippet-getclosestwidget-204217/
* */ IsUnitEngageable /* www.hiveworkshop.com/forums/jass-resources-412/isunitengageable-220436/
* */ IsUnitChanneling /* www.hiveworkshop.com/forums/jass-resources-412/snippet-isunitchanneling-211254/
*
************************************************************************************
*
* Installation:
* - Copy and Paste the above mentioned libraries and the StrikeForce trigger to your map
* - Follow and read carefully all the instructions in the 'IsUnitEngageable' library
* - Follow the Demo instructions
*
*************************************************************************************
*
* Credits:
* Table by Bribe
* GetClosestWIdget by Spinnaker
* IsUnitChanneling by Magtheridon96
*
*************************************************************************************
*
* StrikeForce API
*
*************************************************************************************
* static method create takes unit hero, boolean retreat, integer maxAttackSize, real attackDelay returns thistype
* - Assigns a hero captain, he will lead the attack of the army
* - Set the retreat to true so that if hero dies all units will return to base (if base is set)
* - If the amount of added unit is less than maxAttackSize, hero will not engage
* - maxAttackSize is the size quantity of the group that's engaging
* - attackDelay is the delay of the next attack
*
* method setHeroTargets takes integer targetID returns nothing
* - 16 Total Players (0-15), see global constants for refference
* - If you want the AI to target only player 1 then >>> call structInstanceName.setHeroTargets(0)
* - If you want the AI to target only heroes then >>> call structInstanceName.setHeroTargets(ATTACK_ENEMY_HEROES_ONLY)
*
* method addUnit takes unit follower returns nothing
* - Adds a unit to the group which increases the maxAttackSize
* - Must be done manually for the attack to happen
*
* method setBase takes real x, real y returns nothing
* - Sets the base which the AI will retreat if the hero dies or when retreat is true
*
* method setMaxAttackSize takes integer maxatksize returns nothing
* - Changes/Sets the maxAttackSize of the group
*
* method setAttackDelay takes real attackDelay returns nothing
* - Changes/Sets the delay of the next attack
*
* method heroWaitForToops takes boolean wait, real waitDuration returns nothing
* - This allows the hero to wait for lost units for (x) seconds
*
* method removeUnit takes unit u returns nothing
* - Removes a unit from the engaging group
*
* method remove takes nothing returns nothing
* - Removes all units in the group that's engaging
*
*************************************************************************************
*
* StrikeForceEx API
*
*************************************************************************************
* static method create takes unit captain, real maxDelay, boolean killCaptainWhenGroupIsEmpty returns thistype
* - Assigns a dummy captain, he will lead the attack but he cant attack
* - It is advised that the dummy walks and doesnt have a locust ability
* - Dummy captain must be a non-hero
* - The dummy can be killed OR not when group is empty
* - This captain be recycled by the next creation of the force
*
* static method createEx takes player p, real xCaptain, real yCaptain, real maxDelay returns thistype
* - Creates a dummy captain automatically at XY coordinate in the form of a peasant ('hpea')
* - Take note that this captain will die automatically once the group is empty
* - This captain cannot be recycled
*
* static method setUnitBase takes unit follower, real xNewBase, real yNewBase returns nothing
* - Sets/changes the retreat/base of individual unit
*
* method addUnit takes unit follower, real xReturn, real yReturn returns nothing
* - Adds a unit to the group and sets the return/retreat/base of the follower
* - Must be done manually for the attack to happen
*
* method setCaptainBase takes real xBase, real yBase returns nothing
* - Sets the group formation at XY of captain when preparing an attack
*
* method delayReset takes real delay returns nothing
* - Change/Sets the delay of the next attack
*
* method captainWaitForTroops takes boolean wait, real waitDuration returns nothing
* - This allows the dummy to wait or stop lost units for (x) seconds
*
* method setTargets takes integer targetID returns nothing
* - 16 Total Players (0-15), see global constants for refference
* - If you want the AI to target only player 2 then >>> set structInstanceName.targets = 2
* - If you want the AI to target all enemies then >>> set structInstanceName.targets = ATTACK_ALL_ENEMIES
*
* method enableRetreat takes boolean b returns nothing
* - Enables the retreat option for units
*
* method remove takes boolean killCaptain returns nothing
* - Removes all units from the attackgroup and kills the dummy (optional)
*
**************************************************************************************
*
* Settings: These Globals are configurables, but the recommended setting
*
**************************************************************************************/
globals
/*******************************************************************
* Used by StrikeForce and StrikeForceEx
********************************************************************/
//Distance offset of following unit and captain
private constant real OFFSET_FROM_CAPTAIN = 250
//Temporary retreats at base if target can't be attacked
private constant real TEMPORARY_RETREAT = 5
//Distance of attacking unit and target, if less than indicated, the attacker will engage without waits
private constant real IS_TARGET_FAR = 490000 //squareroot 700, recommended
//Unit will keep on retreating (or go back to captain) until less than indicated
private constant real RETURN_OFFSET = 360000 //squareroot 600, recommended
//Used for retreating, retreat must be true in both systems
private constant real LIFE = 0.4 //40%
/********************************************************************
* Used only by StrikeForceEx, by NORMAL UNITS
********************************************************************/
//The dummy unit that will lead the attack, default footman, this should be non-hero
private constant integer DUMMY_CAPTAIN_ID = 'hfoo'
//Allows attacker to target closest unit in CLOSEST_TARGET range
private constant boolean ENABLE_CLOSEST_TARGET = false
//Engages closest target, ENABLE_CLOSEST_TARGET must be true
private constant real CLOSEST_TARGET = 600
/********************************************************************
* Global configurables
********************************************************************/
//This setting is recommended and shoudn't be changed
private constant real MOVE_DELAY = 3.0
/********************************************************************
* You may now use these constants to reffer attack priorities e.g.
* For StrikeForce >>> call heroX.setHeroTargets(ATTACK_ALL_ENEMIES)
* For StrikeForceEx >>> set Army.targets = ATTACK_ENEMY_HEROES_ONLY
*
* WARNING: These constants must not be modified!
********************************************************************/
constant integer ATTACK_ALL_ENEMIES = 16
constant integer ATTACK_ENEMY_STRUCTURES_ONLY = 17
constant integer ATTACK_ENEMY_FLYING_ONLY = 18
constant integer ATTACK_ENEMY_HEROES_ONLY = 19
endglobals
/**************************************************************************************
* End of configurable globals
***************************************************************************************/
//=====NON-CONFIGURABLES: NEVER TOUCH THESE GLOBAL BLOCK!=====
globals
private constant integer ATTACK = 851983
private constant integer MOVE = 851986
private constant integer SMART = 851971
private constant integer STOP = 851972
private constant real INTERVAL = 1.0
private unit TempUnit = null
private group TempG = CreateGroup()
private integer countUnit = 0 //counts how many units in a group
//used only by StrikeForce
private Table chk //used if hero is registered already
//used only by StrikeForceEx
private TableArray tba //used only by StrikeForceEx
endglobals
//===Simple group recycling
globals
private group array Grp
private integer indexGrp = 0
endglobals
private function NGroup takes nothing returns group
set indexGrp = indexGrp + 1
if Grp[indexGrp]==null then
set Grp[indexGrp] = CreateGroup()
endif
return Grp[indexGrp]
endfunction
private function RGroup takes group g returns nothing
set Grp[indexGrp] = g
call GroupClear(g)
set indexGrp = indexGrp - 1
endfunction
//==========
//! textmacro EngageS takes tID, heroU
private static method engageSpecificPlayerUnit takes nothing returns boolean
local thistype this = DATA
local integer i = 0
set TempUnit = GetFilterUnit()
if UnitAlive(TempUnit) and IsUnitEnemy($heroU$,GetOwningPlayer(TempUnit)) and IsUnitEngageable(TempUnit) then
if $tID$ < 16 then
loop
if $tID$==i then
return GetOwningPlayer(TempUnit)==Player(i)
endif
exitwhen i==$tID$
set i = i+1
endloop
elseif $tID$==16 then
return true
elseif $tID$==17 then
return IsUnitType(TempUnit,UNIT_TYPE_STRUCTURE)
elseif $tID$==18 then
return IsUnitType(TempUnit,UNIT_TYPE_FLYING)
elseif $tID$==19 then
return IsUnitType(TempUnit,UNIT_TYPE_HERO)
endif
endif
return false
endmethod
//! endtextmacro
//! textmacro Targets takes targetID, tar, unitU
if $tar$ > -1 and $tar$ < 16 then
if GetOwningPlayer($unitU$)==Player($tar$) or not IsUnitEnemy($unitU$, Player($tar$)) then
debug call BJDebugMsg("ERROR: can't target this Player!")
else
set $targetID$ = $tar$
endif
elseif $tar$ > 15 and $tar$ < 20 then
set $targetID$ = $tar$
else
debug call BJDebugMsg("ERROR: Please input only from 0 to 19!")
endif
//! endtextmacro
private function AttackMoveOrders takes integer order returns boolean
return (order==ATTACK or order==MOVE)
endfunction
private function UnitAlive takes unit u returns boolean
return not IsUnitType(u, UNIT_TYPE_DEAD) and u!=null
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
private function IsTargetFar takes unit u, unit u2 returns boolean
return (GetDistance(GetUnitX(u), GetUnitY(u), GetUnitX(u2), GetUnitY(u2))) > (IS_TARGET_FAR) //this is 700 range
endfunction
private function AttackNow takes unit u, unit target, real x, real y returns nothing
if not AttackMoveOrders(GetUnitCurrentOrder(u)) then
if IsUnitType(u, UNIT_TYPE_SLEEPING) then
call IssueTargetOrderById(u, ATTACK, target)
else
call IssuePointOrderById(u, ATTACK, x, y)
endif
endif
endfunction
private function EngageCloseTargets takes nothing returns boolean
return UnitAlive(GetFilterUnit()) and IsUnitEnemy(TempUnit,GetOwningPlayer(GetFilterUnit())) and IsUnitEngageable(GetFilterUnit())
endfunction
private function RandomAngle takes nothing returns real
return GetRandomReal(0,6.28)
endfunction
private function LifeLessThanMaxLife takes unit u returns boolean
return GetWidgetLife(u) < (GetUnitState(u, UNIT_STATE_MAX_LIFE) * LIFE)
endfunction
private function MoveToCaptain takes unit u, real x, real y, real offset returns nothing
call IssuePointOrderById(u, ATTACK, x+offset*Cos(RandomAngle()), y+offset*Sin(RandomAngle()))
endfunction
private function ReturnToBase takes unit u, real xBase, real yBase returns nothing
if (GetDistance(GetUnitX(u), GetUnitY(u), xBase, yBase)) > (RETURN_OFFSET) then
call IssuePointOrderById(u, MOVE, xBase, yBase)
endif
endfunction
private function NoEnemyUnits takes unit u, real radius returns boolean
local unit first
call GroupEnumUnitsInRange(TempG, GetUnitX(u), GetUnitY(u), radius, null)
loop
set first = FirstOfGroup(TempG)
exitwhen first==null
if UnitAlive(first) and IsUnitEnemy(first, GetOwningPlayer(u)) and not UnitIsSleeping(first) then
return false
endif
call GroupRemoveUnit(TempG, first)
endloop
return true
endfunction
private function GroupRetreat takes unit u, real captainXX, real captainYY, real baseXX, real baseYY returns nothing
if LifeLessThanMaxLife(u) then
call ReturnToBase(u, baseXX, baseYY)
else
call MoveToCaptain(u, captainXX, captainYY, OFFSET_FROM_CAPTAIN)
endif
endfunction
private function CountUnits takes nothing returns nothing
if UnitAlive(GetEnumUnit()) then
set countUnit = countUnit + 1
endif
endfunction
private function UnitsInGroup takes group grp returns integer
call ForGroup(grp, function CountUnits)
return countUnit
endfunction
//=====END OF NON-CONFIGURABLE BLOCK=====
private struct init
private static method onInit takes nothing returns nothing
set chk = Table.create()
set tba = TableArray[0x2000]
endmethod
endstruct
/*************************************************************************************
* StrikeForce: used by HEROES, assigns as captain who leads
**************************************************************************************/
struct StrikeForce
private unit hero
private unit target
private unit closeTarget
private real baseX
private real baseY
private real moveDelay
private real minAttackDelay //starts with 0, max is attackDelay
private real maxAttackDelay
private real heroWaitInterval
private real heroEngageTime //used only if heroWaitsTroop is true
private real heroWaitTroopDuration
private real tempRetreat
private real heroStopGroup
private integer minAtkSize
private integer maxAtkSize
private integer handleID
private integer targetPlayerID
private boolean retreat
private boolean onSF
private boolean heroWaitsTroop
private boolean heroWaitOneTime
private group heroGroup
private static thistype DATA
private static timer t = CreateTimer()
private static integer instance = 0
private static integer array insAR
//! runtextmacro EngageS(".targetPlayerID",".hero")
private method destroy takes nothing returns nothing
call RGroup(.heroGroup)
set .closeTarget = null
set .hero = null
set .target = null
set .heroGroup = null
call .deallocate()
endmethod
//runs every INTERVAL, units return to base when hero is dead or target cannot be attacked
private static method groupReturnToBase takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this
if UnitAlive(u) then
set this = DATA
call ReturnToBase(u, .baseX, .baseY)
endif
set u = null
endmethod
//runs every INTERVAL, for re-grouping if hero finds a new target or hero waits troops or maxAttackSize is not met
private static method groupFormation takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this
local real hX
local real hY
if UnitAlive(u) then
set this = DATA
set hX = GetUnitX(.hero)
set hY = GetUnitY(.hero)
if .retreat then
call GroupRetreat(u, hX, hY, .baseX, .baseY)
else
call MoveToCaptain(GetEnumUnit(), hX, hY, OFFSET_FROM_CAPTAIN)
endif
endif
set u = null
endmethod
//runs every MOVE_DELAY
private static method groupAttack takes nothing returns nothing
local unit u = GetEnumUnit() //u is the follower of the hero
local thistype this
local real hX
local real hY
local real xTar
local real yTar
local real distHeroFollower
local real distTargetFollower
local real xFollower
local real yFollower
if UnitAlive(u) then
if not IsUnitChanneling(u) then
set this = DATA //DATA taken from looper
set hX = GetUnitX(.hero)
set hY = GetUnitY(.hero)
set xTar = GetUnitX(.target)
set yTar = GetUnitY(.target)
set xFollower = GetUnitX(u)
set yFollower = GetUnitY(u)
set distTargetFollower = GetDistance(xTar, yTar, xFollower, yFollower)
set distHeroFollower = GetDistance(hX, hY, xFollower, yFollower)
if .retreat then
if LifeLessThanMaxLife(u) then
call ReturnToBase(u, .baseX, .baseY)
else
if distHeroFollower > RETURN_OFFSET then
call MoveToCaptain(u, hX, hY, OFFSET_FROM_CAPTAIN)
else
call AttackNow(u, .target, xTar, yTar)
endif
endif
else
if distHeroFollower > RETURN_OFFSET then
call MoveToCaptain(u, hX, hY, OFFSET_FROM_CAPTAIN)
else
call AttackNow(u, .target, xTar, yTar)
endif
endif
endif
else
set .minAtkSize = .minAtkSize - 1
call GroupRemoveUnit(.heroGroup, u)
endif
set u = null
endmethod
//runs every INTERVAL
private method heroEngaging takes nothing returns nothing
if not IsUnitChanneling(.hero) then
//hero attacks
call AttackNow(.hero, .target, GetUnitX(.target), GetUnitY(.target))
//hero group attack, not the hero
call ForGroup(.heroGroup,function thistype.groupAttack)
endif
endmethod
//runs every INTERVAL
private method heroMovement takes nothing returns nothing
set .moveDelay = .moveDelay + INTERVAL
if .moveDelay > MOVE_DELAY then
set .moveDelay = 0
//movement of hero and group, default every 3 seconds
//===Hero retreats, this should be true if hero wants to retreat
if .retreat then
if LifeLessThanMaxLife(.hero) then
call ReturnToBase(.hero, .baseX, .baseY)
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
else
call .heroEngaging()
endif
else
call .heroEngaging()
endif
endif
endmethod
//runs every INTERVAL
private method findTarget takes nothing returns nothing
set .target = GetClosestUnit(GetUnitX(.hero), GetUnitY(.hero), Filter(function thistype.engageSpecificPlayerUnit))
set .minAttackDelay = 0 //resets the delay when engaging the next target
set .heroWaitInterval = 0
if .target==null then
call ReturnToBase(.hero, .baseX, .baseY)
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
else
call ForGroup(.heroGroup, function thistype.groupFormation)
endif
endmethod
//this is the hero moving, not the units, runs every INTERVAL
private static method looper takes nothing returns nothing
local thistype this
local integer i = 0
loop
set i = i+1
set this = insAR[i]
if .onSF then
if UnitAlive(.hero) then
if .minAtkSize >= .maxAtkSize then
set .minAttackDelay = .minAttackDelay + INTERVAL
if .minAttackDelay > .maxAttackDelay then
set DATA = this
if UnitAlive(.target) then
if IsUnitEngageable(.target) then
if .heroWaitsTroop then
set .heroWaitInterval = .heroWaitInterval + INTERVAL
if .heroEngageTime > .heroWaitInterval then
if .heroWaitTroopDuration > .heroWaitInterval then
call .heroMovement()
else
if not IsUnitChanneling(.hero) then
if IsTargetFar(.hero, .target) and NoEnemyUnits(.hero, 600) then
if .heroWaitOneTime then
call IssueImmediateOrderById(.hero, STOP)
call ForGroup(.heroGroup, function thistype.groupFormation)
set .heroWaitOneTime = false
endif
endif
endif
endif
else
set .heroWaitOneTime = true
set .heroWaitInterval = 0
endif
else
call .heroMovement()
endif
else
//if unit cant be attacked, hero and the group will return to base
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
call ReturnToBase(.hero, .baseX, .baseY)
set .tempRetreat = .tempRetreat + INTERVAL
if .tempRetreat > TEMPORARY_RETREAT then
set .tempRetreat = 0
call .findTarget() //this finds another target
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
endif
endif
else
//if target is dead or null, hero will return to base or find another target
//from UnitAlive(.target)
call .findTarget()
endif
endif //no else from .minAttackDelay > .maxAttackDelay
else
//from .minAtkSize >= .maxAtkSize
set .heroStopGroup = .heroStopGroup + INTERVAL
if .heroStopGroup > 3 then
set .heroStopGroup = 0
if not IsUnitChanneling(.hero) then
if IsUnitEngageable(.target) then
call IssuePointOrderById(.hero, ATTACK, GetUnitX(.hero), GetUnitY(.hero))
call ForGroup(.heroGroup, function thistype.groupFormation)
else
call ReturnToBase(.hero, .baseX, .baseY)
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
endif
endif
endif
endif
else
//Hero is dead, from UnitAlive(.hero)
call ForGroup(.heroGroup, function thistype.groupReturnToBase)
endif
else
call .destroy()
set insAR[i] = insAR[instance]
set insAR[instance] = this
set i = i - 1
set instance = instance - 1
if instance==0 then
call PauseTimer(t)
endif
endif
exitwhen i==instance
endloop
endmethod
//StrikeForce API:
static method create takes unit hero, boolean retreat, integer maxAttackSize, real attackDelay returns thistype
local thistype this
if chk.has(GetHandleId(hero)) then
debug call BJDebugMsg("StrikeForce.create ERROR: "+GetUnitName(hero)+" is already registered!")
else
if IsUnitType(hero,UNIT_TYPE_HERO) then
set this = allocate()
set chk[GetHandleId(hero)] = this
set .heroGroup = NGroup()
set .hero = hero
set .handleID = GetHandleId(hero)
set .target = null
set .closeTarget = null
set .moveDelay = 0
set .minAttackDelay = 0
set .maxAttackDelay = attackDelay
set .minAtkSize = 0
set .maxAtkSize = maxAttackSize
set .retreat = retreat
set .onSF = true
set .heroWaitInterval = 0
set .heroEngageTime = 0
set .tempRetreat = 0
set .heroWaitTroopDuration = 0
set .heroStopGroup = 0
set .heroWaitsTroop = false
set .heroWaitOneTime = true
if instance==0 then
call TimerStart(t,INTERVAL,true,function thistype.looper)
endif
set instance = instance + 1
set insAR[instance] = this
call RemoveGuardPosition(hero)
else
debug call BJDebugMsg("StrikeForce.create ERROR: Please register a Hero Type unit!")
endif
endif
return this
endmethod
method setHeroTargets takes integer targetID returns nothing
//! runtextmacro Targets(".targetPlayerID","targetID",".hero")
endmethod
method addUnit takes unit follower returns nothing
if follower!=.hero then
call GroupAddUnit(.heroGroup,follower)
call RemoveGuardPosition(follower)
call MoveToCaptain(follower, GetUnitX(.hero), GetUnitY(.hero), OFFSET_FROM_CAPTAIN)
set .minAtkSize = .minAtkSize + 1
endif
endmethod
method setBase takes real x, real y returns nothing
set .baseX = x
set .baseY = y
endmethod
method setMaxAttackSize takes integer maxatksize returns nothing
set .maxAtkSize = maxatksize
endmethod
method setAttackDelay takes real attackDelay returns nothing
set .maxAttackDelay = attackDelay
endmethod
method heroWaitForToops takes boolean wait, real waitDuration returns nothing
set .heroWaitsTroop = wait
set .heroEngageTime = waitDuration*3
set .heroWaitTroopDuration = .heroEngageTime-waitDuration
endmethod
method removeUnit takes unit u returns nothing
if IsUnitInGroup(u, .heroGroup) then
call GroupRemoveUnit(.heroGroup, u)
set .minAtkSize = .minAtkSize - 1
call IssuePointOrderById(u, MOVE, .baseX, .baseY)
else
debug call BJDebugMsg("StrikeForce.removeUnit ERROR: "+GetUnitName(u)+" is not in this group")
endif
endmethod
method remove takes nothing returns nothing
set .onSF = false
call chk.remove(GetHandleId(.hero))
endmethod
endstruct
/*************************************************************************************
* StrikeForceEx: used for NORMAL UNITS, assigns a dummy captain
**************************************************************************************/
struct StrikeForceEx
private unit captain
private unit target
private unit closestTarget
private real maxDelay
private real minDelay
private real xBack
private real yBack
private real moveWait //use 3.0
private real dummyWaitInterval
private real captainEngageTime
private real captainWaitTime
private real tempRetreat
private integer targetPlayerID
private group dummyGroup
private boolean on
private boolean killCaptain
private boolean retreat
private boolean captainWaitsTroop
private static thistype DATA
private static integer index = 0
private static integer array indexAR
private static timer t = CreateTimer()
//! runtextmacro EngageS(".targetPlayerID",".captain")
private method destroy takes nothing returns nothing
if .killCaptain then
call KillUnit(.captain)
endif
call RGroup(.dummyGroup)
set .captain = null
set .target = null
set .closestTarget = null
set .dummyGroup = null
call .deallocate()
endmethod
//this is only used if the group is about to be destroyed
private static method grpReturn takes nothing returns nothing
local unit u = GetEnumUnit()
local real xRet
local real yRet
if UnitAlive(u) then
set xRet = tba[0].real[GetHandleId(u)]
set yRet = tba[1].real[GetHandleId(u)]
if GetDistance(xRet, yRet, GetUnitX(u), GetUnitY(u)) > RETURN_OFFSET then
call IssuePointOrderById(u, MOVE, xRet, yRet)
endif
endif
set u = null
endmethod
private static method groupFormation takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this
local real x
local real y
if UnitAlive(u) then
set this = DATA
set x = GetUnitX(.captain)
set y = GetUnitY(.captain)
if .retreat then
if LifeLessThanMaxLife(u) then
call ReturnToBase(u, tba[0].real[GetHandleId(u)], tba[1].real[GetHandleId(u)])
else
call MoveToCaptain(u, x, y, OFFSET_FROM_CAPTAIN)
endif
else
call MoveToCaptain(u, x, y, OFFSET_FROM_CAPTAIN)
endif
endif
set u = null
endmethod
//runs every MOVE_DELAY
private static method groupEngagingEx takes unit u, thistype this, real xCaptain, real yCaptain returns nothing
local real xFol
local real yFol
local real xTar
local real yTar
local real distanceFromCaptain
if not IsUnitChanneling(u) then
set xFol = GetUnitX(u)
set yFol = GetUnitY(u)
set xTar = GetUnitX(.target)
set yTar = GetUnitY(.target)
set distanceFromCaptain = GetDistance(xFol, yFol, xCaptain, yCaptain)
if ENABLE_CLOSEST_TARGET then
if UnitAlive(.closestTarget) and IsUnitEngageable(.closestTarget) then
call AttackNow(u, .closestTarget, GetUnitX(.closestTarget), GetUnitY(.closestTarget))
else
//if captain is far, unit will go near captain, else attack the target
if distanceFromCaptain > 600000 then //600
call MoveToCaptain(u, xCaptain, yCaptain, OFFSET_FROM_CAPTAIN)
else
call AttackNow(u, .target, xTar, yTar)
endif
endif
else
//if captain is far, unit will go near captain, else attack the target
if distanceFromCaptain > 600000 then //600
call MoveToCaptain(u, xCaptain, yCaptain, OFFSET_FROM_CAPTAIN)
else
call AttackNow(u, .target, xTar, yTar)
endif
endif
endif
endmethod
//runs every MOVE_DELAY
private static method grpEngaging takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this
local real x
local real y
if UnitAlive(u) then
set this = DATA
set x = GetUnitX(.captain)
set y = GetUnitY(.captain)
if .retreat then
if LifeLessThanMaxLife(u) then
call ReturnToBase(u, tba[0].real[GetHandleId(u)], tba[1].real[GetHandleId(u)])
else
call thistype.groupEngagingEx(u, this, x, y)
endif
else
call thistype.groupEngagingEx(u, this, x, y)
endif
else
call GroupRemoveUnit(.dummyGroup, u)
endif
set u = null
endmethod
//runs every INTERVAL
private method captainEngaging takes nothing returns nothing
local real x
local real y
set .moveWait = .moveWait + INTERVAL
if .moveWait > MOVE_DELAY then
set .moveWait = 0
set x = GetUnitX(.captain)
set y = GetUnitY(.captain)
set DATA = this
if ENABLE_CLOSEST_TARGET then
if UnitAlive(.closestTarget) and IsUnitEngageable(.closestTarget) then
call IssuePointOrderById(.captain, MOVE, GetUnitX(.closestTarget), GetUnitY(.closestTarget))
else
set TempUnit = .captain
set .closestTarget = GetClosestUnitInRange(x, y, CLOSEST_TARGET, Filter(function EngageCloseTargets))
if .closestTarget==null then
call ForGroup(.dummyGroup, function thistype.grpEngaging)
call IssuePointOrderById(.captain, MOVE, GetUnitX(.target), GetUnitY(.target))
endif
endif
else
call ForGroup(.dummyGroup, function thistype.grpEngaging)
call IssuePointOrderById(.captain, MOVE, GetUnitX(.target), GetUnitY(.target))
endif
endif
endmethod
//runs from looper if target is dead or not engageable
private method findCaptainNewTarget takes nothing returns nothing
set .minDelay = 0
set .target = GetClosestUnit(GetUnitX(.captain), GetUnitY(.captain), Filter(function thistype.engageSpecificPlayerUnit))
if .target==null then
//captain goes back to original location
call IssuePointOrderById(.captain, MOVE, .xBack, .yBack)
//individual group go back to their respective locations
call ForGroup(.dummyGroup, function thistype.grpReturn)
else
call ForGroup(.dummyGroup, function thistype.groupFormation)
endif
endmethod
//runs every INTERVAL
private static method looper takes nothing returns nothing
local thistype this
local integer i = 0
loop
set i = i+1
set this = indexAR[i]
if .on and UnitAlive(.captain) then
set .minDelay = .minDelay + INTERVAL
if .minDelay > .maxDelay then
set DATA = this
set countUnit = 0
if UnitsInGroup(.dummyGroup) > 0 then //checks if the group is empty or not
if UnitAlive(.target) then
if IsUnitEngageable(.target) then
if .captainWaitsTroop then
set .dummyWaitInterval = .dummyWaitInterval + INTERVAL
if .captainEngageTime > .dummyWaitInterval then
if .captainWaitTime > .dummyWaitInterval then
call .captainEngaging()
else
if IsTargetFar(.captain, .target) then
call IssueImmediateOrderById(.captain, STOP)
call ForGroup(.dummyGroup, function thistype.groupFormation)
endif
endif
else
set .dummyWaitInterval = 0
endif
else
call .captainEngaging()
endif
else
call ForGroup(.dummyGroup, function thistype.grpReturn)
call IssuePointOrderById(.captain, MOVE, .xBack, .yBack)
set .tempRetreat = .tempRetreat + INTERVAL
if .tempRetreat > TEMPORARY_RETREAT then
set .tempRetreat = 0
call .findCaptainNewTarget()
call ForGroup(.dummyGroup, function thistype.grpReturn)
endif
endif
else //from UnitAlive(.target)
call .findCaptainNewTarget()
endif
else //from UnitsInGroup(.dummyGroup)
if .killCaptain then
set .on = false
else
call SetUnitPosition(.captain, .xBack, .yBack)
endif
endif
endif
else //from .on and UnitAlive(.captain)
//this order returns all units in the group to it's base
set DATA = this
call ForGroup(.dummyGroup, function thistype.grpReturn)
//deallocating instance
call .destroy()
set indexAR[i] = indexAR[index]
set indexAR[index] = this
set index = index - 1
set i = i-1
if index==0 then
call PauseTimer(t)
endif
endif
exitwhen i==index
endloop
endmethod
//StrikeForceEx API:
static method create takes unit captain, real maxDelay, boolean killCaptainWhenGroupIsEmpty returns thistype
local thistype this
if IsUnitType(captain, UNIT_TYPE_HERO) then
debug call BJDebugMsg("StrikeForceEx (create) ERROR: "+GetUnitName(captain)+" must be a non-hero!")
else
set this = allocate()
set .captain = captain
set .target = null
set .closestTarget = null
set .minDelay = 0
set .maxDelay = maxDelay
set .dummyGroup = NGroup() //CreateGroup()
set .on = true
set .killCaptain = killCaptainWhenGroupIsEmpty
set .moveWait = 0
set .targetPlayerID = 0
set .dummyWaitInterval = 0
set .captainEngageTime = 0
set .captainWaitTime = 0
set .tempRetreat = 0
set .captainWaitsTroop = false
set .retreat = false
call SetUnitUseFood(captain, false)
if index==0 then
call TimerStart(t,INTERVAL,true,function thistype.looper)
endif
set index = index + 1
set indexAR[index] = this
call RemoveGuardPosition(captain)
call SetUnitInvulnerable(captain,true)
call ShowUnit(captain,false)
endif
return this
endmethod
static method createEx takes player p, real xCaptain, real yCaptain, real maxDelay returns thistype
local thistype this
local unit u = CreateUnit(p, DUMMY_CAPTAIN_ID, xCaptain, yCaptain, 0)
call UnitAddAbility(u, 'Abun')
set this = create(u, maxDelay, true)
set u = null
return this
endmethod
static method setUnitBase takes unit follower, real xNewBase, real yNewBase returns nothing
set tba[0].real[GetHandleId(follower)] = xNewBase
set tba[1].real[GetHandleId(follower)] = yNewBase
endmethod
method addUnit takes unit follower, real xReturn, real yReturn returns nothing
if follower!=.captain then
set tba[0].real[GetHandleId(follower)] = xReturn
set tba[1].real[GetHandleId(follower)] = yReturn
call GroupAddUnit(.dummyGroup, follower)
call RemoveGuardPosition(follower)
call MoveToCaptain(follower, GetUnitX(.captain), GetUnitY(.captain), OFFSET_FROM_CAPTAIN)
endif
endmethod
method setCaptainBase takes real xBase, real yBase returns nothing
set .xBack = xBase
set .yBack = yBase
endmethod
method delayReset takes real delay returns nothing
set .minDelay = 0
set .maxDelay = delay
endmethod
method captainWaitForTroops takes boolean wait, real waitDuration returns nothing
set .captainWaitsTroop = wait
set .captainEngageTime = waitDuration*3
set .captainWaitTime = .captainEngageTime-waitDuration
endmethod
method setTargets takes integer targetID returns nothing
//! runtextmacro Targets(".targetPlayerID","targetID",".captain")
endmethod
method enableRetreat takes boolean b returns nothing
set .retreat = b
endmethod
method remove takes boolean killCaptain returns nothing
set .killCaptain = killCaptain
set .on = false
endmethod
endstruct
endlibrary
//TESH.scrollpos=37
//TESH.alwaysfold=0
scope StrikeForceDemo
globals
//you may declare this also as a local, but recommended as global
//coz you may want your hero to be removed from the system
StrikeForce heroX
private constant player P = Player(1) //player 1 in GUI
endglobals
struct StrikeForceDemo
static method strikeForce takes nothing returns nothing
//this block is NOT related, just creating units
local integer PreplacedUnits = 5
local integer ArmySize = 10
local integer i = PreplacedUnits
local unit bm = CreateUnit(P, 'Hblm', -1632, 1665, 0)
local unit grunt
call SetHeroLevel(bm, 30, true)
//call SetWidgetLife(bm, 2)
//=============================================================
//once the hero is available, let's put him in the system
//create it first >>> unit hero, boolean retreat, integer maxAttackSize, real attackDelay
//attackDelay means the time delay for the next target
//maxAttackSize means how many units in the group
set heroX = heroX.create(bm, false, ArmySize, 5)
//if one or more troop is far from hero the hero may stop and wait for them if the target is still far
//method heroWaitForToops takes boolean wait, real waitDuration returns nothing
call heroX.heroWaitForToops(true, 5)
//setting the hero targets, ATTACK_ALL_ENEMIES is inside the system global constants
call heroX.setHeroTargets(ATTACK_ALL_ENEMIES)
//set the base, when no more enemies the group will return to base
//this is also used for retreating
call heroX.setBase(-1632, 1665)
//creating an army for the hero
loop
exitwhen i==0
set grunt = CreateUnit(P, 'ogru', 300, 2500, 0) //grunt
call SetWidgetLife(grunt, 5)
//creating a grunt and add to the system
//this increases the attack size
call heroX.addUnit(grunt)
set i = i - 1
endloop
//Text display only, NOT related to the system
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "")
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "StrikeForce vJass is ON, this message is NOT related to the system")
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "ArmySize is " + I2S(ArmySize))
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "Added Unit is " + I2S(PreplacedUnits))
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "Train units to be added to the force until AttackSize is met")
call DisplayTimedTextFromPlayer(Player(0), 0, 0, 2000, "Heal the orcs then they will join the group")
//NOTE: if you set the maxAttackSize and setAttackDelay to 0, the the hero will keep on engaging
//changes or sets the maxAttackSize of the group
//call heroX.setMaxAttackSize(0) //optional since we already created this above
//sets the delay time to engage the next target, I'm not gonna use this ATM
//call heroX.setAttackDelay(0) //optional since we already created this above
endmethod
static method train takes nothing returns nothing
call heroX.addUnit(GetTrainedUnit())
endmethod
static method onInit takes nothing returns nothing
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_TRAIN_FINISH, function thistype.train)
call TimerStart(CreateTimer(), 0, false, function thistype.strikeForce) //hero captain
endmethod
endstruct
endscope