Moderator
M
Moderator
22:38, 16th November 2012
Magtheridon96: Approved.
The demos here are a plus.
Magtheridon96: Approved.
The demos here are a plus.
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
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
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
The global constants for easier reading
You may now use these constants to reffer attack priorities e.g.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
- For StrikeForcecall heroX.setHeroTargets(ATTACK_ALL_ENEMIES)
- For StrikeForceExcall Army.setTargets(ATTACK_ENEMY_HEROES_ONLY)
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