library RigidBody requires UnitZ, TimerUtils, UnitIndexer, Table
/*
~ RigidBody v1.3 ~
Applies highly realistic physics to the native Warcraft 3 movements.
I. Requirements:
• TimerUtils : www.wc3c.net/showthread.php?t=101322
• UnitIndexer : www.hiveworkshop.com/forums/spells-569/unit-indexer-v5-3-0-1-a-260859/
• UnitZ : www.hiveworkshop.com/forums/jass-resources-412/snippet-getterrainz-unitz-236942/
• Table : www.hiveworkshop.com/threads/snippet-new-table.188084/
II. How to install:
• Install and configure the unit indexer properly
• Copy the RigidBody trigger group to your map
• Adjust the system configuration below if neccesary
• Define frictions for each tile in your map
• Input unit data to the data reference for better accuracy
III. User API
1. Applies some physics to unit body
struct RigidBody
real acceleration
- Unit's acceleration rate
real deacceleration
- Unit's deacceleration rate
real turnRate
- Unit's turn rate
real angle
- Unit's current motion direction
real speed
- Unit's current motion velocity
real zVel
- Unit's current gravitational velocity
readonly boolean isEnabled
- True if unit's movement is currrently enabled
readonly boolean isAirborne
- True if the unit is currently mid-air
readonly unit object
- The unit agent
static method RegisterUnit takes unit whichUnit returns thistype
- Applies movement physics to a unit
static method IsUnitRegistered takes unit u returns boolean
- Check if the unit is registered to the system or not
method accelerate takes real power, real maxspeed, real direction, real turnRate returns nothing
- Accelerate with certain power toward given direction
method move takes real x, real y, real z returns nothing
- Move the unit to certain coordinate
method jump takes real power returns nothing
- Jump with certain velocity
method enable takes boolean b returns nothing
- If disabled, physics will be disabled for the unit
method destroy takes nothing returns nothing
- Unregister the unit
static method operator [] takes unit u returns thistype
- Get unit's index
2. Unit's object data reference
public struct UnitDataRef
static method IsRegistered takes unit whichUnit returns boolean
- True if the unit data has been defined
static method GetAttackRange takes unit whichUnit returns real
static method GetBackswingPoint takes unit whichUnit returns real
- Get unit data
static method Add takes integer unitId, real attackRange, real backswingPoint returns nothing
- Define unit data
3. Terrain type specifications
public struct TerrainUtils
static method GetTerrainSteepness takes real x1, real y1, real x2, real y2 returns real
- Get tiltness between two points
static method GetTerrainFriction takes real x, real y returns real
- Get terrain friction at given coordinate
static method SetTerrainTypeFriction takes integer terrainId, real friction returns nothing
- Define tile friction
IV. Credits:
• Nestharus
• Garfield1337
• Vexorian
• Bribe
*/ ////////////////////
// Configurations //
////////////////////
// Warning! Every tiny bit of modification to these settings may
// causes significant impact to the motion behavior!
// So be careful when you are modifying it if you have "deeply"
// implement the system to your map.
globals
// 1. Physic calculations interval
private constant real INTERVAL = 0.0312500
// 2. Unit position update rate
// - Higher values will significantly improve performance
// - But it causes the movement looks rough
private constant real RELOCATION_RATE = 0.01
// 3. Gravitational force of your world (map)
private constant real GRAVITY = 9.8*INTERVAL
// 4. Max friction of the ground to be identified as slippery
private constant real SLIPPERY_TILE_MAX_FRICTION = 1.0
// 5. Offset accuracy of tiltness detection
// - Different value may result in different motion behavior
private constant real SLOPE_DETECTION_RADIUS = 0.1
// 6. Number of tiltness detection iterations
// - Higher value means higher precision (more realistic theoritically)
// - Different value may result in different motion behavior
private constant integer SLOPE_DETECTION_ACCURACY = 5
// 7. Ground tiltness speed factors
// - Downhill movement speed bonus
private constant real SLOPE_SPEED_INCREMENT_STRENGTH = 0.05
// - Maximum speed bonus on downhill movement
private constant real MAXIMUM_SLOPE_SPEED_BONUS_RATE = 1.5
// - Uphill movement speed penalty
private constant real SLOPE_SPEED_REDUCTION_STRENGTH = 0.05
// 8. Ground tiltness strength
// - Tiltness affects movement exponentially
// - Higher value causes slopier ground to have stronger impact to the motion
private constant real SLOPE_STRENGTH_FACTOR = 1.25
// 9. Maximum walktable ground tiltness (in radian)
// - Units can't move on ground tiltness beyond this value
private constant real MAX_WALKABLE_SLOPE = 85.0*bj_DEGTORAD
// 10. Maximum speed of unit slipping
// - Units will be passively slipped when standing on sloppy ground
// - The effect can be overcame by ground friction so it will be seemingly idle
private constant real SLIP_MAXIMUM_SPEED = 522.0*INTERVAL
// 11. Turn rate of unit slipping
// - Higher value causes motion to direct downward more quickly when slipping
private constant real SLIP_TURN_RATE = 15.0*bj_DEGTORAD
// 12. Absolute motion speed limit
// - Nothing can move faster than following specified value
private constant real ABSOLUTE_SPEED_LIMIT = 1566.0*INTERVAL
// 13. If true, units will try to deaccelerate themselves when idle
// - Acts like additional friction
// - Ideally turned on for battle-based maps
private constant boolean IDLE_DEACCELERATION = false
// 14. If true, every classified units will be registered to the system on creation
private constant boolean AUTO_REGISTER = true
// 15. Units can't take off if the ground has lower tiltness than following specified value
// - A unit can take off (for example from ramps) if it has enough velocity to escape the gravitation
private constant real MINIMUM_TAKE_OFF_TILTNESS = 45.0*bj_DEGTORAD
// 16. General unit attack range (used for units without pre-registered data)
// - For range attackers
private constant real RANGE_UNIT_ATTACK_RANGE = 600.0
// - For melee attackers
private constant real MELEE_UNIT_ATTACK_RANGE = 128.0
// 17. General unit backswing point (used for units without pre-defined data)
private constant real GENERAL_UNIT_BACKSWING_POINT = 0.800
// 18. General unit turn rate
// - Turn rate on slippery grounds
private constant real GENERAL_UNIT_TURN_RATE = 5.0*bj_DEGTORAD
// - Turn rate on non-slippery grounds
private constant real GENERAL_UNIT_TURN_RATE_2 = 90.0*bj_DEGTORAD
// 19. General unit acceleration
private constant real GENERAL_UNIT_ACCELERATION = 25.0*INTERVAL
// 20. General unit deacceleration
// - Only works if IDLE_DEACCELERATION is set to "true"
private constant real GENERAL_UNIT_DEACCELERATION = 5.0*INTERVAL
// 21. General unit mass
// - Acts like additional gravitational force
private constant real GENERAL_UNIT_MASS = 5.0*INTERVAL
// 22. Detection trigger will be recycled if have more wasted events than the following value
private constant integer TRIGGER_RECYCLE_THRESHOLD = 15
// 23. Target adjustment range
private constant real TARGET_ADJUSTMENT_RANGE = 64.0
// 24. Target adjustment movespeed factor
// - Factor applied to movespeed when adjusting target
private constant real TARGET_ADJUSTMENT_SPEED = 0.5
// 25. Speed reduction on collision
private constant real COLLISION_BOUNCE_FACTOR = 0.5
endglobals
native UnitAlive takes unit id returns boolean
private function FilterUnit takes unit u returns boolean
return not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_FLYING) and GetUnitAbilityLevel(u, 'Aloc') == 0
endfunction
globals
private real WorldMaxX = 0
private real WorldMaxY = 0
private real WorldMinX = 0
private real WorldMinY = 0
private integer EventWasted = 0
private constant real TAU = bj_PI*2
private constant real HP = bj_PI/2
endglobals
private module Init
private static method onInit takes nothing returns nothing
call Initialize()
endmethod
endmodule
public struct UnitDataRef
private static Table AttackRangeTable
private static Table BackswingPointTable
static method IsRegistered takes unit whichUnit returns boolean
return BackswingPointTable.boolean[GetUnitTypeId(whichUnit)]
endmethod
static method GetAttackRange takes unit whichUnit returns real
return AttackRangeTable.real[GetUnitTypeId(whichUnit)]
endmethod
static method GetBackswingPoint takes unit whichUnit returns real
return BackswingPointTable.real[GetUnitTypeId(whichUnit)]
endmethod
static method Add takes integer unitId, real attackRange, real backswingPoint returns nothing
set AttackRangeTable.real[unitId] = attackRange
set BackswingPointTable.real[unitId] = backswingPoint
set BackswingPointTable.boolean[unitId] = true
endmethod
private static method Initialize takes nothing returns nothing
set AttackRangeTable = Table.create()
set BackswingPointTable = Table.create()
endmethod
implement Init
endstruct
public struct TerrainUtils extends array
private static Table TerrainUtils
static method GetTerrainSteepness takes real x1, real y1, real x2, real y2 returns real
return Sin(GetTerrainZ(x1, y1)-GetTerrainZ(x2, y2))/SLOPE_DETECTION_RADIUS
endmethod
static method GetTerrainFriction takes real x, real y returns real
return TerrainUtils.real[GetTerrainType(x, y)]
endmethod
static method SetTerrainTypeFriction takes integer id, real friction returns nothing
set TerrainUtils.real[id] = friction
endmethod
private static method Initialize takes nothing returns nothing
set TerrainUtils = Table.create()
endmethod
implement Init
endstruct
struct RigidBody extends array
real acceleration
real deacceleration
real turnRate
real angle // Unit's motion direction
real speed // Unit's motion velocity
real zVel // Unit's gravitational velocity
private boolean isMoving
private boolean isPatroling
readonly boolean isEnabled
readonly boolean isAirborne
readonly unit object
private unit target
private real locX
private real locY
private real locZ
private timer tick
private timer locator
private timer disabler
private real orderX
private real orderY
private widget orderTarget
private integer orderId
private static real LowestA = 0
private static real LowestX = 0
private static real LowestY = 0
private static real LowestZ = 0
private static group Objects = CreateGroup()
private static trigger DetectTrigger = CreateTrigger()
private static conditionfunc DetectFunc
static method operator [] takes unit u returns thistype
return GetUnitUserData(u)
endmethod
static method IsUnitRegistered takes unit u returns boolean
return thistype(GetUnitUserData(u)).object != null
endmethod
private static method AngularDifferenceAbs takes real a, real b returns real
return RAbsBJ(Atan2(Sin(a-b), Cos(a-b)))
endmethod
private static method Exponent takes real b returns real
return Pow(SLOPE_STRENGTH_FACTOR, b)
endmethod
method destroy takes nothing returns nothing
call ReleaseTimer(.tick)
call ReleaseTimer(.locator)
call ReleaseTimer(.disabler)
set .object = null
set .target = null
set .tick = null
set .locator = null
set .disabler = null
endmethod
// Detecting lowest ground with constant time complexity of O(n) = N
// depending on specified SLOPE_DETECTION_ACCURACY
private method getLowestPoint takes nothing returns nothing
local real x
local real y
local real z
local real x2
local real y2
local real z2
local real inc = bj_PI
local real angle = GetRandomReal(0, bj_PI-.1) // Significantly reduces no-slope detection error possibility
local integer i
set LowestZ = 99999999
loop
exitwhen angle >= TAU
set x = .locX+SLOPE_DETECTION_RADIUS*Cos(.angle+angle)
set y = .locY+SLOPE_DETECTION_RADIUS*Sin(.angle+angle)
set z = GetTerrainZ(x, y)
if z < LowestZ then
set LowestX = x
set LowestY = y
set LowestZ = z
set LowestA =.angle+ angle
elseif z == LowestZ then
set LowestX = .locX
set LowestY = .locY
set LowestA = .angle
return
endif
set angle = angle+inc
endloop
set i = 0
loop
set i = i + 1
set inc = inc/2
set x = .locX+SLOPE_DETECTION_RADIUS*Cos(LowestA+inc)
set y = .locY+SLOPE_DETECTION_RADIUS*Sin(LowestA+inc)
set z = GetTerrainZ(x, y)
set x2 = .locX+SLOPE_DETECTION_RADIUS*Cos(LowestA-inc)
set y2 = .locY+SLOPE_DETECTION_RADIUS*Sin(LowestA-inc)
set z2 = GetTerrainZ(x2, y2)
if z < LowestZ or z2 < LowestZ then
if z < z2 then
set LowestX = x
set LowestY = y
set LowestZ = z
set LowestA = LowestA+inc
else
set LowestX = x2
set LowestY = y2
set LowestZ = z2
set LowestA = LowestA-inc
endif
endif
exitwhen i == SLOPE_DETECTION_ACCURACY
endloop
endmethod
private method turn takes real targetAngle, real turnRate returns nothing
if Cos(.angle-targetAngle) <= Cos(turnRate) then
if Sin(targetAngle-.angle) >= 0 then
set .angle = .angle+turnRate
else
set .angle = .angle-turnRate
endif
else
set .angle = targetAngle
endif
endmethod
// Apply gravitational forces
private method slip takes real power, real direction, real turnRate returns nothing
local real diff = AngularDifferenceAbs(direction, .angle)
local real spdPow = 1-diff*2/bj_PI
local boolean b
call turn(direction, turnRate*(1-AngularDifferenceAbs(diff, HP)/HP))
set b = .speed < SLIP_MAXIMUM_SPEED
if spdPow < 0 or b then
set .speed = .speed+power*spdPow
if .speed > SLIP_MAXIMUM_SPEED and b then
set .speed = SLIP_MAXIMUM_SPEED
elseif .speed < 0 then
set .angle = direction
set .speed = 0.0
endif
endif
endmethod
method accelerate takes real power, real maxspeed, real direction, real turnRate returns nothing
local real diff
local real spdPow
if .speed == 0 then
set .angle = direction
if .speed < power then
set .speed = power
endif
set diff = 0.0
else
set diff = AngularDifferenceAbs(direction, .angle)
call turn(direction, turnRate*(1-AngularDifferenceAbs(diff, HP)/HP))
endif
set spdPow = 1-diff*2/bj_PI
if spdPow < 0 or .speed < maxspeed then
set .speed = .speed+power*spdPow
if .speed > maxspeed and spdPow >= 0 then
set .speed = maxspeed
elseif .speed < 0 then
set .angle = direction
set .speed = -.speed
endif
elseif .speed > maxspeed then
set .speed = .speed-.deacceleration
endif
endmethod
method enable takes boolean b returns nothing
set .isEnabled = b
if b then
set .locX = GetUnitX(.object)
set .locY = GetUnitY(.object)
set .locZ = GetUnitZ(.object)
set .isAirborne = true
endif
endmethod
method jump takes real power returns nothing
set .zVel = power
set .isAirborne = true
endmethod
method move takes real x, real y, real z returns nothing
set .locX = x
set .locY = y
set .locZ = GetTerrainZ(x, y)+z
call SetUnitX(.object, .locX)
call SetUnitY(.object, .locY)
call SetUnitZ(.object, .locZ)
if z > 0 then
set .isAirborne = true
else
set .isAirborne = false
call SetUnitFlyHeight(.object, 0, 0)
endif
endmethod
private static method Relocate takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
if .isEnabled and (.speed > 0 or .isMoving) then
// Detect teleportation
if not IsUnitInRangeXY(.object, .locX, .locY, .speed) then
set .locX = GetUnitX(.object)
set .locY = GetUnitY(.object)
else
call SetUnitX(.object, .locX)
call SetUnitY(.object, .locY)
endif
if .isAirborne then
call SetUnitZ(.object, .locZ)
endif
endif
endmethod
private static method OnPeriodic takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local real slope
local real slopeC
local real slopeS
local real slope2
local real reduction
local real friction
local real diff
local real a
local real r
local real x
local real y
local real z
local real x2
local real y2
local real ms
local integer order
local boolean moving
local boolean slippy
if .isEnabled and UnitAlive(.object) then
set x = .locX
set y = .locY
set a = GetUnitFacing(object)*bj_DEGTORAD
set ms = GetUnitMoveSpeed(object)*INTERVAL
if not .isAirborne then
call getLowestPoint()
if LowestX != .locX or LowestY != .locY then
set slope = TerrainUtils.GetTerrainSteepness(x, y, LowestX, LowestY)
if slope > HP then
set slope = HP
endif
set slopeC = Cos(slope)
set slopeS = Sin(slope)
else
set slope = 0
set slopeS = 0
set slopeC = 1
endif
set friction = TerrainUtils.GetTerrainFriction(x, y)
set slippy = friction <= SLIPPERY_TILE_MAX_FRICTION
set order = GetUnitCurrentOrder(.object)
set moving = .isMoving and order != 0 or .isPatroling and not IsUnitPaused(.object)
if UnitAlive(.target) then
if TimerGetRemaining(.disabler) == 0 then
if UnitDataRef.IsRegistered(.object) then
set r = UnitDataRef.GetAttackRange(.object)
elseif IsUnitType(.object, UNIT_TYPE_RANGED_ATTACKER) then
set r = RANGE_UNIT_ATTACK_RANGE
else
set r = MELEE_UNIT_ATTACK_RANGE
endif
set x2 = x-GetUnitX(.target)
set y2 = y-GetUnitY(.target)
set moving = moving or x2*x2+y2*y2 > r*r
endif
elseif .target != null then
set .isMoving = order != 0
if not .isMoving then
set .target = null
endif
set moving = not IsUnitPaused(.object)
endif
if moving then
if .orderTarget == null then
if IsUnitInRangeXY(.object, .orderX, .orderY, TARGET_ADJUSTMENT_RANGE) then
set ms = ms * TARGET_ADJUSTMENT_SPEED
endif
endif
if slippy then
call accelerate(.acceleration, ms, a, .turnRate*slopeC)
elseif slope < MAX_WALKABLE_SLOPE then
call accelerate(.acceleration, ms, a, GENERAL_UNIT_TURN_RATE_2*slopeC)
endif
endif
set diff = AngularDifferenceAbs(LowestA, .angle)
if not slippy then
set r = .speed*slopeS*SLOPE_SPEED_INCREMENT_STRENGTH*(1-diff*2/bj_PI)
if r < 0 then
if slope > MAX_WALKABLE_SLOPE then
set .speed = 0
elseif .speed >= ms*.5 then
set .speed = .speed+r
endif
else
set .speed = .speed+r
endif
if .speed > ms*MAXIMUM_SLOPE_SPEED_BONUS_RATE then
set .speed = ms*MAXIMUM_SLOPE_SPEED_BONUS_RATE
endif
endif
if diff >= HP and slope > MAX_WALKABLE_SLOPE then
set .speed = 0
endif
if slope != 0 then
call slip((GRAVITY+GENERAL_UNIT_MASS)*Exponent(slopeS+1), LowestA, SLIP_TURN_RATE*slopeS)
endif
if moving then
if slippy then
set .speed = .speed-friction*slopeC
endif
else
if slope > MAX_WALKABLE_SLOPE then
set .speed = .speed-friction*slopeC
else
set .speed = .speed-friction
endif
static if IDLE_DEACCELERATION then
set .speed = .speed-.deacceleration
endif
endif
if .speed > ABSOLUTE_SPEED_LIMIT then
set .speed = ABSOLUTE_SPEED_LIMIT
elseif .speed < 0 then
set .speed = 0
endif
if moving then
if .orderTarget == null then
if AngularDifferenceAbs(Atan2(.orderY-.locY, .orderX-.locX), GetUnitFacing(.object)*bj_DEGTORAD) > HP / 2 then
call IssuePointOrderById(.object, .orderId, .orderX, .orderY)
endif
else
if AngularDifferenceAbs(Atan2(GetWidgetY(.orderTarget)-.locY, GetWidgetX(.orderTarget)-.locX), GetUnitFacing(.object)*bj_DEGTORAD) > HP / 2 then
call IssueTargetOrderById(.object, .orderId, .orderTarget)
endif
endif
endif
endif
if x < WorldMaxX and x > WorldMinX and y < WorldMaxY and y > WorldMinY then
set x2 = x+SLOPE_DETECTION_RADIUS*Cos(.angle)
set y2 = y+SLOPE_DETECTION_RADIUS*Sin(.angle)
set x = x+.speed*Cos(.angle)
set y = y+.speed*Sin(.angle)
set slope = -TerrainUtils.GetTerrainSteepness(.locX, .locY, x2, y2)
if .isAirborne then
set .locZ = .locZ+.zVel
set .zVel = .zVel-(GRAVITY+GENERAL_UNIT_MASS)
set z = GetTerrainZ(x, y)
if .locZ <= z+.01 then
set .locZ = z
set .isAirborne = false
// Calculate speed change when colliding with the ground
set reduction = Atan((z-GetTerrainZ(.locX, .locY))/.speed)
set .speed = .speed*((bj_PI-Atan(RAbsBJ(.zVel)/.speed)-reduction)/HP-1)
if .speed > ABSOLUTE_SPEED_LIMIT then
set .speed = ABSOLUTE_SPEED_LIMIT
elseif .speed < 0 then
set .speed = 0
endif
call SetUnitFlyHeight(.object, 0, 0)
endif
else
set x2 = x-SLOPE_DETECTION_RADIUS*Cos(.angle)
set y2 = y-SLOPE_DETECTION_RADIUS*Sin(.angle)
set slope2 = -TerrainUtils.GetTerrainSteepness(x2, y2, x, y)
if slope2 > HP then
set slope2 = HP
endif
if slope > slope2 and slope-slope2 > MINIMUM_TAKE_OFF_TILTNESS then
// Check if the velocity is enough to escape the gravitational force
set .zVel = .speed*Sin(slope)
if .zVel > ((GetTerrainZ(x, y)-GetTerrainZ(x2, y2))+(GRAVITY+GENERAL_UNIT_MASS)) then
set .isAirborne = true
set .locZ = GetTerrainZ(.locX, .locY)+.zVel
endif
endif
endif
call SetUnitX(.object, WorldBounds.maxX)
call SetUnitY(.object, WorldBounds.maxY)
if IsTerrainWalkable(x, y) or .isAirborne then
set .locX = x
set .locY = y
else
set .angle = angle + bj_PI
set .speed = .speed * COLLISION_BOUNCE_FACTOR
endif
call SetUnitX(.object, .locX)
call SetUnitY(.object, .locY)
endif
endif
set t = null
endmethod
static method RegisterUnit takes unit whichUnit returns thistype
local thistype this = GetUnitUserData(whichUnit)
if .object == null then
set .object = whichUnit
set .angle = GetUnitFacing(whichUnit)*bj_DEGTORAD
set .isAirborne = false
set .isEnabled = true
set .isPatroling = false
set .locX = GetUnitX(whichUnit)
set .locY = GetUnitY(whichUnit)
set .locZ = 0.0
set .speed = 0
set .turnRate = GENERAL_UNIT_TURN_RATE
set .tick = NewTimerEx(this)
set .locator = NewTimerEx(this)
set .disabler = NewTimerEx(this)
set .acceleration = GENERAL_UNIT_ACCELERATION
set .deacceleration = GENERAL_UNIT_DEACCELERATION
static if not LIBRARY_AutoFly then
if UnitAddAbility(.object, 'Amrf') and UnitRemoveAbility(.object, 'Amrf') then
endif
endif
call GroupAddUnit(Objects, .object)
call TriggerRegisterUnitEvent(DetectTrigger, .object, EVENT_UNIT_ACQUIRED_TARGET)
call TimerStart(.locator, RELOCATION_RATE, true, function thistype.Relocate)
call TimerStart(.tick, INTERVAL, true, function thistype.OnPeriodic)
endif
return this
endmethod
private static method OnImmediateOrder takes nothing returns boolean
local unit u = GetTriggerUnit()
local thistype this = GetUnitUserData(u)
local integer order = GetIssuedOrderId()
if .object != null and .isEnabled and GetUnitCurrentOrder(u) == order then
set .isMoving = false
set .isPatroling = false
set .orderId = order
set .orderTarget = null
endif
set u = null
return false
endmethod
private static method OnOrder takes nothing returns boolean
local unit u = GetTriggerUnit()
local thistype this = GetUnitUserData(u)
local integer order = GetIssuedOrderId()
if .object != null and .isEnabled and GetUnitCurrentOrder(u) == order then
set .isPatroling = false
set .target = null
set .orderId = order
set .orderTarget = GetOrderTarget()
if order == 851973 then // If stun order
set .isMoving = false
else
if .orderTarget == null then
set .orderX = GetOrderPointX()
set .orderY = GetOrderPointY()
set .isMoving = true
else
set .isMoving = true
endif
endif
endif
set u = null
return false
endmethod
private static method OnAttack takes nothing returns boolean
local unit u = GetAttacker()
local thistype this = GetUnitUserData(u)
if .object != null and .isEnabled then
set .isMoving = false
set .target = GetTriggerUnit()
if UnitDataRef.IsRegistered(u) then
call TimerStart(.disabler, UnitDataRef.GetBackswingPoint(u), false, null)
else
call TimerStart(.disabler, GENERAL_UNIT_BACKSWING_POINT, false, null)
endif
endif
set u = null
return false
endmethod
private static method OnDetect takes nothing returns boolean
local thistype this = GetUnitUserData(GetTriggerUnit())
if .object != null and .isEnabled then
set .isMoving = true
set .isPatroling = true
endif
return false
endmethod
private static method OnCast takes nothing returns boolean
local thistype this = GetUnitUserData(GetTriggerUnit())
if .object != null and .isEnabled and (GetSpellTargetX() != 0 or GetSpellTargetY() != 0) then
set .isMoving = false
set .target = null
endif
return false
endmethod
static if AUTO_REGISTER then
private static method OnIndex takes nothing returns boolean
local unit u = GetIndexedUnit()
if FilterUnit(u) then
call RegisterUnit(u)
endif
set u = null
return false
endmethod
endif
private static method OnDeindex takes nothing returns boolean
local thistype this = thistype[GetIndexedUnit()]
local group tempG
local unit u
if .object != null then
call GroupRemoveUnit(Objects, .object)
call destroy()
set EventWasted = EventWasted+1
if EventWasted == TRIGGER_RECYCLE_THRESHOLD then
set EventWasted = 0
call DestroyTrigger(DetectTrigger)
set DetectTrigger = CreateTrigger()
call TriggerAddCondition(DetectTrigger, DetectFunc)
set tempG = CreateGroup()
loop
set u = FirstOfGroup(Objects)
exitwhen u == null
call GroupRemoveUnit(Objects, u)
call GroupAddUnit(tempG, u)
call TriggerRegisterUnitEvent(DetectTrigger, u, EVENT_UNIT_ACQUIRED_TARGET)
endloop
call DestroyGroup(Objects)
set Objects = tempG
set tempG = null
endif
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local player p
local integer i = 0
local trigger t1 = CreateTrigger()
local trigger t2 = CreateTrigger()
local trigger t3 = CreateTrigger()
local trigger t4 = CreateTrigger()
set WorldMaxX = GetRectMaxX(bj_mapInitialPlayableArea)
set WorldMaxY = GetRectMaxY(bj_mapInitialPlayableArea)
set WorldMinX = GetRectMinX(bj_mapInitialPlayableArea)
set WorldMinY = GetRectMinX(bj_mapInitialPlayableArea)
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
set p = Player(i)
call TriggerRegisterPlayerUnitEvent(t4, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
call TriggerRegisterPlayerUnitEvent(t1, p, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
call TriggerRegisterPlayerUnitEvent(t1, p, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
call TriggerRegisterPlayerUnitEvent(t2, p, EVENT_PLAYER_UNIT_SPELL_CAST, null)
call TriggerRegisterPlayerUnitEvent(t3, p, EVENT_PLAYER_UNIT_ATTACKED, null)
set i = i + 1
endloop
set DetectFunc = Condition(function thistype.OnDetect)
call TriggerAddCondition(DetectTrigger, DetectFunc)
call TriggerAddCondition(t1, Condition(function thistype.OnOrder))
call TriggerAddCondition(t2, Condition(function thistype.OnCast))
call TriggerAddCondition(t3, Condition(function thistype.OnAttack))
call TriggerAddCondition(t4, Condition(function thistype.OnImmediateOrder))
static if AUTO_REGISTER then
call RegisterUnitIndexEvent(Condition(function thistype.OnIndex), UnitIndexer.INDEX)
endif
call RegisterUnitIndexEvent(Condition(function thistype.OnDeindex), UnitIndexer.DEINDEX)
endmethod
endstruct
endlibrary