scope Init initializer PreInit
globals// constants
private constant integer CREEP_ID = 'hpea'
private constant integer HERO_ID = 'H000'
private constant integer TEST_OBJECT_ID = 'N001'
private constant real CAM_HEIGHT_ADD = 200.00
private constant real DECAY_TIME = 20.00
private constant integer CAMP_COUNT = 30
private constant integer CREEP_COUNT = 20
private constant player CREEPS_OWNER = Player(1)
endglobals
globals
private constant hashtable table = InitHashtable()
private real CENTER_X
private real CENTER_Y
endglobals
private constant function Map takes nothing returns rect
return bj_mapInitialPlayableArea
endfunction
private function RandomX takes nothing returns real
return GetRandomReal(GetRectMinX(Map()), GetRectMaxX(Map()))
endfunction
private function RandomY takes nothing returns real
return GetRandomReal(GetRectMinY(Map()), GetRectMaxY(Map()))
endfunction
private function IsHero takes unit u returns boolean
return IsUnitType(u, UNIT_TYPE_HERO)
endfunction
private function A takes nothing returns nothing
local real manapercent = 100
local unit u = GetTriggerUnit()
if IsHero(u) then
call TriggerSleepAction(2.00)
call ReviveHero(u, CENTER_X, CENTER_Y, true)
call SetUnitFlyHeight(u, 400.00, 0.00)
call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA )*manapercent*0.01)
call IssueImmediateOrder(u, "manashieldon")
endif
set u = null
endfunction
private function B takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer handleId
local real x
local real y
if not IsHero(u) then
call TriggerSleepAction(DECAY_TIME)
set handleId = GetHandleId(u)
set x = LoadReal(table, handleId, 0)
set y = LoadReal(table, handleId, 1)
call RemoveUnit(u)
call FlushChildHashtable(table, handleId)
set handleId = GetHandleId(CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), x, y, 0))
call SaveReal(table, handleId, 0, x)
call SaveReal(table, handleId, 1, y)
endif
set u = null
endfunction
private function C takes nothing returns nothing
local unit u = GetTriggerUnit()
if IsUnitAlly(u, GetTriggerPlayer()) then
if IsHero(u) then
call SetHeroLevel(u, (GetHeroLevel(u) + 1), false)
endif
endif
set u = null
endfunction
private function D takes nothing returns nothing
if GetLocalPlayer() == Player(0) then
call SetCameraField(CAMERA_FIELD_ZOFFSET, GetCameraField(CAMERA_FIELD_ZOFFSET) + CAM_HEIGHT_ADD, 0)
endif
endfunction
private function Init takes nothing returns nothing
local trigger t1 = CreateTrigger()
local trigger t2 = CreateTrigger()
local trigger t3 = CreateTrigger()
local trigger t4 = CreateTrigger()
local integer a = 1
local integer b = 1
local quest q1 = CreateQuest()
local quest q2 = CreateQuest()
local string q1_title = "|cff0000ffSpinning Black Hole|r v1.8 |cffffcc00[3D]|r"
local string q2_title = "|cffffcc00Share your Feedback|r"
local string q1_descript = "Test Commands:|n - Press Esc to zoom out camera view|n - Select your hero to level up|n"
local string q2_descript = "If you found some bugs and glithes, you can PM AGD at HiveWorkshop.com.|nPlease also share your comments and suggestions regarding the spell."
local string q1_icon = "war3mapImported\\WarcraftIcon.tga"
local string q2_icon = "war3mapImported\\NoIcon.tga"
local unit u = CreateUnit(CREEPS_OWNER, TEST_OBJECT_ID, RandomX(), RandomY(), 0)
local real x
local real y
local integer handleId
call SelectUnit(CreateUnit(Player(0), HERO_ID, CENTER_X, CENTER_Y, 0), true)
call QuestSetTitle(q1, q1_title)
call QuestSetDescription(q1, q1_descript)
call QuestSetIconPath(q1, q1_icon)
call QuestSetRequired(q1, true)
call QuestSetDiscovered(q1, true)
call QuestSetCompleted(q1, false)
call QuestSetTitle(q2, q2_title)
call QuestSetDescription(q2, q2_descript)
call QuestSetIconPath(q2, q2_icon)
call QuestSetRequired(q2, false)
call QuestSetDiscovered(q2, true)
call QuestSetCompleted(q2, false)
call TriggerRegisterPlayerUnitEvent(t1, CREEPS_OWNER, EVENT_PLAYER_UNIT_DEATH, null)
call TriggerRegisterPlayerUnitEvent(t2, CREEPS_OWNER, EVENT_PLAYER_UNIT_DECAY, null)
call TriggerRegisterPlayerUnitEvent(t3, Player(0), EVENT_PLAYER_UNIT_SELECTED, null)
call TriggerRegisterPlayerEvent(t4, Player(0), EVENT_PLAYER_END_CINEMATIC)
call TriggerAddAction(t1, function A)
call TriggerAddAction(t2, function B)
call TriggerAddAction(t3, function C)
call TriggerAddAction(t4, function D)
call FogEnable(false)
call FogMaskEnable(false)
call SetPlayerState(CREEPS_OWNER, PLAYER_STATE_GIVES_BOUNTY, 1)
call SetHeroLevel(u, 10, false)
call SelectHeroSkill(u, 'ANms')
call SelectHeroSkill(u, 'ANms')
call SelectHeroSkill(u, 'ANms')
call SelectHeroSkill(u, 'ANms')
call SelectHeroSkill(u, 'ANms')
loop
exitwhen a > CAMP_COUNT
set b = 1
set x = RandomX()
set y = RandomY()
loop
exitwhen b > CREEP_COUNT
set handleId = GetHandleId(CreateUnit(CREEPS_OWNER, CREEP_ID, x, y, 0))
call SaveReal(table, handleId, 0, x)
call SaveReal(table, handleId, 1, y)
set b = b + 1
endloop
set a = a + 1
endloop
endfunction
private function PreInit takes nothing returns nothing
set CENTER_X = GetRectCenterX(Map())
set CENTER_Y = GetRectCenterY(Map())
call Init()
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 120, "
Test:
- Press ESC in-game to zoom out camera for a better view of the black hole explosion.
- Then scroll the mouse wheel upwards to go back to default camera altitude.
- Select your hero to level up.")
endfunction
endscope
Name | Type | is_array | initial_value |
library UnitMotion /*
*/uses /*
*/ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*/Vector /* http://www.wc3c.net/showthread.php?t=87027
*/Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
*/UnitDex /* https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
*/optional AutoFly /* https://www.hiveworkshop.com/threads/snippet-autofly.249422/
*///! novjass
/*
Description:
This system is used to handle if possible, all unit movements, internally. You can use this
to easily create knockback movements, throws, and jumps whether it be uniform or accelerated
motion. By default, this system also takes into consideration the units' acceleration due to
gravity as well as the side effect of gravity, i.e., ground friction. But if you don't need
these additional features, you can just set their values to 0 in the configuration section
below.
*/
|=========|
| CREDITS |
|=========|
Author:
- AGD
Dependencies:
- Anitarf (Vector)
- Vexorian (TimerUtils)
- Bribe (Table)
- TriggerHappy (UnitDex, AutoFly)
- Nestharus (ErrorMessage)
|-----|
| API |
|-----|
struct UnitMotion/*
*/static constant real FPS /* Frames per second
*/static constant real FRAME_RATE /* Reciprocal of FPS (Refresh rate)
*/static constant real GRAVITY /* Acceleration due to gravity in units per second^2
*/static constant real GROUND_FRICTION /* The coefficient of friction of the ground
*/static constant real DEFAULT_UNIT_FRICTION /* The default value of unit's coefficient of friction
*/static constant boolean AUTO_REGISTER_UNITS /* Determines whether newly entering units in the game are
automatically added to the system
*/readonly static UnitMotion triggerInstance /* Use this to refer to the UnitMotion instance of
the moving unit inside a motion handler function
*/real x /* The x-component of the UnitMotion
*/real y /* The y-component of the UnitMotion
*/real z /* The z-component of the UnitMotion
*/real coefficientOfFriction /* The coefficient of friction of the unit
*/readonly real friction /* The magnitude of the ground friction vector acting on the unit
*/readonly boolean airborne /* Determines if the unit referred by the UnitMotion instance is airborne
*/readonly boolean freeFalling /* Determines if the unit referred by the UnitMotion instance is free-falling
*/readonly unit unit /* The unit the UnitMotion instance belongs to
*/static method operator [] takes unit whichUnit returns UnitMotion/*
- Reftrieves the UnitMotion instance corresponding to the input unit
*/method destroy takes nothing returns nothing/*
- Destroys a UnitMotion instance
- Only available when AUTO_REGISTER_UNITS == false
*/method addVelocityByVector takes vector whichVector returns this/*
- Adds a vector to the UnitMotion's velocity vector
*/method addVelocity takes Velocity velocity returns this/*
*/method removeVelocity takes Velocity velocity returns this/*
- Adds/Removes a Velocity from this UnitMotion instance
*/method addAccelerationByVector takes vector whichVector returns this/*
- Adds a vector to the UnitMotion's acceleration vector
*/method addAcceleration takes Acceleration acceleration returns this/*
*/method removeAcceleration takes Acceleration acceleration returns this/*
- Adds/Removes an Acceleration from this UnitMotion instance
*/method addTorqueByVector takes vector axisOfRotation returns this/*
- Adds a vector to the UnitMotion's torque axis vector
- The length of the <axisOfRotation> vector determines the magnitude of the torque
*/method addTorqueByVectorEx takes vector axisOfRotation, real radius returns this/*
- Adds a vector to the UnitMotion's torque axis vector
- The magnitude added by the vector is equal to the magnitude of the torque necessary to make
a circular motion with a radius equal to the input radius
*/method addTorque takes Torque torque returns this/*
*/method removeTorque takes Torque torque returns this/*
- Adds a centripetal acceleration to the UnitMotion instance
*/method addTorqueIncrement takes vector axisOfRotation, real magnitude returns this/*
- Adds an increment in the torque's magnitude per period
*/method registerMotionHandler takes boolexpr expr returns this/*
*/method unregisterMotionHandler takes boolexpr expr returns this/*
*/method clearMotionHandlers takes nothing returns this/*
- Manages boolexprs that runs when the unit moves due to a vector applied
to the unit using this system
*/static method registerUnitAddHandler takes boolexpr expr returns nothing/*
*/static method unregisterUnitAddHandler takes boolexpr expr returns nothing/*
*/static method clearUnitAddHandlers takes nothing returns nothing/*
- Manages boolexprs that runs when a UnitMotion instance is first created for a unit
- Only available when UnitMotion.AUTO_REGISTER_UNITS == false
*/static method registerRemoveAddHandler takes boolexpr expr returns nothing/*
*/static method unregisterRemoveAddHandler takes boolexpr expr returns nothing/*
*/static method clearUnitRemoveHandlers takes nothing returns nothing/*
- Manages boolexprs that runs when a UnitMotion instance of a unit is destroyed
- Only available when UnitMotion.AUTO_REGISTER_UNITS == false
*/struct Velocity/*
*/static method create takes nothing returns Velocity/*
*/method destroy takes nothing returns nothing/*
- Creates/Destroys a Velocity instance
*/method add takes vector whichVector returns nothing/*
- Adds a vector to this Velocity's vector
*/struct Acceleration/*
*/static method create takes nothing returns Acceleration/*
*/method destroy takes nothing returns nothing/*
- Creates/Destroys an Acceleration instance
*/method add takes vector whichVector returns nothing/*
- Adds a vector to this Acceleration's vector
*/struct Torque/*
*/static method create takes nothing returns Torque/*
*/method destroy takes nothing returns nothing/*
- Creates/Destroys a Torque instance
*/method add takes vector axisOfRotation returns nothing/*
- Adds a vector to this Torque's vector
- The length of the <axisOfRotation> vector determines the magnitude
of the torque
*///! endnovjass
private keyword Init
private keyword List
struct UnitMotion extends array
/********************************************************/
/* SYSTEM CONFIGURATION */
/********************************************************/
/*
The number of frames per second in which
the periodic function operates.
Default Value: 32 */
static constant real FPS = 32.00
/*
The 'acceleration' due to gravity.
Default Value: -981.00 */
static constant real GRAVITY = -981.00
/*
The coefficient of the ground friction.
Default Value: 0.70 */
static constant real GROUND_FRICTION = 0.70
/*
The default value for the coefficient of
friction for units. This value is applied
upon UnitMovement creation.
Default Value: 0.60 */
static constant real DEFAULT_UNIT_FRICTION = 0.60
/*
Determines if newly created units are
automatically registered into the
system. */
static constant boolean AUTO_REGISTER_UNITS = false
/********************************************************/
/* END OF CONFIGURATION */
/********************************************************/
/*======================================================*/
/* Do not change anything below this line if you're */
/* not so sure on what you're doing. */
/*======================================================*/
private thistype prev
private thistype next
private boolean flag
private integer motionHandlerCount
private real deltaFriction
private real unitFriction
private trigger motionHandlerTrigger
private vector velocity
private vector acceleration
private vector torqueAxis
private vector torqueIncrement
private vector timerForce
readonly static thistype triggerInstance = 0
readonly static thistype addedInstance = 0
readonly static thistype removedInstance = 0
static constant real FRAME_RATE = 1.00/FPS
private static timer timer = CreateTimer()
private static group enumerator = CreateGroup()
private static code periodicCode
private static TableArray tableArray
private static TableArray velocityTable
private static TableArray accelerationTable
private static TableArray torqueTable
static if not thistype.AUTO_REGISTER_UNITS then
private static trigger unitAddHandlerTrigger
private static trigger unitRemoveHandlerTrigger
private static integer unitAddHandlerCount = 0
private static integer unitRemoveHandlerCount = 0
endif
private method constructor takes nothing returns nothing
local thistype last = thistype(0).prev
set thistype(0).prev = this
set last.next = this
set this.prev = last
set this.next = 0
set this.velocity = vector.create(0.00, 0.00, 0.00)
set this.acceleration = vector.create(0.00, 0.00, GRAVITY*FRAME_RATE)
set this.torqueAxis = vector.create(0.00, 0.00, 0.00)
set this.torqueIncrement = vector.create(0.00, 0.00, 0.00)
set this.unitFriction = DEFAULT_UNIT_FRICTION
set this.deltaFriction = GRAVITY*GROUND_FRICTION*DEFAULT_UNIT_FRICTION*FRAME_RATE
set this.flag = true
static if not LIBRARY_AutoFly then
if UnitAddAbility(GetUnitById(this), 'Arav') and UnitRemoveAbility(GetUnitById(this), 'Arav') then
endif
endif
if this.prev == 0 then
call TimerStart(timer, FRAME_RATE, true, periodicCode)
endif
endmethod
private method destructor takes nothing returns nothing
static if not thistype.AUTO_REGISTER_UNITS then
local thistype prev = removedInstance
set removedInstance = this
call TriggerEvaluate(unitRemoveHandlerTrigger)
set removedInstance = prev
endif
call this.velocity.destroy()
call this.acceleration.destroy()
call this.torqueAxis.destroy()
call this.torqueIncrement.destroy()
call tableArray[this].flush()
call DestroyTrigger(this.motionHandlerTrigger)
set this.motionHandlerTrigger = null
set this.velocity = 0
set this.acceleration = 0
set this.torqueAxis = 0
set this.torqueIncrement = 0
set this.deltaFriction = 0.00
set this.unitFriction = 0.00
set this.motionHandlerCount = 0
set this.flag = false
set this.next.prev = this.prev
set this.prev.next = this.next
if thistype(0).next == 0 then
call PauseTimer(timer)
endif
endmethod
static method operator [] takes unit u returns thistype
static if not thistype.AUTO_REGISTER_UNITS then
local thistype this = GetUnitId(u)
local thistype prev
if not this.flag then
call this.constructor()
set prev = addedInstance
set addedInstance = this
call TriggerEvaluate(unitAddHandlerTrigger)
set addedInstance = prev
endif
return this
else
return GetUnitId(u)
endif
endmethod
static if not thistype.AUTO_REGISTER_UNITS then
method destroy takes nothing returns nothing
debug call ThrowError(not this.flag, "UnitMotion", "destroy", "thistype", this, "Attempted to destroy an unallocated instance")
call this.destructor()
endmethod
endif
method operator unit takes nothing returns unit
debug call ThrowError(not this.flag, "UnitMotion", "unit", "thistype", this, "Attempted to access an unallocated instance field")
return GetUnitById(this)
endmethod
method operator airborne takes nothing returns boolean
debug call ThrowError(not this.flag, "UnitMotion", "airborne", "thistype", this, "Attempted to access an unallocated instance field")
return GetUnitFlyHeight(this.unit) > GetUnitDefaultFlyHeight(this.unit) + 10.00
endmethod
method operator x= takes real x returns nothing
debug call ThrowError(not this.flag, "UnitMotion", "x=", "thistype", this, "Attempted to configure an unallocated instance")
set this.velocity.x = x
endmethod
method operator x takes nothing returns real
debug call ThrowError(not this.flag, "UnitMotion", "x", "thistype", this, "Attempted to access an unallocated instance field")
return this.velocity.x
endmethod
method operator y= takes real y returns nothing
debug call ThrowError(not this.flag, "UnitMotion", "y=", "thistype", this, "Attempted to configure an unallocated instance.")
set this.velocity.y = y
endmethod
method operator y takes nothing returns real
debug call ThrowError(not this.flag, "UnitMotion", "y", "thistype", this, "Attempted to access an unallocated instance field.")
return this.velocity.y
endmethod
method operator z= takes real z returns nothing
debug call ThrowError(not this.flag, "UnitMotion", "z=", "thistype", this, "Attempted to configure an unallocated instance")
set this.velocity.z = z
endmethod
method operator z takes nothing returns real
debug call ThrowError(not this.flag, "UnitMotion", "z", "thistype", this, "Attempted to access an unallocated instance field")
return this.velocity.z
endmethod
method operator coefficientOfFriction= takes real frictionFactor returns nothing
debug call ThrowError(not this.flag, "UnitMotion", "coefficientOfFriction=", "thistype", this, "Attempted to configure an unallocated instance")
set this.unitFriction = frictionFactor
set this.deltaFriction = RMinBJ(this.acceleration.z*GROUND_FRICTION*frictionFactor, 0.00)
endmethod
method operator coefficientOfFriction takes nothing returns real
debug call ThrowError(not .flag, "UnitMotion", "coefficientOfFriction", "thistype", this, "Attempted to access an unallocated instance field")
return this.unitFriction
endmethod
method operator friction takes nothing returns real
debug call ThrowError(not .flag, "UnitMotion", "friction", "thistype", this, "Attempted to access an unallocated instance field")
return this.deltaFriction*FPS
endmethod
method operator freeFalling takes nothing returns boolean
local vector acceleration = this.acceleration
debug call ThrowError(not this.flag, "UnitMotion", "freeFalling", "thistype", this, "Attempted to use an unallocated instance")
return this.airborne and /*
*/ acceleration.x == 0.00 and /*
*/ acceleration.y == 0.00 and /*
*/ acceleration.z == GRAVITY*FRAME_RATE
endmethod
private static method addVector takes vector this, vector toAdd returns nothing
if toAdd < 0 then
call this.subtract(-toAdd)
else
call this.add(toAdd)
endif
endmethod
method addVelocityByVector takes vector whichVector returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "addVelocityByVector()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(whichVector == 0, "UnitMotion", "addVelocityByVector()", "thistype", this, "Attempted to use a null vector instance")
call addVector(this.velocity, whichVector)
return this
endmethod
method addVelocity takes Velocity velocity returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "addVelocity()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(velocity == 0, "UnitMotion", "addVelocity()", "thistype", this, "Attempted to use a null Velocity instance")
debug call ThrowError(velocity < 0, "UnitMotion", "addVelocity()", "thistype", this, "Attempted to use an invalid Velocity instance")
debug call ThrowError(velocityTable[this].has(velocity), "UnitMotion", "addVelocity()", "thistype", this, "Attempted to add an already added Velocity instance")
call this.velocity.add(velocity)
set velocityTable[this][velocity] = s__Velocity_references[velocity].insert(this)
return this
endmethod
method removeVelocity takes Velocity velocity returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "removeVelocity()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(velocity == 0, "UnitMotion", "removeVelocity()", "thistype", this, "Attempted to use a null Velocity instance")
debug call ThrowError(velocity < 0, "UnitMotion", "removeVelocity()", "thistype", this, "Attempted to use an invalid Velocity instance")
debug call ThrowError(not velocityTable[this].has(velocity), "UnitMotion", "removeVelocity()", "thistype", this, "Attempted to remove an unadded Velocity instance")
call this.velocity.subtract(velocity)
call List(velocityTable[this][velocity]).remove()
call velocityTable[this].remove(velocity)
return this
endmethod
method addAccelerationByVector takes vector whichVector returns thistype
local vector acceleration = this.acceleration
debug call ThrowError(not this.flag, "UnitMotion", "addAccelerationByVector()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(whichVector == 0, "UnitMotion", "addAccelerationByVector()", "thistype", this, "Attempted to use a null vector instance")
call whichVector.scale(FRAME_RATE)
call addVector(acceleration, whichVector)
call whichVector.scale(FPS)
set this.deltaFriction = RMinBJ(acceleration.z*GROUND_FRICTION*this.unitFriction, 0.00)
return this
endmethod
method addAcceleration takes Acceleration acceleration returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "addAcceleration()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(acceleration == 0, "UnitMotion", "addAcceleration()", "thistype", this, "Attempted to use a null Acceleration instance")
debug call ThrowError(acceleration < 0, "UnitMotion", "addAcceleration()", "thistype", this, "Attempted to use an invalid Acceleration instance")
debug call ThrowError(accelerationTable[this].has(acceleration), "UnitMotion", "addAcceleration()", "thistype", this, "Attempted to add an already added Acceleration instance")
call this.acceleration.add(acceleration)
set this.deltaFriction = RMinBJ(this.acceleration.z*GROUND_FRICTION*this.unitFriction, 0.00)
set accelerationTable[this][acceleration] = s__Acceleration_references[acceleration].insert(this)
return this
endmethod
method removeAcceleration takes Acceleration acceleration returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "removeAcceleration()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(acceleration == 0, "UnitMotion", "removeAcceleration()", "thistype", this, "Attempted to use a null Acceleration instance")
debug call ThrowError(acceleration < 0, "UnitMotion", "removeAcceleration()", "thistype", this, "Attempted to use an invalid Acceleration instance")
debug call ThrowError(not accelerationTable[this].has(acceleration), "UnitMotion", "removeAcceleration()", "thistype", this, "Attempted to remove an unadded Acceleration instance")
call this.acceleration.subtract(acceleration)
set this.deltaFriction = RMinBJ(this.acceleration.z*GROUND_FRICTION*this.unitFriction, 0.00)
call List(accelerationTable[this][acceleration]).remove()
call accelerationTable[this].remove(acceleration)
return this
endmethod
method addTorqueByVector takes vector axis returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "addTorqueByVector()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(axis == 0, "UnitMotion", "addTorqueByVector()", "thistype", this, "Attempted to use a null vector instance")
call axis.scale(FRAME_RATE)
call addVector(this.torqueAxis, axis)
call axis.scale(FPS)
return this
endmethod
method addTorqueByVectorEx takes vector axis, real radius returns thistype
local real speed = this.velocity.getLength()
local real length = axis.getLength()
debug call ThrowError(not this.flag, "UnitMotion", "addTorqueByVectorEx()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(axis == 0, "UnitMotion", "addTorqueByVectorEx()", "thistype", this, "Attempted to use a null vector instance")
debug call ThrowError(radius == 0.00, "UnitMotion", "addTorqueByVectorEx()", "thistype", this, "Attempted to input a zero radius")
call axis.scale((speed*speed)/(radius*length))
call addVector(this.torqueAxis, axis)
call axis.scale((radius*length)/(speed*speed))
return this
endmethod
method addTorque takes Torque torque returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "addTorque()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(torque == 0, "UnitMotion", "addTorque()", "thistype", this, "Attempted to use a null Torque instance")
debug call ThrowError(torque < 0, "UnitMotion", "addTorque()", "thistype", this, "Attempted to use an invalid Torque instance")
debug call ThrowError(torqueTable[this].has(torque), "UnitMotion", "addTorque()", "thistype", this, "Attempted to add an already added Torque instance")
call this.torqueAxis.add(torque)
set torqueTable[this][torque] = s__Torque_references[torque].insert(this)
return this
endmethod
method removeTorque takes Torque torque returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "removeTorque()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(torque == 0, "UnitMotion", "removeTorque()", "thistype", this, "Attempted to use a null Torque instance")
debug call ThrowError(torque < 0, "UnitMotion", "removeTorque()", "thistype", this, "Attempted to use an invalid Torque instance")
debug call ThrowError(torqueTable[this].has(torque), "UnitMotion", "removeTorque()", "thistype", this, "Attempted to remove an unadded Torque instance")
call this.torqueAxis.subtract(torque)
call List(torqueTable[this][torque]).remove()
call torqueTable[this].remove(torque)
return this
endmethod
method addTorqueIncrement takes vector axis, real magnitude returns thistype
local real length = axis.getLength()
debug call ThrowError(not this.flag, "UnitMotion", "addTorqueIncrement()", "thistype", this, "Attempted to use an unallocated instance")
call axis.setLength(FRAME_RATE*magnitude/length)
call addVector(this.torqueIncrement, axis)
call axis.setLength(FPS*length/magnitude)
return this
endmethod
method registerMotionHandler takes boolexpr expr returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "registerMotionHandler()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(expr == null, "UnitMotion", "registerMotionHandler()", "thistype", this, "Attemped to register a null boolexpr")
debug call ThrowError(tableArray[this].handle.has(GetHandleId(expr)), "UnitMotion", "registerHandler()", "thistype", this, "Attemped to register a boolexpr twice")
if this.motionHandlerCount == 0 then
set this.motionHandlerTrigger = CreateTrigger()
endif
set this.motionHandlerCount = this.motionHandlerCount + 1
set tableArray[this].triggercondition[GetHandleId(expr)] = TriggerAddCondition(this.motionHandlerTrigger, expr)
return this
endmethod
method unregisterMotionHandler takes boolexpr expr returns thistype
local integer exprId
debug call ThrowError(not this.flag, "UnitMotion", "unregisterMotionHandler()", "thistype", this, "Attempted to use an unallocated instance")
debug call ThrowError(expr == null, "UnitMotion", "unregisterMotionHandler()", "thistype", this, "Attemped to unregister a null boolexpr")
debug call ThrowError(not tableArray[this].handle.has(GetHandleId(expr)), "UnitMotion", "unregisterHandler()", "thistype", this, "Attemped to unregister a boolexpr twice")
set this.motionHandlerCount = this.motionHandlerCount - 1
if this.motionHandlerCount == 0 then
call tableArray[this].handle.remove(GetHandleId(expr))
call DestroyTrigger(this.motionHandlerTrigger)
set this.motionHandlerTrigger = null
else
set exprId = GetHandleId(expr)
call TriggerRemoveCondition(this.motionHandlerTrigger, tableArray[this].triggercondition[exprId])
call tableArray[this].handle.remove(exprId)
endif
return this
endmethod
method clearMotionHandlers takes nothing returns thistype
debug call ThrowError(not this.flag, "UnitMotion", "unit", "thistype", this, "Attempted to use an unallocated instance")
call tableArray[this].flush()
call DestroyTrigger(this.motionHandlerTrigger)
set this.motionHandlerTrigger = null
set this.motionHandlerCount = 0
return this
endmethod
static if not thistype.AUTO_REGISTER_UNITS then
static method registerUnitAddHandler takes boolexpr expr returns nothing
debug call ThrowError(expr == null, "UnitMotion", "registerUnitAddHandler()", "thistype", 0, "Attemped to register a null boolexpr")
debug call ThrowError(tableArray[0].handle.has(GetHandleId(expr)), "UnitMotion", "registerUnitAddHandler()", "thistype", 0, "Attemped to register a boolexpr twice")
if unitAddHandlerCount == 0 then
set unitAddHandlerTrigger = CreateTrigger()
endif
set unitAddHandlerCount = unitAddHandlerCount + 1
set tableArray[0].triggercondition[GetHandleId(expr)] = TriggerAddCondition(unitAddHandlerTrigger, expr)
endmethod
static method unregisterUnitAddHandler takes boolexpr expr returns nothing
local integer exprId
debug call ThrowError(expr == null, "UnitMotion", "unregisterUnitAddHandler()", "thistype", 0, "Attemped to unregister a null boolexpr")
debug call ThrowError(not tableArray[0].handle.has(GetHandleId(expr)), "UnitMotion", "unregisterUnitAddHandler()", "thistype", 0, "Attemped to unregister a boolexpr twice")
set unitAddHandlerCount = unitAddHandlerCount - 1
if unitAddHandlerCount == 0 then
call tableArray[0].handle.remove(GetHandleId(expr))
call DestroyTrigger(unitAddHandlerTrigger)
set unitAddHandlerTrigger = null
else
set exprId = GetHandleId(expr)
call TriggerRemoveCondition(unitAddHandlerTrigger, tableArray[0].triggercondition[exprId])
call tableArray[0].handle.remove(exprId)
endif
endmethod
static method clearUnitAddHandlers takes nothing returns nothing
call tableArray[0].flush()
call DestroyTrigger(unitAddHandlerTrigger)
set unitAddHandlerTrigger = null
set unitAddHandlerCount = 0
endmethod
static method registerUnitRemoveHandler takes boolexpr expr returns nothing
debug call ThrowError(expr == null, "UnitMotion", "registerUnitRemoveHandler()", "thistype", 0, "Attemped to register a null boolexpr")
debug call ThrowError(tableArray[8191].handle.has(GetHandleId(expr)), "UnitMotion", "registerUnitRemoveHandler()", "thistype", 0, "Attemped to register a boolexpr twice")
if unitRemoveHandlerCount == 0 then
set unitRemoveHandlerTrigger = CreateTrigger()
endif
set unitRemoveHandlerCount = unitRemoveHandlerCount + 1
set tableArray[8191].triggercondition[GetHandleId(expr)] = TriggerAddCondition(unitRemoveHandlerTrigger, expr)
endmethod
static method unregisterUnitRemoveHandler takes boolexpr expr returns nothing
local integer exprId
debug call ThrowError(expr == null, "UnitMotion", "unregisterUnitRemoveHandler()", "thistype", 0, "Attemped to unregister a null boolexpr")
debug call ThrowError(not tableArray[8191].handle.has(GetHandleId(expr)), "UnitMotion", "unregisterUnitRemoveHandler()", "thistype", 0, "Attemped to unregister a boolexpr twice")
set unitRemoveHandlerCount = unitRemoveHandlerCount - 1
if unitRemoveHandlerCount == 0 then
call tableArray[8191].handle.remove(GetHandleId(expr))
call DestroyTrigger(unitRemoveHandlerTrigger)
set unitRemoveHandlerTrigger = null
else
set exprId = GetHandleId(expr)
call TriggerRemoveCondition(unitRemoveHandlerTrigger, tableArray[8191].triggercondition[exprId])
call tableArray[8191].handle.remove(exprId)
endif
endmethod
static method clearUnitRemoveHandlers takes nothing returns nothing
call tableArray[8191].flush()
call DestroyTrigger(unitRemoveHandlerTrigger)
set unitRemoveHandlerTrigger = null
set unitRemoveHandlerCount = 0
endmethod
endif
private static method vectorNotZero takes vector this returns boolean
return this.x != 0.00 or/*
*/ this.y != 0.00 or/*
*/ this.z != 0.00
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local thistype prev
local thistype next
local unit u
local real xVel
local real yVel
local real unitZ
local real defaultZ
local real friction
local vector velocity
local vector acceleration
local vector torqueAxis
local vector torqueIncrement
loop
exitwhen this == 0
set velocity = this.velocity
set acceleration = this.acceleration
if vectorNotZero(acceleration) then
call velocity.add(acceleration)
set this.deltaFriction = RMinBJ(acceleration.z*GROUND_FRICTION*this.unitFriction, 0.00)
endif
if vectorNotZero(velocity) then
set u = this.unit
set unitZ = GetUnitFlyHeight(u)
set defaultZ = GetUnitDefaultFlyHeight(u) + 0.10
if unitZ > defaultZ then
call SetUnitPropWindow(u, 0.00)
call SetUnitTurnSpeed(u, 0.00)
else
call SetUnitPropWindow(u, GetUnitDefaultPropWindow(u))
call SetUnitTurnSpeed(u, GetUnitDefaultTurnSpeed(u))
set friction = this.deltaFriction
if velocity.z < 0.00 then
set velocity.z = 0.00
endif
if friction < 0.00 then
set xVel = velocity.x
set yVel = velocity.y
if xVel != 0.00 or yVel != 0.00 then
if friction*friction > xVel*xVel + yVel*yVel then
set velocity.x = 0.00
set velocity.y = 0.00
else
call velocity.setLength(RMaxBJ(velocity.getLength() + friction, 0.00))
endif
endif
endif
endif
if velocity.x != 0.00 or velocity.y != 0.00 or velocity.z > 0.00 or (unitZ > defaultZ and velocity.z != 0.00) then
set torqueAxis = this.torqueAxis
set torqueIncrement = this.torqueIncrement
if vectorNotZero(torqueIncrement) then
call torqueAxis.add(torqueIncrement)
endif
if vectorNotZero(torqueAxis) then
call velocity.rotate(torqueAxis, torqueAxis.getLength()/velocity.getLength())
endif
call SetUnitX(u, GetUnitX(u) + velocity.x*FRAME_RATE)
call SetUnitY(u, GetUnitY(u) + velocity.y*FRAME_RATE)
call SetUnitFlyHeight(u, unitZ + velocity.z*FRAME_RATE, 0.00)
set triggerInstance = this
call TriggerEvaluate(this.motionHandlerTrigger)
set triggerInstance = 0
endif
endif
set this = this.next
endloop
set u = null
endmethod
static if thistype.AUTO_REGISTER_UNITS then
private static method onIndex takes nothing returns nothing
call thistype(GetIndexedUnitId()).constructor()
endmethod
private static method onDeindex takes nothing returns nothing
call thistype(GetIndexedUnitId()).destructor()
endmethod
endif
private static method init takes nothing returns nothing
static if thistype.AUTO_REGISTER_UNITS then
local code onIndex = function thistype.onIndex
local code onDeindex = function thistype.onDeindex
call OnUnitIndex(onIndex)
call OnUnitDeindex(onDeindex)
endif
set periodicCode = function thistype.periodic
set tableArray = TableArray[0x2001]
set velocityTable = TableArray[0x2000]
set accelerationTable = TableArray[0x2000]
set torqueTable = TableArray[0x2000]
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
call init()
endmethod
endmodule
private struct List extends array
private static key prevK
private static key nextK
private static key dataK
method operator data takes nothing returns UnitMotion
return Table(dataK)[this]
endmethod
method operator next takes nothing returns thistype
return Table(nextK)[this]
endmethod
method insert takes UnitMotion data returns thistype
local thistype node = Table.create()
local thistype last = Table(prevK)[this]
set Table(prevK)[node] = last
set Table(nextK)[node] = this
set Table(prevK)[this] = node
set Table(nextK)[last] = node
set Table(dataK)[node] = data
return node
endmethod
method remove takes nothing returns nothing
local thistype prev = Table(prevK)[this]
local thistype next = Table(nextK)[this]
set Table(prevK)[next] = prev
set Table(nextK)[prev] = next
call Table(dataK).remove(this)
call Table(this).destroy()
endmethod
method clear takes nothing returns nothing
local thistype node = this.next
loop
exitwhen node == this
call node.remove()
set node = node.next
endloop
endmethod
static method create takes nothing returns thistype
local thistype this = Table.create()
set Table(prevK)[this] = this
set Table(nextK)[this] = this
return this
endmethod
method destroy takes nothing returns nothing
call this.clear()
call Table(prevK).remove(this)
call Table(nextK).remove(this)
call Table(this).destroy()
endmethod
endstruct
private module CommonMembers
static method create takes nothing returns thistype
local thistype this = vector.create(0.00, 0.00, 0.00)
set this.references = List.create()
return this
endmethod
method destroy takes nothing returns nothing
call this.clearReferences()
call this.references.destroy()
call vector(this).destroy()
endmethod
endmodule
struct Velocity extends array
private List references
method clearReferences takes nothing returns nothing
local List node = this.references.next
loop
exitwhen node == this
call node.data.removeVelocity(this)
set node = node.next
endloop
endmethod
implement CommonMembers
method add takes vector vec returns nothing
local List node = this.references.next
if vec < 0 then
loop
exitwhen node == this.references
call s__UnitMotion_velocity[node.data].subtract(-vec)
set node = node.next
endloop
call vector(this).subtract(-vec)
else
loop
exitwhen node == this.references
call s__UnitMotion_velocity[node.data].add(vec)
set node = node.next
endloop
call vector(this).add(vec)
endif
endmethod
endstruct
struct Acceleration extends array
private List references
method clearReferences takes nothing returns nothing
local List node = this.references.next
loop
exitwhen node == this
call node.data.removeAcceleration(this)
set node = node.next
endloop
endmethod
implement CommonMembers
method add takes vector vec returns nothing
local List node = this.references.next
call vec.scale(UnitMotion.FRAME_RATE)
if vec < 0 then
loop
exitwhen node == this.references
call s__UnitMotion_acceleration[node.data].subtract(-vec)
set node.data.coefficientOfFriction = node.data.coefficientOfFriction
set node = node.next
endloop
call vector(this).subtract(-vec)
else
loop
exitwhen node == this.references
call s__UnitMotion_acceleration[node.data].add(vec)
set node.data.coefficientOfFriction = node.data.coefficientOfFriction
set node = node.next
endloop
call vector(this).add(vec)
endif
call vec.scale(UnitMotion.FPS)
endmethod
endstruct
struct Torque extends array
private List references
method clearReferences takes nothing returns nothing
local List node = this.references.next
loop
exitwhen node == this
call node.data.removeTorque(this)
set node = node.next
endloop
endmethod
implement CommonMembers
method add takes vector axis returns nothing
local List node = this.references.next
call axis.scale(UnitMotion.FRAME_RATE)
if axis < 0 then
loop
exitwhen node == this.references
call s__UnitMotion_torqueAxis[node.data].subtract(-axis)
set node = node.next
endloop
call vector(this).subtract(-axis)
else
loop
exitwhen node == this.references
call s__UnitMotion_torqueAxis[node.data].add(axis)
set node = node.next
endloop
call vector(this).add(axis)
endif
call axis.scale(UnitMotion.FPS)
endmethod
endstruct
endlibrary
library UnitMotionSentinel initializer Init /*
*/requires /*
*/UnitMotion /*
*/UnitDex /*
*/WorldBounds /*
*/
private function BoundUnit takes nothing returns nothing
local unit u = GetUnitById(UnitMotion.triggerInstance)
local real x = GetUnitX(u)
local real y = GetUnitY(u)
if x < WorldBounds.minX then
call SetUnitX(u, WorldBounds.minX)
elseif x > WorldBounds.maxX then
call SetUnitX(u, WorldBounds.maxX)
endif
if y < WorldBounds.minY then
call SetUnitY(u, WorldBounds.minY)
elseif y > WorldBounds.maxY then
call SetUnitY(u, WorldBounds.maxY)
endif
set u = null
endfunction
private function OnAdd takes nothing returns nothing
local code c = function BoundUnit
static if UnitMotion.AUTO_REGISTER_UNITS then
call UnitMotion[GetIndexedUnit()].registerMotionHandler(Filter(c))
else
call UnitMotion.addedInstance.registerMotionHandler(Filter(c))
endif
endfunction
private function Init takes nothing returns nothing
local code onAdd = function OnAdd
static if UnitMotion.AUTO_REGISTER_UNITS then
call OnUnitIndex(onAdd)
else
call UnitMotion.registerUnitAddHandler(Filter(onAdd))
endif
endfunction
endlibrary
library StunSystem uses Table
//********************************************************************************
// Stun - Version 1.2.0.0 - By iAyanami aka Ayanami
//********************************************************************************
//
// Stun:
// - An easy to use system that stuns units
// - Able to keep track of the remaining stun duration
//
// Requirements:
// - JASS NewGen
// - Table
//
// Functions:
// - Stun.apply takes unit whichUnit, real duration, boolean stack returns nothing
// * whichUnit is the target to be stunned
// * duration is the duration of the stun
// * stack is to determine if the stun should be a stacking one or not
// * true - the stun will stack, the duration of the stun will add up with the previous duration
// * false - the stun will not stack, the unit will be stunned for the longer stun duration
//
// - Stun.getDuration takes unit whichUnit returns real
// * whichUnit is the target to check
// * returns the remaining stun duration
//
// - Stun.stop takes unit whichUnit returns nothing
// * removes stun from the target
//
// How to import:
// - Copy the whole "Stun" Trigger Folder into your map
// - Save the map. Close and re-open the map.
// - You should have a new unit (Stun Dummy), ability (Stun (System)) and buff (Stun (System)) created
// - Read through the Configuration part of the code
//
// Credits:
// - Bribe for Table
//
//********************************************************************************
// CONFIGURABLES
//********************************************************************************
globals
// timer period. lower the value, the more accurate but might cause decrease in
// performance
private constant real PERIOD = 0.03125
// raw code of ability "Stun (System)"
private constant integer ABILID = 'ASTN'
// raw code of buff "Stun (System)"
private constant integer BUFFID = 'BSTN'
// raw code of unit "Stun Dummy"
private constant integer STUNID = 'sTUN'
endglobals
//********************************************************************************
// CODE
//********************************************************************************
// initialization
module Init
private static method onInit takes nothing returns nothing
set table = Table.create()
set caster = CreateUnit(Player(13), STUNID, 0, 0, 0)
call UnitAddAbility(caster, ABILID)
endmethod
endmodule
struct Stun extends array
private unit u
private real dur
private thistype next
private thistype prev
private static Table table
private static timer t = CreateTimer()
private static unit caster
private static integer count = 0
// remove the stun and deallocate
private method destroy takes nothing returns nothing
call UnitRemoveAbility(this.u, BUFFID)
if this.next != 0 then
set this.next.prev = this.prev
endif
set this.prev.next = this.next
set this.dur = 0
set this.prev = thistype(0).prev
set thistype(0).prev = this
if thistype(0).next == 0 then
call PauseTimer(t)
endif
call table.remove(GetHandleId(this.u))
endmethod
// iterating through all instances every PERIOD
private static method iterate takes nothing returns nothing
local thistype this = thistype(0)
loop
set this = this.next
exitwhen this == 0
if this.dur <= 0 or IsUnitType(this.u, UNIT_TYPE_DEAD) or GetUnitTypeId(this.u) == 0 then
call this.destroy()
else
set this.dur = this.dur - PERIOD
endif
endloop
endmethod
// immediately removes stun for the specified unit
// ex: call Stun.stop(whichTarget)
static method stop takes unit u returns nothing
local integer id = GetHandleId(u)
if table.has(id) then
call thistype(table[id]).destroy()
endif
endmethod
// gets the duration left for stun, not stunned units always return 0
// ex: local real r = Stun.getDuration(whichTarget)
static method getDuration takes unit u returns real
return thistype(table[GetHandleId(u)]).dur
endmethod
// stunning specified target and to see if the stun is a stacking one or not
// ex: call Stun.apply(whichTarget, 5.0, false)
static method apply takes unit u, real dur, boolean b returns nothing
local thistype this
local integer id = GetHandleId(u)
if table.has(id) then
set this = table[id]
else
if thistype(0).prev == 0 then
set count = count + 1
set this = count
else
set this = thistype(0).prev
set thistype(0).prev = this.prev
endif
if thistype(0).next == 0 then
call TimerStart(t, PERIOD, true, function thistype.iterate)
else
set thistype(0).next.prev = this
endif
set this.next = thistype(0).next
set thistype(0).next = this
set this.prev = thistype(0)
set table[id] = this
set this.u = u
set this.dur = 0
call IssueTargetOrder(caster, "firebolt", this.u)
endif
if b and dur > 0 then
set this.dur = this.dur + dur
elseif this.dur < dur then
set this.dur = dur
endif
endmethod
implement Init
endstruct
endlibrary
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
//
// Requires
// --------
// RegisterPlayerUnitEvent: www.hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
/*
============= Why this is important? =================
1. Does not generate 16 events per spell.
2. This uses one trigger evaluation instead of one for each
individual spell (some maps have quite a few). This helps keep
framerate high and fluid when a spell is cast.
*/
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary
library DummyRecycler /*
// DummyRecycler v1.24
// by Flux
//
// A system that recycles dummy units while considering their facing angle.
// It can be used as attachment units or as dummy caster.
//
// Why is recycling a unit important?
// Because creating a unit is is one of the slowest function in the game
// and it will always leave a permanent tiny bit of memory (0.04 KB).
// Furthermore, if a Damage Detection System (DDS) or a Unit Indexer is
// imported to your map, using this system will even result to better
// performance because DDS and Unit Indexer process new created units.
//
//
*/ requires /*
nothing
*/ optional Table/*
if not found, this system will use a hashtable. Hashtables are limited to
255 per map.
*/ optional WorldBounds /*
if not found, this system will initialize its own Map Boundaries.
//
//
// Features:
//
// -- Dummy Sharing
// When a Dummy List gets low on unit count, it will borrow Dummy Units
// from the Dummy List with the highest unit count. The transfer is not
// instant because the shared Dummy Unit has to turn to the appropriate
// angle of its new Dummy List before it can be recycled.
// See BORROW_REQUEST.
//
// -- Self-balancing recycling algorithm
// Recycled Dummy Units will be thrown to the List having the least number
// of Dummy Units.
//
// -- Recycling least used
// Allows recycling a Dummy from the Dummy List with the highest
// unit count. It is useful when the facing angle of the Dummy Unit
// does not matter.
// See GetRecycledDummyAnyAngle.
//
// -- Self-adaptation
// When there are no free Dummy Units from a Dummy List, it will end up creating
// a new unit instead but that unit will be permanently added as a Dummy
// Unit to be recycled increasing the overall total Dummy Unit count.
//
// -- Count control
// Allows limiting the overall number of Dummy Units.
// See MAX_DUMMY_COUNT.
//
// -- Delayed Recycle
// Allows recycling Dummy Units after some delay to allocate time for the
// death animation of Special Effects to be seen.
// See DummyAddRecycleTimer.
//
// ******************************************************************
// ***************************** API: *******************************
// ******************************************************************
//
// function GetRecycledDummy takes real x, real y, real z, real facing returns unit
// - Retrieve an unused Dummy Unit from the List.
// - The equivalent of CreateUnit.
// - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
// function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
// - Use this function if the facing angle of the Dummy doesn't matter to you.
// - It will return a unit from the list having the highest number of unused Dummy Units.
// - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
// function RecycleDummy takes unit u returns nothing
// - Recycle the Dummy unit for it to be used again later.
// - The equivalent of RemoveUnit.
//
// function DummyAddRecycleTimer takes unit u, real time returns nothing
// - Recycle the Dummy unit after a certain time.
// - Use this to allocate time for the the death animation of an effect attached to the
// Dummy Unit to finish..
// - The equivalent of UnitApplyTimedLife.
//
//--------------------
// CREDITS
//--------------------
// Bribe - for the MissileRecycler (vJASS) where I got this concept from
// http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
// - for the optional Table
// http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
// Vexorian - for the Attachable and Pitch Animation Model (dummy.mdx)
// http://www.wc3c.net/showthread.php?t=101150
// Maker and IcemanBo - for the unit permanent 0.04 KB memory leak of units.
// http://www.hiveworkshop.com/forums/trigger-gui-editor-tutorials-279/memory-leaks-263410/
// Nestharus - for the data structure
// http://www.hiveworkshop.com/forums/2809461-post7.html
// - for the optional WorldBounds
// http://githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j
// =============================================================== //
// ====================== CONFIGURATION ========================== //
// =============================================================== */
globals
//The rawcode of the Dummy Unit
private constant integer DUMMY_ID = 'sbh3'
//The owner of the Dummy Unit
private constant player OWNER = Player(15)
//The number of indexed angle. The higher the value the:
// - Lesser the turning time for the Dummy Units.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 10 (Max difference of 18 degrees)
private constant integer ANGLES_COUNT = 10
//The number of Dummy units per ANGLES_COUNT. The higher the value the:
// - Higher the number of units that can be recycled per angle, when
// no more units are in queue, the system will resort to use CreateUnit.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 3 to 5 (for less overhead in Map Loading Screen)
private constant integer STORED_UNIT_COUNT = 3
//The maximum number of Dummy units that can exist. When the system resort
//to using CreateUnit, the unit will be permanently added to the Dummy
//List. To avoid spamming Dummy Units and having too much free Dummy
//Units to allocate, the maximum number of Dummy Units is capped.
// Recommended Value: 80 to 120
private constant integer MAX_DUMMY_COUNT = 100
//When a certain angle have less than BORROW_REQUEST units in its list,
//it will start to borrow Dummy Units from the list with the highest
//Dummy Unit count.
// Recommended Value: Half of maximum STORED_UNIT_COUNT
private constant integer BORROW_REQUEST = 5
//It will only return a Dummy if the current dummy is close
//to it's appropriate facing angle. This is to avoid returning
//a Dummy which is still turning to face it's list angle.
private constant real ANGLE_TOLERANCE = 10.0
//An additional option to automatically hide recycled dummy units in the
//corner of the map camera bounds
private constant boolean HIDE_ON_MAP_CORNER = true
endglobals
//Every time a new dummy unit is retrieved, it will apply this resets
//If it is redundant/you dont need it, remove it.
//! textmacro DUMMY_UNIT_RESET
call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
//! endtextmacro
// =============================================================== //
// ==================== END CONFIGURATION ======================== //
// =============================================================== //
globals
private integer dummyCount = ANGLES_COUNT*STORED_UNIT_COUNT
private real array angle
private integer array count
private integer array countHead
private integer array countNext
private integer array countPrev
private integer array next
private integer array prev
private unit array dummy
private integer upper
private integer lower
private integer lastInstance
private constant real FACING_OFFSET = 180.0/ANGLES_COUNT
endglobals
static if HIDE_ON_MAP_CORNER and not LIBRARY_WorldBounds then
private module BoundsInit
readonly static real x
readonly static real y
private static method onInit takes nothing returns nothing
local rect map = GetWorldBounds()
set thistype.x = GetRectMaxX(map)
set thistype.y = GetRectMaxY(map)
call RemoveRect(map)
set map = null
endmethod
endmodule
private struct Bounds extends array
implement BoundsInit
endstruct
endif
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable hash = InitHashtable()
endif
private static method onInit takes nothing returns nothing
local real add = 360.0/ANGLES_COUNT
local real a = 0
local integer this = ANGLES_COUNT
local integer head = 0
local integer cHead = JASS_MAX_ARRAY_SIZE - 1 //avoid allocation collision
local integer i = R2I(MAX_DUMMY_COUNT/ANGLES_COUNT + 0.5)
set upper = STORED_UNIT_COUNT
set lower = STORED_UNIT_COUNT
static if LIBRARY_Table then
set tb = Table.create()
endif
//Initialize countHeads
loop
exitwhen i < 0
set countNext[cHead] = cHead
set countPrev[cHead] = cHead
set countHead[i] = cHead
set cHead = cHead - 1
set i = i - 1
endloop
set cHead = countHead[STORED_UNIT_COUNT] //All heads will be inserted here initially
//Create the Dummy units
loop
exitwhen a >= 360
//Initialize head
set next[head] = head
set prev[head] = head
set count[head] = STORED_UNIT_COUNT
set angle[head] = a
//Insert head in the Count List
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
set i = 0
loop
exitwhen i >= STORED_UNIT_COUNT
//Queued Linked List
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
static if HIDE_ON_MAP_CORNER then
static if LIBRARY_WorldBounds then
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, a)
else
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, Bounds.x, Bounds.y, a)
endif
else
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, 0, 0, a)
endif
call PauseUnit(dummy[this], true)
static if LIBRARY_Table then
set tb[GetHandleId(dummy[this])] = this
else
call SaveInteger(hash, GetHandleId(dummy[this]), 0, this)
endif
set this = this + 1
set i = i + 1
endloop
set head = head + 1
set a = a + add
endloop
set lastInstance = this
endmethod
endmodule
private struct S extends array
implement M
endstruct
private function GetHead takes integer facing returns integer
if facing < 0 or facing >= 360 then
set facing = facing - (facing/360)*360
if facing < 0 then
set facing = facing + 360
endif
endif
return R2I((facing*ANGLES_COUNT/360.0))
endfunction
function GetRecycledDummy takes real x, real y, real z, real facing returns unit
local integer head = GetHead(R2I(facing + FACING_OFFSET))
local integer this = next[head]
local integer cHead
//If there are Dummy Units in the Queue List already facing close to the appropriate angle
if this != head and RAbsBJ(GetUnitFacing(dummy[this]) - angle[head]) <= ANGLE_TOLERANCE then
//Remove from the Queue List
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
//For double free protection
set next[this] = -1
//Unit Properties
set bj_lastCreatedUnit = dummy[this]
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call SetUnitFacing(bj_lastCreatedUnit, facing)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
//! runtextmacro DUMMY_UNIT_RESET()
//Update Count and Bounds
set count[head] = count[head] - 1
//------------------------------------------------
// Unit Sharing
//------------------------------------------------
if count[head] < BORROW_REQUEST and count[countNext[countHead[upper]]] > count[head] then
set count[head] = count[head] + 1
set this = next[countNext[countHead[upper]]]
call SetUnitFacing(dummy[this], angle[head])
//Remove
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
//Add to the Current List
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
set head = countNext[countHead[upper]]
set count[head] = count[head] - 1
endif
//---------------------------
//Update Count Lists
//---------------------------
//Remove from the current Count List
set countNext[countPrev[head]] = countNext[head]
set countPrev[countNext[head]] = countPrev[head]
//Add to the new Count List
set cHead = countHead[count[head]]
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set cHead = countHead[upper]
if countNext[cHead] == cHead then
set upper = upper - 1
endif
if count[head] < lower then
set lower = count[head]
endif
else
set bj_lastCreatedUnit = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
call PauseUnit(bj_lastCreatedUnit, true)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
if dummyCount < MAX_DUMMY_COUNT then
set this = lastInstance
//For double free protection
set next[this] = -1
set dummy[this] = bj_lastCreatedUnit
static if LIBRARY_Table then
set S.tb[GetHandleId(bj_lastCreatedUnit)] = this
else
call SaveInteger(S.hash, GetHandleId(bj_lastCreatedUnit), 0, this)
endif
set lastInstance = lastInstance + 1
endif
set dummyCount = dummyCount + 1
endif
return bj_lastCreatedUnit
endfunction
function RecycleDummy takes unit u returns nothing
static if LIBRARY_Table then
local integer this = S.tb[GetHandleId(u)]
else
local integer this = LoadInteger(S.hash, GetHandleId(u), 0)
endif
local integer head
local integer cHead
//If the unit is a legit Dummy Unit
if this > 0 and next[this] == -1 then
//Find where to insert based on the list having the least number of units
set head = countNext[countHead[lower]]
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
//Update Status
call SetUnitFacing(u, angle[head])
call PauseUnit(u, true)
call SetUnitOwner(u, OWNER, false)
static if HIDE_ON_MAP_CORNER then
static if LIBRARY_WorldBounds then
call SetUnitX(u, WorldBounds.maxX)
call SetUnitY(u, WorldBounds.maxY)
else
call SetUnitX(u, Bounds.x)
call SetUnitY(u, Bounds.y)
endif
else
call SetUnitScale(u, 0, 0, 0)
call SetUnitVertexColor(u, 0, 0, 0, 0)
endif
set count[head] = count[head] + 1
//---------------------------
// Update Count Lists
//---------------------------
//Remove
set countNext[countPrev[head]] = countNext[head]
set countPrev[countNext[head]] = countPrev[head]
//Add to the new Count List
set cHead = countHead[count[head]]
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set cHead = countHead[lower]
if countNext[cHead] == cHead then
set lower = lower + 1
endif
if count[head] > upper then
set upper = count[head]
endif
elseif this == 0 then
call RemoveUnit(u)
debug elseif next[this] != -1 then
debug call BJDebugMsg("|cffffcc00[DummyRecycler]:|r Attempted to recycle a pending/free Dummy Unit.")
endif
endfunction
private function Expires takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
static if LIBRARY_Table then
call RecycleDummy(S.tb.unit[id])
call S.tb.unit.remove(id)
else
call RecycleDummy(LoadUnitHandle(S.hash, id, 0))
call FlushChildHashtable(S.hash, id)
endif
call DestroyTimer(t)
set t = null
endfunction
function DummyAddRecycleTimer takes unit u, real time returns nothing
local timer t = CreateTimer()
static if LIBRARY_Table then
set S.tb.unit[GetHandleId(t)] = u
else
call SaveUnitHandle(S.hash, GetHandleId(t), 0, u)
endif
call TimerStart(t, time, false, function Expires)
set t = null
endfunction
function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
return GetRecycledDummy(x, y, z, angle[countNext[countHead[upper]]])
endfunction
// runtextmacro DUMMY_DEBUG_TOOLS()
endlibrary
library ResourcePreloader /* v1.4c
*/uses /*
*/optional BJObjectId /* https://www.hiveworkshop.com/threads/bjobjectid.287128/
*/optional Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
*/optional UnitRecycler /* https://www.hiveworkshop.com/threads/snippet-unit-recycler.286701/
*///! novjass
|================|
| Written by AGD |
|================|
[CREDITS]
/* IcemanBo - for suggesting further improvements
Silvenon - for the sound preloading method */
|-----|
| API |
|-----|
function PreloadUnit takes integer rawcode returns nothing/*
- Assigns a certain type of unit to be preloaded
*/function PreloadItem takes integer rawcode returns nothing/*
- Assigns a certain type of item to be preloaded
*/function PreloadAbility takes integer rawcode returns nothing/*
- Assigns a certain type of ability to be preloaded
*/function PreloadEffect takes string modelPath returns nothing/*
- Assigns a certain type of effect to be preloaded
*/function PreloadSound takes string soundPath returns nothing/*
- Assigns a certain type of sound to be preloaded
The following functions requires the BJObjectId library:
*/function PreloadUnitEx takes integer start, integer end returns nothing/*
- Assigns a range of units to be preloaded
*/function PreloadItemEx takes integer start, integer end returns nothing/*
- Assigns a range of items to be preloaded
*/function PreloadAbilityEx takes integer start, integer end returns nothing/*
- Assigns a range of abilities to be preloaded
*///! endnovjass
/*========================================================================================================*/
/* Do not try to change below this line if you're not so sure on what you're doing. */
/*========================================================================================================*/
private keyword S
/*============================================== TextMacros ==============================================*/
//! textmacro PRELOAD_TYPE takes NAME, ARG, TYPE, INDEX, I
function Preload$NAME$ takes $ARG$ what returns nothing
static if LIBRARY_Table then
if S.tb[$I$].boolean[$INDEX$] then
return
endif
set S.tb[$I$].boolean[$INDEX$] = true
call Do$NAME$Preload(what)
else
if LoadBoolean(S.tb, $I$, $INDEX$) then
return
endif
call SaveBoolean(S.tb, $I$, $INDEX$, true)
call Do$NAME$Preload(what)
endif
endfunction
//! endtextmacro
//! textmacro RANGED_PRELOAD_TYPE takes NAME
function Preload$NAME$Ex takes integer start, integer end returns nothing
local boolean mode = start < end
loop
call Preload$NAME$(start)
exitwhen start == end
if mode then
set start = BJObjectId(start).plus_1()
exitwhen start > end
else
set start = BJObjectId(start).minus_1()
exitwhen start < end
endif
endloop
endfunction
//! endtextmacro
/*========================================================================================================*/
private function DoUnitPreload takes integer id returns nothing
static if LIBRARY_UnitRecycler then
call RecycleUnitEx(CreateUnit(Player(15), id, 0, 0, 270))
else
call RemoveUnit(CreateUnit(Player(15), id, 0, 0, 0))
endif
endfunction
private function DoItemPreload takes integer id returns nothing
call RemoveItem(UnitAddItemById(S.dummy, id))
endfunction
private function DoAbilityPreload takes integer id returns boolean
return UnitAddAbility(S.dummy, id) and UnitRemoveAbility(S.dummy, id)
endfunction
private function DoEffectPreload takes string path returns nothing
call DestroyEffect(AddSpecialEffectTarget(path, S.dummy, "origin"))
endfunction
private function DoSoundPreload takes string path returns nothing
local sound s = CreateSound(path, false, false, false, 10, 10, "")
call SetSoundVolume(s, 0)
call StartSound(s)
call KillSoundWhenDone(s)
set s = null
endfunction
//! runtextmacro PRELOAD_TYPE("Unit", "integer", "unit", "what", "0")
//! runtextmacro PRELOAD_TYPE("Item", "integer", "item", "what", "1")
//! runtextmacro PRELOAD_TYPE("Ability", "integer", "ability", "what", "2")
//! runtextmacro PRELOAD_TYPE("Effect", "string", "effect", "StringHash(what)", "3")
//! runtextmacro PRELOAD_TYPE("Sound", "string", "sound", "StringHash(what)", "4")
static if LIBRARY_BJObjectId then
//! runtextmacro RANGED_PRELOAD_TYPE("Unit")
//! runtextmacro RANGED_PRELOAD_TYPE("Item")
//! runtextmacro RANGED_PRELOAD_TYPE("Ability")
endif
/*========================================================================================================*/
private module Init
private static method onInit takes nothing returns nothing
local rect world = GetWorldBounds()
static if LIBRARY_Table then
set tb = TableArray[5]
endif
set dummy = CreateUnit(Player(15), 'hpea', 0, 0, 0)
call UnitAddAbility(dummy, 'AInv')
call UnitAddAbility(dummy, 'Avul')
call UnitRemoveAbility(dummy, 'Amov')
call SetUnitY(dummy, GetRectMaxY(world) + 1000)
call RemoveRect(world)
set world = null
endmethod
endmodule
private struct S extends array
static if LIBRARY_Table then
static TableArray tb
else
static hashtable tb = InitHashtable()
endif
static unit dummy
implement Init
endstruct
endlibrary
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
* v1.2.1, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex assigns every unit an unique integer. It attempts to make that number within the
* maximum array limit so you can associate it with one.
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
// external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/* ________________________________________________________________________
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
private module UnitDexConfig
// The raw code of the leave detection ability.
static constant integer DETECT_LEAVE_ABILITY = 'uDex'
// Allow debug messages (debug mode must also be on)
static constant boolean ALLOW_DEBUGGING = true
// Uncomment the lines below to define a filter for units being indexed
/*static method onFilter takes unit u returns boolean
return true
endmethod*/
endmodule
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines except for UnitDexRemove
*
* function GetUnitId takes unit whichUnit returns integer
* function GetUnitById takes integer index returns unit
*
* function UnitDexEnable takes boolean flag returns nothing
* function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
* function IsUnitIndexed takes unit u returns boolean
* function IsIndexingEnabled takes nothing returns boolean
*
* function GetIndexedUnit takes nothing returns unit
* function GetIndexedUnitId takes nothing returns integer
*
* function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
* function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
* function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
* function OnUnitIndex takes code func returns triggercondition
* function OnUnitDeidex takes code func returns triggercondition
* _________________________________________________________________________
* 4. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex.Enabled = false // toggle the indexer
* UnitDex.Initialized // returns true if the preload timer has finished
* UnitDex.Count // returns the amount of units indexed
* UnitDex.Unit[0] // access the UnitDex array directly
* UnitDex.Group // a unit group containing every indexed unit (for enumeration)
* UnitDex.LastIndex // returns the last indexed unit's id
* _________________________________________________________________________
* 5. Public Variables
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* These are to be used with the "eventtype" argument of the event API:
*
* constant integer EVENT_UNIT_INDEX = 0
* constant integer EVENT_UNIT_DEINDEX = 1
* _________________________________________________________________________
* 6. Examples
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Associate a unit with a variable
*
* set UnitFlag[GetUnitId(yourUnit)] = true
*
* 2. Allocate a struct instance using a units assigned ID
*
* local somestruct data = GetUnitId(yourUnit)
*
* 3. Detect when a unit leaves the map
*
* function Exit takes nothing returns nothing
* call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
* endfunction
*
* call OnUnitDeindex(function Exit)
* // or
* call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
* _________________________________________________________________________
* 7. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Enumerate all preplaced units
* 2. TriggerRegisterEnterRegion native to detect when a unit enters the map
* 3. Assign each unit that enters the map a unique integer
* 4. Give every unit an ability based off of defend. There is no native to accurately
* detect when a unit leaves the map but when a unit dies or is removed from the game
* they are issued the "undefend" order
* 5. Catch the "undefend" order and remove unit from the queue
* _________________________________________________________________________
* 8. Notes
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
* - The object merger line should be commented out after saving and restarting.
* - All public functions are inlined except UnitDexRemove.
*
* -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
// Event types
constant integer EVENT_UNIT_INDEX = 0
constant integer EVENT_UNIT_DEINDEX = 1
// System variables
private trigger array IndexTrig
private integer Index = 0
private real E=-1
private boolexpr FilterEnter
endglobals
function GetUnitId takes unit whichUnit returns integer
return GetUnitUserData(whichUnit)
endfunction
function GetUnitById takes integer index returns unit
return UnitDex.Unit[index]
endfunction
function GetIndexedUnit takes nothing returns unit
return UnitDex.Unit[UnitDex.LastIndex]
endfunction
function GetIndexedUnitId takes nothing returns integer
return UnitDex.LastIndex
endfunction
function IsUnitIndexed takes unit u returns boolean
return (GetUnitById(GetUnitId(u)) != null)
endfunction
function UnitDexEnable takes boolean flag returns nothing
set UnitDex.Enabled = flag
endfunction
function IsIndexingEnabled takes nothing returns boolean
return UnitDex.Enabled
endfunction
function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
return TriggerAddCondition(IndexTrig[eventtype], func)
endfunction
function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
call TriggerRemoveCondition(IndexTrig[eventtype], c)
endfunction
function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
endfunction
function OnUnitIndex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
endfunction
function OnUnitDeindex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
endfunction
function UnitDexRemove takes unit u, boolean runEvents returns boolean
return UnitDex.Remove(u, runEvents)
endfunction
/****************************************************************/
private keyword UnitDexCore
struct UnitDex extends array
static boolean Enabled = true
readonly static integer LastIndex
readonly static boolean Initialized=false
readonly static group Group=CreateGroup()
readonly static unit array Unit
readonly static integer Count = 0
readonly static integer array List
implement UnitDexConfig
private static integer Counter = 0
implement UnitDexCore
endstruct
/****************************************************************/
private module UnitDexCore
static method Remove takes unit u, boolean runEvents returns boolean
local integer i
if (IsUnitIndexed(u)) then
set i = GetUnitId(u)
set UnitDex.List[i] = Index
set Index = i
call GroupRemoveUnit(UnitDex.Group, u)
call SetUnitUserData(u, 0)
if (runEvents) then
set UnitDex.LastIndex = i
set E = EVENT_UNIT_DEINDEX
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
set E = -1
endif
set UnitDex.Unit[i] = null
set UnitDex.Count = UnitDex.Count - 1
return true
endif
return false
endmethod
private static method onGameStart takes nothing returns nothing
local integer i = 0
static if (not LIBRARY_GroupUtils) then // Check if GroupUtils exists so we can use it's enumeration group.
local group ENUM_GROUP = CreateGroup() // If not, create the group.
endif
// Index preplaced units
loop
call GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(i), FilterEnter)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
static if (not LIBRARY_GroupUtils) then
call DestroyGroup(ENUM_GROUP)
set ENUM_GROUP = null
endif
// run init triggers
set i = 1
loop
exitwhen i > Counter
set LastIndex = i
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
set E = EVENT_UNIT_INDEX
set E = -1
set i = i + 1
endloop
set LastIndex = Counter
set Initialized = true
set FilterEnter = null
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onEnter takes nothing returns boolean
local unit u = GetFilterUnit()
local integer i = GetUnitId(u)
local integer t = Index
if (i == 0 and thistype.Enabled) then
// If a filter was defined pass the unit through it.
static if (thistype.onFilter.exists) then
if (not thistype.onFilter(u)) then
set u = null
return false // check failed
endif
endif
// Handle debugging
static if (thistype.DEBUG_MODE and thistype.ALLOW_DEBUGGING) then
if (t == 0 and Counter+1 >= JASS_MAX_ARRAY_SIZE) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached!")
set u = null
return false
endif
endif
// Add to group of indexed units
call GroupAddUnit(thistype.Group, u)
// Give unit the leave detection ability
call UnitAddAbility(u, thistype.DETECT_LEAVE_ABILITY)
call UnitMakeAbilityPermanent(u, true, thistype.DETECT_LEAVE_ABILITY)
// Allocate index
if (Index != 0) then
set Index = List[t]
else
set Counter = Counter + 1
set t = Counter
endif
set List[t] = -1
set LastIndex = t
set thistype.Unit[t] = u
set Count = Count + 1
// Store the index
call SetUnitUserData(u, t)
if (thistype.Initialized) then
// Execute custom events registered with RegisterUnitIndexEvent
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_INDEX
// Reset so the event can occur again
set E = -1
endif
endif
set u = null
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit u
local integer i
// Check if order is undefend.
if (thistype.Enabled and GetIssuedOrderId() == 852056) then
set u = GetTriggerUnit()
// If unit was killed (not removed) then don't continue
if (GetUnitAbilityLevel(u, thistype.DETECT_LEAVE_ABILITY) != 0) then
set u = null
return false
endif
set i = GetUnitId(u)
// If unit has been indexed then deindex it
if (i > 0 and i <= Counter and u == GetUnitById(i)) then
// Recycle the index
set List[i] = Index
set Index = i
set LastIndex = i
// Remove to group of indexed units
call GroupRemoveUnit(thistype.Group, u)
// Execute custom events without any associated triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_DEINDEX
// Remove entry
call SetUnitUserData(u, 0)
set thistype.Unit[i] = null
// Decrement unit count
set Count = Count - 1
// Reset so the event can occur again
set E = -1
endif
set u = null
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local player p
local unit u
static if (not LIBRARY_WorldBounds) then // Check if WorldBounts exists, if not then define the necessary vars
local region reg = CreateRegion() // If WorldBounds wasn't found, create the region manually
local rect world = GetWorldBounds()
endif
set FilterEnter = Filter(function thistype.onEnter)
// Begin to index units when they enter the map
static if (LIBRARY_WorldBounds) then
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, FilterEnter)
else
call RegionAddRect(reg, world)
call TriggerRegisterEnterRegion(CreateTrigger(), reg, FilterEnter)
call RemoveRect(world)
set world = null
endif
call TriggerAddCondition(t, Filter(function thistype.onLeave))
set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
loop
set p = Player(i)
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
// Hide the detect ability from players
call SetPlayerAbilityAvailable(p, thistype.DETECT_LEAVE_ABILITY, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TimerStart(CreateTimer(), 0, false, function thistype.onGameStart)
endmethod
endmodule
endlibrary
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
One map, one hashtable. Welcome to NewTable 4.1.0.1
This newest iteration of Table introduces the new HashTable struct.
You can now instantiate HashTables which enables the use of large
parent and large child keys, just like a standard hashtable. Previously,
the user would have to instantiate a Table to do this on their own which -
while doable - is something the user should not have to do if I can add it
to this resource myself (especially if they are inexperienced).
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
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 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")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
//! 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 integerm
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) //return this.integer[key]
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key) //call this.integer.remove(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
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array
//Enables myHash[parentKey][childKey] syntax.
//Basically, it creates a Table in the place of the parent key if
//it didn't already get created earlier.
method operator [] takes integer index returns Table
local Table t = Table(this)[index]
if t == 0 then
set t = Table.create()
set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
endif
return t
endmethod
//You need to call this on each parent key that you used if you
//intend to destroy the HashTable or simply no longer need that key.
method remove takes integer index returns nothing
local Table t = Table(this)[index]
if t != 0 then
call t.destroy()
call Table(this).remove(index)
endif
endmethod
//Added in version 4.1
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
//HashTables are just fancy Table indices.
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
//Like I said above...
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
endlibrary
library ErrorMessage /* v1.0.2.0
*************************************************************************************
*
* Issue Compliant Error Messages
*
************************************************************************************
*
* function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
* - In the event of an error the game will be permanently paused
*
* function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
*
************************************************************************************/
private struct Fields extends array
static constant string COLOR_RED = "|cffff0000"
static constant string COLOR_YELLOW = "|cffffff00"
static string lastError = null
endstruct
private function Pause takes nothing returns nothing
call PauseGame(true)
endfunction
private function ThrowMessage takes string libraryName, string functionName, string objectName, integer objectInstance, string description, string errorType, string color returns nothing
local string str
local string color_braces = "|cff66FF99"
local string orange = "|cffff6600"
set str = "->\n-> " + color_braces + "{|r " + "Library" + color_braces + "(" + orange + libraryName + color_braces + ")"
if (objectName != null) then
if (objectInstance != 0) then
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + " (|rinstance = " + orange + I2S(objectInstance) + color_braces + ") )" + "|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
else
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + ")|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
else
set str = str + "|r." + "Function" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
set str = str + color_braces + " }|r " + "has thrown an exception of type " + color_braces + "(" + color + errorType + color_braces + ")|r."
set Fields.lastError = str + "\n->\n" + "-> " + color + description + "|r\n->"
endfunction
function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Error", Fields.COLOR_RED)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
call TimerStart(CreateTimer(), 0, true, function Pause)
set objectInstance = 1/0
endif
endfunction
function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Warning", Fields.COLOR_YELLOW)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
set Fields.lastError = null
endif
endfunction
endlibrary
library Vector
//*****************************************************************
//* VECTOR LIBRARY
//*
//* written by: Anitarf
//*
//* The library contains a struct named vector, which represents a
//* point in 3D space. As such, it has three real members, one for
//* each coordinate: x, y, z. It also has the following methods:
//*
//* static method create takes real x, real y, real z returns vector
//* Creates a new vector with the given coordinates.
//*
//* method getLength takes nothing returns real
//* Returns the length of the vector it is called on.
//*
//* static method sum takes vector augend, vector addend returns vector
//* Returns the sum of two vectors as a new vector.
//*
//* method add takes vector addend returns nothing
//* Similar to sum, except that it doesn't create a new vector for the result,
//* but changes the vector it is called on by adding the "added" to it.
//*
//* static method difference takes vector minuend, vector subtrahend returns vector
//* Returns the difference between two vectors as a new vector.
//*
//* method subtract takes vector subtrahend returns nothing
//* Similar to difference, except that it doesn't create a new vector for the result,
//* but changes the vector it is called on by subtracting the "subtrahend" from it.
//*
//* method scale takes real factor returns nothing
//* Scales the vector it is called on by the given factor.
//*
//* method setLength takes real length returns nothing
//* Sets the length of the vector it is called on to the given value, maintaining its orientation.
//*
//* static method dotProduct takes vector a, vector b returns real
//* Calculates the dot product (also called scalar product) of two vectors.
//*
//* static method crossProduct takes vector a, vector b returns vector
//* Calculates the cross product (also called vector product) of two vectors
//* and returns it as a new vector.
//*
//* static method tripleProductScalar takes vector a, vector b, vector c returns real
//* Calculates the triple scalar product of three vectors.
//*
//* static method tripleProductVector takes vector a, vector b, vector c returns vector
//* Calculates the triple vector product of three vectors and returns it as a new vector.
//*
//*
//* static method projectionVector takes vector projected, vector direction returns vector
//* Calculates the projection of the vector "projected" onto the vector "direction"
//* and returns it as a new vector.
//* Returns null if the vector "direction" has a length of 0.
//*
//* method projectVector takes vector direction returns nothing
//* Projects the vector it is called on onto the vector "direction".
//* Does nothing if the vector "direction" has a length of 0.
//*
//* static method projectionPlane takes vector projected, vector normal returns vector
//* Calculates the projection of the vector "projected" onto a plane defined by
//* its normal vector and returns it as a new vector.
//* Returns null if the vector "normal" has a length of 0.
//*
//* method projectPlane takes vector normal returns nothing
//* Projects the vector it is called on onto a plane defined by its normal vector.
//* Does nothing if the vector "normal" has a length of 0.
//*
//* static method getAngle takes vector a, vector b returns real
//* Returns the angle between two vectors, in radians, returns a value between 0 and pi.
//* Returns 0.0 if any of the vectors are 0 units long.
//*
//* method rotate takes vector axis, real angle returns nothing
//* Rotates the vector it is called on around the axis defined by the vector "axis"
//* by the given angle, which should be input in radians.
//* Does nothing if axis is 0 units long.
//*
//*
//* static method createTerrainPoint takes real x, real y returns vector
//* Creates a vector to the given terrain coordinate, taking its z height into account.
//*
//* method getTerrainPoint takes real x, real y returns nothing
//* Sets the vector it is called on to the given terrain coordinate, taking its z height into account.
//*
//* static method createTerrainNormal takes real x, real y, real sampleRadius returns vector
//* Creates the normal vector of the terrain at given coordinates. "sampleRadius" defines
//* how far apart the reference points will be, if they are further apart, the result will
//* be an impression of smoother terrain; normaly the value should be between 0 and 128.
//*
//* method getTerrainNormal takes real x, real y, real sampleRadius returns nothing
//* Sets the vector it is called on to the normal of the terrain at given coordinates.
//*
//*
//* method isInCylinder takes vector cylinderOrigin, vector cylinderHeight, real cylinderRadius returns boolean
//* Determines if a point is within a given cylinder. The cylinder's origin vector points
//* to the center of one of the two paralel circular sides, and the height vector points
//* from the origin point to the center of the other of the two paralel circular sides.
//* Returns false if the point is not in the cylinder or if the vector cylinderHeight is 0 units long.
//*
//* method isInCone takes vector coneOrigin, vector coneHeight, real coneRadius returns boolean
//* Determines if a point is within a given cone. The cone's origin vector points to the
//* center of the circular side, and the height vector points from the origin point to
//* the tip of the cone.
//* Returns false if the point is not in the cylinder or if the vector coneHeight is 0 units long.
//*
//* method isInSphere takes vector sphereOrigin, real sphereRadius returns boolean
//* Determines if a point is within a give sphere. The sphere's origin vector points to the
//* center of the sphere.
//* Returns false if the point is not in the sphere.
//****************************************************************
struct vector
real x
real y
real z
static method create takes real x, real y, real z returns vector
local vector v = vector.allocate()
set v.x=x
set v.y=y
set v.z=z
return v
endmethod
method getLength takes nothing returns real
return SquareRoot(.x*.x + .y*.y + .z*.z)
endmethod
static method sum takes vector augend, vector addend returns vector
local vector v = vector.allocate()
set v.x = augend.x+addend.x
set v.y = augend.y+addend.y
set v.z = augend.z+addend.z
return v
endmethod
method add takes vector addend returns nothing
set this.x=this.x+addend.x
set this.y=this.y+addend.y
set this.z=this.z+addend.z
endmethod
static method difference takes vector minuend, vector subtrahend returns vector
local vector v = vector.allocate()
set v.x = minuend.x-subtrahend.x
set v.y = minuend.y-subtrahend.y
set v.z = minuend.z-subtrahend.z
return v
endmethod
method subtract takes vector subtrahend returns nothing
set this.x=this.x-subtrahend.x
set this.y=this.y-subtrahend.y
set this.z=this.z-subtrahend.z
endmethod
method scale takes real factor returns nothing
set this.x=this.x*factor
set this.y=this.y*factor
set this.z=this.z*factor
endmethod
method setLength takes real length returns nothing
local real l = SquareRoot(.x*.x + .y*.y + .z*.z)
if l == 0.0 then
debug call BJDebugMsg("vector.setLength error: The length of the vector is 0.0!")
return
endif
set l = length/l
set this.x = this.x*l
set this.y = this.y*l
set this.z = this.z*l
endmethod
static method dotProduct takes vector a, vector b returns real
return (a.x*b.x+a.y*b.y+a.z*b.z)
endmethod
static method crossProduct takes vector a, vector b returns vector
local vector v = vector.allocate()
set v.x = a.y*b.z - a.z*b.y
set v.y = a.z*b.x - a.x*b.z
set v.z = a.x*b.y - a.y*b.x
return v
endmethod
static method tripleProductScalar takes vector a, vector b, vector c returns real
return ((a.y*b.z - a.z*b.y)*c.x+(a.z*b.x - a.x*b.z)*c.y+(a.x*b.y - a.y*b.x)*c.z)
endmethod
static method tripleProductVector takes vector a, vector b, vector c returns vector
local vector v = vector.allocate()
local real n = a.x*c.x+a.y*c.y+a.z*c.z
local real m = a.x*b.x+a.y*b.y+a.z*b.z
set v.x = b.x*n-c.x*m
set v.y = b.y*n-c.y*m
set v.z = b.z*n-c.z*m
return v
endmethod
// ================================================================
static method projectionVector takes vector projected, vector direction returns vector
local vector v = vector.allocate()
local real l = direction.x*direction.x+direction.y*direction.y+direction.z*direction.z
if l == 0.0 then
call v.destroy()
debug call BJDebugMsg("vector.projectionVector error: The length of the direction vector is 0.0!")
return 0
endif
set l = (projected.x*direction.x+projected.y*direction.y+projected.z*direction.z) / l
set v.x = direction.x*l
set v.y = direction.y*l
set v.z = direction.z*l
return v
endmethod
method projectVector takes vector direction returns nothing
local real l = direction.x*direction.x+direction.y*direction.y+direction.z*direction.z
if l == 0.0 then
debug call BJDebugMsg("vector.projectVector error: The length of the direction vector is 0.0!")
return
endif
set l = (this.x*direction.x+this.y*direction.y+this.z*direction.z) / l
set this.x = direction.x*l
set this.y = direction.y*l
set this.z = direction.z*l
endmethod
static method projectionPlane takes vector projected, vector normal returns vector
local vector v = vector.allocate()
local real l = normal.x*normal.x+normal.y*normal.y+normal.z*normal.z
if l == 0.0 then
call v.destroy()
debug call BJDebugMsg("vector.projectionPlane error: The length of the normal vector is 0.0!")
return 0
endif
set l = (projected.x*normal.x+projected.y*normal.y+projected.z*normal.z) / l
set v.x = projected.x - normal.x*l
set v.y = projected.y - normal.y*l
set v.z = projected.z - normal.z*l
return v
endmethod
method projectPlane takes vector normal returns nothing
local real l = normal.x*normal.x+normal.y*normal.y+normal.z*normal.z
if l == 0.0 then
debug call BJDebugMsg("vector.projectPlane error: The length of the normal vector is 0.0!")
return
endif
set l = (this.x*normal.x+this.y*normal.y+this.z*normal.z) / l
set this.x = this.x - normal.x*l
set this.y = this.y - normal.y*l
set this.z = this.z - normal.z*l
endmethod
static method getAngle takes vector a, vector b returns real
local real l = SquareRoot(a.x*a.x + a.y*a.y + a.z*a.z)*SquareRoot(b.x*b.x + b.y*b.y + b.z*b.z)
if l == 0 then
debug call BJDebugMsg("vector.getAngle error: The length of at least one of the vectors is 0.0!")
return 0.0
endif
return Acos((a.x*b.x+a.y*b.y+a.z*b.z)/l) //angle is returned in radians
endmethod
method rotate takes vector axis, real angle returns nothing //angle is taken in radians
local real xx
local real xy
local real xz
local real yx
local real yy
local real yz
local real zx
local real zy
local real zz
local real al = axis.x*axis.x+axis.y*axis.y+axis.z*axis.z //axis length^2
local real f
local real c = Cos(angle)
local real s = Sin(angle)
if al == 0.0 then
debug call BJDebugMsg("vector.rotate error: The length of the axis vector is 0.0!")
return
endif
set f = (this.x*axis.x+this.y*axis.y+this.z*axis.z) / al
set zx = axis.x*f
set zy = axis.y*f
set zz = axis.z*f //axis component of rotated vector
set xx = this.x-zx
set xy = this.y-zy
set xz = this.z-zz //component of vector perpendicular to axis
set al = SquareRoot(al)
set yx = (axis.y*xz - axis.z*xy)/al
set yy = (axis.z*xx - axis.x*xz)/al //y same length as x by using cross product and dividing with axis length
set yz = (axis.x*xy - axis.y*xx)/al //x,y - coordinate system in which we rotate
set this.x=xx*c+yx*s+zx
set this.y=xy*c+yy*s+zy
set this.z=xz*c+yz*s+zz
endmethod
// ================================================================
private static location loc = Location(0.0,0.0)
static method createTerrainPoint takes real x, real y returns vector
local vector v = vector.allocate()
call MoveLocation(vector.loc,x,y)
set v.x=x
set v.y=y
set v.z=GetLocationZ(loc)
return v
endmethod
method getTerrainPoint takes real x, real y returns nothing
call MoveLocation(vector.loc,x,y)
set this.x=x
set this.y=y
set this.z=GetLocationZ(loc)
endmethod
static method createTerrainNormal takes real x, real y, real sampleRadius returns vector
local vector v = vector.allocate()
local real zx
local real zy
call MoveLocation(vector.loc, x-sampleRadius, y)
set zx=GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x+sampleRadius, y)
set zx=zx-GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x, y-sampleRadius)
set zy=GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x, y+sampleRadius)
set zy=zy-GetLocationZ(vector.loc)
set sampleRadius=2*sampleRadius
set v.x = zx*sampleRadius
set v.y = zy*sampleRadius
set v.z = sampleRadius*sampleRadius
return v
endmethod
method getTerrainNormal takes real x, real y, real sampleRadius returns nothing
local real zx
local real zy
call MoveLocation(vector.loc, x-sampleRadius, y)
set zx=GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x+sampleRadius, y)
set zx=zx-GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x, y-sampleRadius)
set zy=GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x, y+sampleRadius)
set zy=zy-GetLocationZ(vector.loc)
set sampleRadius=2*sampleRadius
set this.x = zx*sampleRadius
set this.y = zy*sampleRadius
set this.z = sampleRadius*sampleRadius
endmethod
// ================================================================
method isInCylinder takes vector cylinderOrigin, vector cylinderHeight, real cylinderRadius returns boolean
local real l
local real x = this.x-cylinderOrigin.x
local real y = this.y-cylinderOrigin.y
local real z = this.z-cylinderOrigin.z
if x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z < 0.0 then //point below cylinder
return false
endif
set x = x-cylinderHeight.x
set y = y-cylinderHeight.y
set z = z-cylinderHeight.z
if x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z > 0.0 then //point above cylinder
return false
endif
set l = cylinderHeight.x*cylinderHeight.x+cylinderHeight.y*cylinderHeight.y+cylinderHeight.z*cylinderHeight.z
if l == 0.0 then
debug call BJDebugMsg("vector.isInCylinder error: The length of the cylinderHeight vector is 0.0!")
return false
endif
set l = (x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z) / l
set x = x - cylinderHeight.x*l
set y = y - cylinderHeight.y*l
set z = z - cylinderHeight.z*l
if x*x+y*y+z*z > cylinderRadius*cylinderRadius then //point outside cylinder
return false
endif
return true
endmethod
method isInCone takes vector coneOrigin, vector coneHeight, real coneRadius returns boolean
local real l
local real x = this.x-coneOrigin.x
local real y = this.y-coneOrigin.y
local real z = this.z-coneOrigin.z
if x*coneHeight.x+y*coneHeight.y+z*coneHeight.z < 0.0 then //point below cone
return false
endif
set l = coneHeight.x*coneHeight.x+coneHeight.y*coneHeight.y+coneHeight.z*coneHeight.z
if l == 0.0 then
debug call BJDebugMsg("vector.isInCone error: The length of the coneHeight vector is 0.0!")
return false
endif
set l = (x*coneHeight.x+y*coneHeight.y+z*coneHeight.z) / l
set x = x - coneHeight.x*l
set y = y - coneHeight.y*l
set z = z - coneHeight.z*l
if SquareRoot(x*x+y*y+z*z) > coneRadius*(1.0-l) then //point outside cone
return false
endif
return true
endmethod
method isInSphere takes vector sphereOrigin, real sphereRadius returns boolean
if sphereRadius*sphereRadius < ((this.x-sphereOrigin.x)*(this.x-sphereOrigin.x)+(this.y-sphereOrigin.y)*(this.y-sphereOrigin.y)+(this.z-sphereOrigin.z)*(this.z-sphereOrigin.z)) then
return false
endif
return true
endmethod
endstruct
endlibrary
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: http://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* 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 = 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
library BJObjectId
//! novjass
// creating:
// from native object-id / integer
set oid = BJObjectId('A000')
// from string
set oid = BJObjectId.from_str("I001")
// printing:
call BJDebugMsg(oid.to_str())
// looping through a set of object-id(s)
// forward
local BJObjectId oid = BJObjectId('A000')
local BJObjectId last_oid = BJObjectId('A010')
loop
exitwhen oid > last_oid
// do stuff
set oid = oid.plus_1()
endloop
// backward
local BJObjectId oid = BJObjectId('A010')
local BJObjectId last_oid = BJObjectId('A000')
loop
exitwhen oid < last_oid
// do stuff
set oid = oid.minus_1()
endloop
// mapping BJObjectId(s) to array-indices / struct instances
local integer index
local MyStruct my_struct
set index = BJObjectId('H000').to_unit_index()
set my_struct = MyStruct( BJObjectId('h001').to_unit_index() )
set index = BJObjectId('I000').to_item_index()
set my_struct = BJObjectId('I001').to_item_index() // don't really need the MyStruct cast (because vJass =))
set index = BJObjectId('B000').to_destructable_index()
set my_struct = BJObjectId('B001').to_destructable_index()
set index = BJObjectId('D000').to_doodad_index()
set my_struct = BJObjectId('D001').to_doodad_index()
set index = BJObjectId('A000').to_ability_index()
set my_struct = BJObjectId('A001').to_ability_index()
set index = BJObjectId('B000').to_buff_index()
set my_struct = BJObjectId('B001').to_buff_index()
set index = BJObjectId('R000').to_upgrade_index()
set my_struct = BJObjectId('R001').to_upgrade_index()
// for campaign objects the methods have a "c" after the "to_"
set index = BJObjectId('A000').to_cunit_index()
set index = BJObjectId('A000').to_cability_index()
...
// NOTE: the to_*_index methods break for object-id(s) > 'XXOZ', i.e
// 900 is the maximum number of objects you can have and still be able to use those methods;
// the reason is that the object-id(s) after 'XXOZ' have indices > 8190
//! endnovjass
struct BJObjectId extends array
static method from_str takes string oid returns thistype
// '0' = 48 .. '9' = 57,
// 'A' = 65 .. 'Z' = 90
// 'a' = 97 .. 'z' = 122
//
// index(<chr>):
// '0' = 0; chr(0 + 48) = '0' = 48
// 'A' = 17; chr(17 + 48) = 'A' = 65
// 'a' = 49; chr(49 + 48) = 'a' = 97
//
local string chars = "0123456789.......ABCDEFGHIJKLMNOPQRSTUVWXYZ......abcdefghijklmnopqrstuvwxyz"
local integer this = 0
local integer i
local integer j
local integer ordinal
local string chr
local integer pow_256 = 1
set i = 3
loop
exitwhen i < 0
set chr = SubString(oid, i, i + 1)
set j = 0
loop
exitwhen j >= 75
if chr == SubString(chars, j, j + 1) then
set this = this + (j + 48) * pow_256
set pow_256 = pow_256 * 256
exitwhen true
endif
set j = j + 1
endloop
set i = i - 1
endloop
return this
endmethod
method to_str takes nothing returns string
local string chars = "0123456789.......ABCDEFGHIJKLMNOPQRSTUVWXYZ......abcdefghijklmnopqrstuvwxyz"
local integer t = this
local integer i
local integer b
local string result = ""
set i = t
set t = i / 0x100
set b = i - t * 0x100 - 48
set result = SubString(chars, b, b + 1) + result
set i = t
set t = i / 0x100
set b = i - t * 0x100 - 48
set result = SubString(chars, b, b + 1) + result
set i = t
set t = i / 0x100
set b = i - t * 0x100 - 48
set result = SubString(chars, b, b + 1) + result
set t = t - 48
set result = SubString(chars, t, t + 1) + result
return result
endmethod
method plus_1 takes nothing returns thistype
local integer t = this
local integer i
local integer b1
local integer b2
local integer b3
local integer b4
set i = t
set t = i / 0x100
set b4 = i - t * 0x100
if b4 < 'Z' then
if b4 != '9' then
set i = i + 1
else
set i = i + 8
endif
else
set i = t
set t = i / 0x100
set b3 = i - t * 0x100
if b3 < 'Z' then
if b3 != '9' then
set i = i * 0x00000100 + 0x00000100 + '0'
else
set i = i * 0x00000100 + 0x00000800 + '0'
endif
else
set i = t
set t = i / 0x100
set b2 = i - t * 0x100
if b2 < 'Z' then
if b2 != '9' then
set i = i * 0x00010000 + 0x00010000 + '0' * 0x00000100 + '0'
else
set i = i * 0x00010000 + 0x00080000 + '0' * 0x00000100 + '0'
endif
else
set i = t
if i != '9' then
set i = i * 0x01000000 + 0x01000000 + '0' * 0x00010000 + '0' * 0x00000100 + '0'
else
set i = i * 0x01000000 + 0x08000000 + '0' * 0x00010000 + '0' * 0x00000100 + '0'
endif
endif
endif
endif
return i
endmethod
method minus_1 takes nothing returns thistype
local integer t = this
local integer i
local integer b1
local integer b2
local integer b3
local integer b4
set i = t
set t = i / 0x100
set b4 = i - t * 0x100
if b4 > '0' then
if b4 != 'A' then
set i = i - 1
else
set i = i - 8
endif
else
set i = t
set t = i / 0x100
set b3 = i - t * 0x100
if b3 > '0' then
if b3 != 'A' then
set i = i * 0x00000100 - 0x00000100 + 'Z'
else
set i = i * 0x00000100 - 0x00000800 + 'Z'
endif
else
set i = t
set t = i / 0x100
set b2 = i - t * 0x100
if b2 > '0' then
if b2 != 'A' then
set i = i * 0x00010000 - 0x00010000 + 'Z' * 0x00000100 + 'Z'
else
set i = i * 0x00010000 - 0x00080000 + 'Z' * 0x00000100 + 'Z'
endif
else
set i = t
if i != 'A' then
set i = i * 0x01000000 - 0x01000000 + 'Z' * 0x00010000 + 'Z' * 0x00000100 + 'Z'
else
set i = i * 0x01000000 - 0x08000000 + 'Z' * 0x00010000 + 'Z' * 0x00000100 + 'Z'
endif
endif
endif
endif
return i
endmethod
method operator< takes thistype other returns boolean
return integer(this) < integer(other)
endmethod
private static integer array first_unit_oid
private static integer array first_cunit_oid
private static method onInit takes nothing returns nothing
set first_unit_oid['H'] = 'H000'
set first_unit_oid['h'] = 'h000'
set first_unit_oid['O'] = 'O000'
set first_unit_oid['o'] = 'o000'
set first_unit_oid['E'] = 'E000'
set first_unit_oid['e'] = 'e000'
set first_unit_oid['U'] = 'U000'
set first_unit_oid['u'] = 'u000'
set first_unit_oid['N'] = 'N000'
set first_unit_oid['n'] = 'n000'
set first_cunit_oid['H'] = 'H600'
set first_cunit_oid['h'] = 'h600'
set first_cunit_oid['O'] = 'O600'
set first_cunit_oid['o'] = 'o600'
set first_cunit_oid['E'] = 'E600'
set first_cunit_oid['e'] = 'e600'
set first_cunit_oid['U'] = 'U600'
set first_cunit_oid['u'] = 'u600'
set first_cunit_oid['N'] = 'N600'
set first_cunit_oid['n'] = 'n600'
endmethod
method to_unit_index takes nothing returns integer
return this - first_unit_oid[this / 0x01000000] + 1
endmethod
method to_cunit_index takes nothing returns integer
return this - first_cunit_oid[this / 0x01000000] + 1
endmethod
method to_item_index takes nothing returns integer
return this - 'I000' + 1
endmethod
method to_citem_index takes nothing returns integer
return this - 'I600' + 1
endmethod
method to_destructable_index takes nothing returns integer
return this - 'B000' + 1
endmethod
method to_cdestructable_index takes nothing returns integer
return this - 'B600' + 1
endmethod
method to_doodad_index takes nothing returns integer
return this - 'D000' + 1
endmethod
method to_cdoodad_index takes nothing returns integer
return this - 'D600' + 1
endmethod
method to_ability_index takes nothing returns integer
return this - 'A000' + 1
endmethod
method to_cability_index takes nothing returns integer
return this - 'A600' + 1
endmethod
method to_buff_index takes nothing returns integer
return this - 'B000' + 1
endmethod
method to_cbuff_index takes nothing returns integer
return this - 'B600' + 1
endmethod
method to_upgrade_index takes nothing returns integer
return this - 'R000' + 1
endmethod
method to_cupgrade_index takes nothing returns integer
return this - 'R600' + 1
endmethod
endstruct
endlibrary
library WorldBounds /* v2.0.0.0
************************************************************************************
*
* struct WorldBounds extends array
*
* Fields
* -------------------------
*
* readonly static integer maxX
* readonly static integer maxY
* readonly static integer minX
* readonly static integer minY
*
* readonly static integer centerX
* readonly static integer centerY
*
* readonly static rect world
* readonly static region worldRegion
*
************************************************************************************/
private module WorldBoundInit
private static method onInit takes nothing returns nothing
set world = bj_mapInitialPlayableArea
set maxX = R2I(GetRectMaxX(world))
set maxY = R2I(GetRectMaxY(world))
set minX = R2I(GetRectMinX(world))
set minY = R2I(GetRectMinY(world))
set centerX = R2I((maxX + minX)/2)
set centerY = R2I((minY + maxY)/2)
set worldRegion = CreateRegion()
call RegionAddRect(worldRegion, world)
endmethod
endmodule
struct WorldBounds extends array
readonly static integer maxX
readonly static integer maxY
readonly static integer minX
readonly static integer minY
readonly static integer centerX
readonly static integer centerY
readonly static rect world
readonly static region worldRegion
implement WorldBoundInit
endstruct
endlibrary
library AutoFly initializer onInit requires UnitDex
/***************************************************************
*
* v1.0, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* AutoFly adds & removes crow form upon any unit entering the map. This
* allows modifying of the units height without having to do it manually.
*
* Credits go to azlier for the orginal idea.
*
****************************************************************/
globals
private constant integer CROW_FORM = 'Amrf'
endglobals
private function AddFly takes nothing returns boolean
return UnitAddAbility(GetIndexedUnit(), CROW_FORM) and UnitRemoveAbility(GetIndexedUnit(), CROW_FORM)
endfunction
private function onInit takes nothing returns nothing
call RegisterUnitIndexEvent(Filter(function AddFly), EVENT_UNIT_INDEX)
endfunction
endlibrary
library SpinningBlackHole /* v1.8 [3D]
*/requires /*
*/UnitMotion /*
*/UnitMotionSentinel /*
*/StunSystem /* https://www.hiveworkshop.com/threads/system-stun.196749/
*/optional SpellEffectEvent /* https://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
*/optional ResourcePreloader /* https://www.hiveworkshop.com/threads/snippet-resource-preloader.287358/
*/optional DummyRecycler /* https://www.hiveworkshop.com/threads/dummy-recycler-v1-24.277659/
*/optional UnitDex /* https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
*/optional GroupUtils /* https://www.wc3c.net/showthread.php?t=104464
*/optional Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
|---------|
| Credits |
|---------|
Author:
- AGD
Dependencies:
- iAyanami aka Ayanami (StunSystem)
- Bribe (SpellEffectEvent, Table)
- Flux (DummyRecycler)
- TriggerHappy (UnitDex)
- AGD (ResourcePreloader)
- Rising_Dusk (GroupUtils)
Others:
- Vexorian (dummy.mdx)
|-------------------------|
| Importing Instructions: |
|-------------------------|
- Copy the dummy model in the import manager
- Copy all the neccessary object data in the object editor
- Copy the "Spinning Black Hole [3D] by AGD" trigger category together with the library requirements
- Configure the object rawcodes in the configuration below
Note:
This requires jasshelper or much better if you just install JASS NewGen Pack or WEX
Do not release another version of this spell or make any form of duplication (aside for your
personal use) without the creator's permission and please give credits to the author if you
use this resource in your map.
Share your feedback and suggestions about this spell at Hive Workshop and if you have some
problems in configuring the spell, don't hesitate to ask the author. Please also report bugs and
glitches if you found some.
|----------------|
| Spell Summary: |
|----------------|
Creates a spinning black hole at a target area that gradually increases its radius over time.
Units within the radius of the event horizon will be pulled towards the singularity with a speed
that increases as they approach it and will take damage over time. Units at the singularity are
crushed together with the other masses, taking a percentage of their maximum hit points as an
additional damage over time. Before the black hole vanishes, it explodes with great force,
throwing units from within outwards with a distance that increases the closer they are to the
singularity, dealing damage and stunning units for a certain duration. */
globals
/*=================================================================*/
/* Spell Configuration */
/*=================================================================*/
/*-----------------------------------------------------------------*/
/* Static Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Basic Configuration */
/*******************************************************************/
/*
The rawcode of the spell */
private constant integer SPELL_ID = 'SBH1'
/*
The rawcode of the dummy unit */
private constant integer DUMMY_ID = 'sbh3'
/*
Determines if the target units will be paused when being
pulled (Reduces a significant amount of lag) */
private constant boolean PAUSE_TARGETS = true
/*
Determines if the targets shrink as they approach the singularity */
private constant boolean SHRINK_UNIT_SIZE = true
/*
The periodic execution interval
The default value is 0.03125 (32 FPS) */
private constant real PERIOD = 1.00/32.00
/*
The attack type of the black hole's pull */
private constant attacktype PULL_ATTACK_TYPE = ATTACK_TYPE_NORMAL
/*
The attack type of the black hole's singularity compression */
private constant attacktype COMPRESS_ATTACK_TYPE = ATTACK_TYPE_NORMAL
/*
The attack type of the black hole's explosion */
private constant attacktype EXPLOSION_ATTACK_TYPE = ATTACK_TYPE_NORMAL
/*
The damage type of the black hole's pull */
private constant damagetype PULL_DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
/*
The damage type of the black hole's singularity compression */
private constant damagetype COMPRESS_DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
/*
The damage type of the black hole's explosion */
private constant damagetype EXPLOSION_DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
/*
The weapon type of the black hole's pull */
private constant weapontype PULL_WEAPON_TYPE = null
/*
The weapon type of the black hole's singularity compression */
private constant weapontype COMPRESS_WEAPON_TYPE = null
/*
The weapon type of the black hole's explosion */
private constant weapontype EXPLOSION_WEAPON_TYPE = null
/*******************************************************************/
/* Spell Aesthetics */
/*******************************************************************/
/*
Determines if the spell uses special effects for the black hole's
target units */
private constant boolean USE_TARGET_SFX = true
/*
If true, special effect will only be attached to the unit once and
will last until the black hole explosion. Else, special effect will
constantly be attached on the unit and destroyed per period. */
private constant boolean STATIC_PULL_SFX = true
/*
The special effect model of the black hole's singularity */
private constant string SFX_MODEL_1 = "Abilities\\Spells\\Undead\\Possession\\PossessionTarget.mdl"
private constant string SFX_MODEL_2 = "Abilities\\Spells\\Other\\Drain\\ManaDrainTarget.mdl"
/*
The special effect model of the black hole */
private constant string SFX_MODEL_3 = "Abilities\\Spells\\Human\\slow\\slowtarget.mdl"
/*
The special effect model of the black hole's explosion */
private constant string SFX_MODEL_4 = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl"
/*
The special effect model attached to pulled units
(Only works if USE_TARGET_SFX is true) */
private constant string PULL_SFX_MODEL = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"
/*
Attachment point for SFX_MODEL_1 */
private constant string ATTACH_POINT_1 = "origin"
/*
Attachment point for SFX_MODEL_2 */
private constant string ATTACH_POINT_2 = "origin"
/*
Attachment point for SFX_MODEL_3 */
private constant string ATTACH_POINT_3 = "origin"
/*
Attachment point for SFX_MODEL_4 */
private constant string ATTACH_POINT_4 = "origin"
/*
Attachment point for PULL_SFX_MODEL */
private constant string PULL_SFX_ATTACH_POINT = "chest"
/*
The scaling of the special effect of the explosion in relation to
the radius */
private constant real EXPLOSION_SCALE = 0.20
endglobals
/*-----------------------------------------------------------------*/
/* Dynamic Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Main Spell Mechanics */
/*******************************************************************/
/*
Determines the duration of the black hole */
private constant function Duration takes integer level returns real
return 7.00 + 1.00*level
endfunction
/*
Determines the explosion stun duration */
private constant function StunDuration takes integer level returns real
return 2.50 + 0.50*level
endfunction
/*
Determines the duration of increase in radius (The value must
obviously be smaller that the black hole's duration)
The value of the final radius is obviously not affected by this
value. This duration doesn't start at the same time with the
appearance of the black hole. Rather it ends at the same time as
its disappearance. */
private constant function RadiusIncreaseDuration takes integer level returns real
return 3.00 + 1.00*level
endfunction
/*
Determines the damage dealt per second to the units being pulled */
private constant function PullDamagePerSecond takes integer level returns real
return 50.00 + 10.00*level
endfunction
/*
Determines the damage dealt per second by the black hole's
singularity (Max Health Percentage) */
private constant function PullDamagePerSecondPercentage takes integer level returns real
return 2.00 + 1.00*level
endfunction
/*
Determines the damage done by the black hole's explosion */
private constant function ThrowDamage takes integer level, real radius, real distanceFromCenter returns real
local real minimum = 70.00 + 30.00*level
local real maximum = 350.00 + 150.00*level
return (maximum - minimum)*(1.00 - distanceFromCenter/radius) + minimum
endfunction
/*
Determines the radius of the black hole (Synchronize the
Black Hole Stun ability's AOE in the object editor with
this value) */
private constant function Radius takes integer level returns real
return 300.00 + 100.00*level
endfunction
/*
Determines the radius of the singularity */
private constant function SingularityRadius takes integer level returns real
return 50.00 + 10.00*level
endfunction
/*
Determines the maximum value of increase in radius before the black
hole vanishes (Percentage) */
private constant function RadiusIncrease takes integer level returns real
return 40.00 + 0.00*level
endfunction
/*
Determines how small the scale of the unit is reduced upon landing
in the singularity (Value of 0 means no scale reduction)
This function may cause problems if you have units in your map will
scaling value not equal to 1. If this does cause problems, just set
the value to 0. */
private constant function UnitScaleReduction takes integer level returns real
return 70.00 + 0.00*level
endfunction
/*******************************************************************/
/* Spell Kinematics */
/*******************************************************************/
/*
The acceleration due to the black hole's gravity */
private function PullAcceleration takes integer level, real percentDistanceFromEventHorizon returns real
return (14.00 + 0.00*level) + (6.00 + 1.00*level)*percentDistanceFromEventHorizon
endfunction
/*
The pull angle deflection in relation to the target's distance from
the singularity (In other words, the angle deflection of the pull
that causes the target to "spiral-in" towards the black hole's
singularity)
You can put a POSITIVE value for a COUNTER-CLOCKWISE spin or a
NEGATIVE value for a CLOCKWISE spin (A value of ZERO will turn the
spell to a Non-Spinning Black Hole) */
private function PullAngleDeflection takes integer level, real distanceFromEventHorizon returns real
return -(0.00*level + Pow((distanceFromEventHorizon)*0.10, 4.00)/800.00)
endfunction
/*
The absolute value limit for the pull angle deflection (This is
important so that the angle deflection will not continually
increase to the point where it will already cause a "spiral-out"
motion) */
private function PullAngleDeflectionLimit takes integer level returns real
return 25.50 - 0.00*level
endfunction
/*
The distance of the explosion throw in the xy-plane */
private function ThrowDistance takes integer level, real radius, real distanceFromCenter returns real
local real minimum = (0.10 + 0.00*level)*radius
local real maximum = (1.00 + 0.00*level)*radius
return (maximum - minimum)*(1.00 - distanceFromCenter/radius) + minimum
endfunction
/*
The maximum height of the parabola of the explosion throw */
private function ThrowHeight takes integer level, real radius, real distanceFromCenter returns real
local real minimum = (0.05 + 0.00*level)*radius
local real maximum = (0.40 + 0.00*level)*radius
return (maximum - minimum)*(1.00 - distanceFromCenter/radius) + minimum
endfunction
/*******************************************************************/
/* Spell Target Filter */
/*******************************************************************/
/*
Determines the type of units allowed as the spell's target. The
default setup filters out allies and structures. */
private function PullTargetFilter takes unit target, unit caster, player owner returns boolean
return not IsUnitAlly(target, owner) and/*
*/ not IsUnitType(target, UNIT_TYPE_STRUCTURE)
endfunction
/*
Determines the type of units allowed as the spell's target. The
default setup filters out allies and structures. */
private function ExplosionTargetFilter takes unit target, unit caster, player owner returns boolean
return not IsUnitAlly(target, owner) and/*
*/ not IsUnitType(target, UNIT_TYPE_STRUCTURE)
endfunction
/*=================================================================*/
/* End of Spell Configuration */
/*=================================================================*/
/*===== Do not change anything below this line if you're not so sure on what you're doing =====*/
native UnitAlive takes unit u returns boolean
private struct SpinningBlackHole extends array
private static timer staticTimer = CreateTimer()
private static thistype this = 0
private static vector zAxis
private static vector tempVector
private static group forSwap
private static group tempGroup
private static unit tempCaster
private static player tempOwner
private static integer tempLevel
private static real tempRadius
private static real tempSmallRadius
private static real tempCenterX
private static real tempCenterY
private static real tempDistance
private static real tempDamage
private static real tempDamagePercent
private static unit picked
private static real dx
private static real dy
private static real tempX
private static real tempY
private static real distance
private static real angle
private static real zAngle
private static real deflectLimit
private static real velocity
private static real xyVelocity
private static real stunDuration
private thistype recycler
private thistype prev
private thistype next
private player owner
private unit caster
private unit dummy
private integer level
private integer stageId
private real duration
private real elapsed
private real radius
private real radiusIncrease
private real growthDuration
private real smallRadius
private real frictionalForce
private real damagePerInterval
private real percentDamagePerInterval
private real scaleReduction
private real centerX
private real centerY
private effect sfx1
private effect sfx2
private effect sfx3
private effect targetSfx
private group targetGroup
static if SHRINK_UNIT_SIZE then
private static real tempScaleReduction
endif
static if not LIBRARY_GroupUtils then
private static group ENUM_GROUP = CreateGroup()
endif
static if STATIC_PULL_SFX and USE_TARGET_SFX then
private static thistype unitId
static if LIBRARY_Table then
private static TableArray table
else
private static hashtable table = InitHashtable()
endif
endif
/*
Group refresher - removes 'shadow units' from the static target group */
static if LIBRARY_UnitDex then
private static method onDeindex takes nothing returns nothing
local unit deindexed = GetIndexedUnit()
local thistype this = thistype(0).next
loop
exitwhen this == 0
call GroupRemoveUnit(.targetGroup, deindexed)
set this = .next
endloop
set deindexed = null
endmethod
elseif not LIBRARY_GroupUtils then
private static boolean clearGroup
private static method refresh takes nothing returns nothing
if clearGroup then
set clearGroup = false
call GroupClear(forSwap)
endif
call GroupAddUnit(forSwap, GetEnumUnit())
endmethod
private static method refreshGroup takes nothing returns nothing
set clearGroup = true
call ForGroup(tempGroup, function thistype.refresh)
if clearGroup then
call GroupClear(tempGroup)
endif
endmethod
endif
/*
This removes unit from the target group once it no longer meets
the condition for being allowed as a target */
private static method removeTarget takes nothing returns nothing
static if SHRINK_UNIT_SIZE then
if tempScaleReduction > 0 then
call SetUnitScale(picked, 1.00, 0, 0)
endif
endif
static if PAUSE_TARGETS then
call PauseUnit(picked, false)
else
call SetUnitPathing(picked, true)
endif
static if USE_TARGET_SFX and STATIC_PULL_SFX then
set unitId = GetHandleId(picked)
static if LIBRARY_Table then
call DestroyEffect(table[this].effect[unitId])
call table[this].remove(unitId)
else
call DestroyEffect(LoadEffectHandle(table, 0, unitId))
call RemoveSavedHandle(table, 0, unitId)
endif
endif
endmethod
/*
Periodic actions
Executes the main spell mechanics */
private static method periodic takes nothing returns nothing
set this = thistype(0).next
loop
exitwhen this == 0
set tempCaster = this.caster
set tempOwner = this.owner
set tempGroup = this.targetGroup
set tempRadius = this.radius
set tempCenterX = this.centerX
set tempCenterY = this.centerY
set tempLevel = this.level
static if SHRINK_UNIT_SIZE then
set tempScaleReduction = this.scaleReduction
endif
/*
Stage 1:
Pull units within the radius of the black hole */
if this.stageId == 1 then
set this.elapsed = this.elapsed + PERIOD
set tempDamage = this.damagePerInterval
set tempDamagePercent = this.percentDamagePerInterval
set tempSmallRadius = this.smallRadius
if this.elapsed > this.duration - this.growthDuration then
set tempRadius = tempRadius*(1.00 + this.radiusIncrease)
set this.radius = tempRadius
call SetUnitScale(this.dummy, tempRadius*0.01, 0, 0)
endif
/* Enumerate units within the radius and add each of them to the group
if they weren't added yet */
call GroupEnumUnitsInRange(ENUM_GROUP, tempCenterX, tempCenterY, tempRadius, null)
loop
set picked = FirstOfGroup(ENUM_GROUP)
exitwhen picked == null
call GroupRemoveUnit(ENUM_GROUP, picked)
if not IsUnitInGroup(picked, tempGroup) and PullTargetFilter(picked, tempCaster, tempOwner) then
call GroupAddUnit(tempGroup, picked)
static if PAUSE_TARGETS then
call PauseUnit(picked, true)
else
call SetUnitPathing(picked, false)
endif
static if USE_TARGET_SFX and STATIC_PULL_SFX then
static if LIBRARY_Table then
set table[this].effect[GetHandleId(picked)] = AddSpecialEffectTarget(PULL_SFX_MODEL, picked, PULL_SFX_ATTACH_POINT)
else
call SaveEffectHandle(table, this, GetHandleId(picked), AddSpecialEffectTarget(PULL_SFX_MODEL, picked, PULL_SFX_ATTACH_POINT))
endif
endif
endif
endloop
/* Do manual group refresh if UnitDex is not found */
static if not LIBRARY_UnitDex then
static if LIBRARY_GroupUtils then
call GroupRefresh(tempGroup)
else
call refreshGroup()
endif
endif
/* Pull Targets */
loop
set picked = FirstOfGroup(tempGroup)
exitwhen picked == null
call GroupRemoveUnit(tempGroup, picked)
if PullTargetFilter(picked, tempCaster, tempOwner) then
set dx = tempCenterX - GetUnitX(picked)
set dy = tempCenterY - GetUnitY(picked)
set distance = SquareRoot(dx*dx + dy*dy)
if distance <= tempRadius then
call GroupAddUnit(ENUM_GROUP, picked)
set tempDistance = tempRadius - distance
set angle = bj_DEGTORAD*PullAngleDeflection(tempLevel, tempDistance)
set deflectLimit = RAbsBJ(PullAngleDeflectionLimit(tempLevel)*bj_DEGTORAD)
if deflectLimit > 0.00 and RAbsBJ(angle) > deflectLimit then
if angle > 0.00 then
set angle = Atan2(dy, dx) - deflectLimit
else
set angle = Atan2(dy, dx) + deflectLimit
endif
else
set angle = Atan2(dy, dx) - angle
endif
set xyVelocity = PullAcceleration(tempLevel, tempDistance/tempRadius)
set tempVector = vector.create(xyVelocity*Cos(angle), xyVelocity*Sin(angle), 0.00)
call UnitMotion[picked].addVelocityByVector(tempVector)
call tempVector.destroy()
static if PAUSE_TARGETS then
call PauseUnit(picked, true)
else
call SetUnitPathing(picked, false)
endif
static if SHRINK_UNIT_SIZE then
if tempScaleReduction > 0 then
call SetUnitScale(picked, 1.00 - (tempScaleReduction*tempDistance/tempRadius*0.01), 0, 0)
endif
endif
static if USE_TARGET_SFX and not STATIC_PULL_SFX then
call DestroyEffect(AddSpecialEffectTarget(PULL_SFX_MODEL, picked, PULL_SFX_ATTACH_POINT))
endif
/* Deal damage to pulled units */
call UnitDamageTarget(tempCaster, picked, tempDamage, true, false, PULL_ATTACK_TYPE, PULL_DAMAGE_TYPE, PULL_WEAPON_TYPE)
/* Deal additional damage to units compressed in the singularity */
if distance <= tempSmallRadius then
call UnitDamageTarget(tempCaster, picked, GetUnitState(picked, UNIT_STATE_MAX_LIFE)*tempDamagePercent, true, false, COMPRESS_ATTACK_TYPE, COMPRESS_DAMAGE_TYPE, COMPRESS_WEAPON_TYPE)
endif
else
call removeTarget()
endif
else
call removeTarget()
endif
endloop
set forSwap = tempGroup
set this.targetGroup = ENUM_GROUP
set ENUM_GROUP = forSwap
if this.elapsed > this.duration then
set this.stageId = 2
endif
/*
Stage 2:
Explode and destroy the black hole instance */
elseif this.stageId == 2 then
set stunDuration = StunDuration(tempLevel)
/* Do manual group refresh if UnitDex is not found */
static if not LIBRARY_UnitDex then
static if LIBRARY_GroupUtils then
call GroupRefresh(tempGroup)
else
call refreshGroup()
endif
endif
/* Loop through all targets and stun them */
loop
set picked = FirstOfGroup(tempGroup)
exitwhen picked == null
call GroupRemoveUnit(tempGroup, picked)
call removeTarget()
if ExplosionTargetFilter(picked, tempCaster, tempOwner) then
set dx = GetUnitX(picked) - tempCenterX
set dy = GetUnitY(picked) - tempCenterY
set tempDistance = SquareRoot(dx*dx + dy*dy)
set angle = Atan2(dy, dx)
set distance = ThrowDistance(tempLevel, tempRadius, tempDistance)
if ThrowHeight(tempLevel, tempRadius, tempDistance) > 0.00 then
set zAngle = Atan2(4.00*ThrowHeight(tempLevel, tempRadius, tempDistance), distance)
set velocity = SquareRoot(-UnitMotion.GRAVITY*distance/Sin(2.00*zAngle))
set xyVelocity = velocity*Cos(zAngle)
set tempVector = vector.create(xyVelocity*Cos(angle), xyVelocity*Sin(angle), velocity*Sin(zAngle))
else
set xyVelocity = SquareRoot(2.00*UnitMotion[picked].friction*distance)
set tempVector = vector.create(xyVelocity*Cos(angle), xyVelocity*Sin(angle), 0.00)
endif
call UnitMotion[picked].addVelocityByVector(tempVector)
call tempVector.destroy()
call UnitDamageTarget(tempCaster, picked, ThrowDamage(tempLevel, tempRadius, tempDistance), true, false, EXPLOSION_ATTACK_TYPE, EXPLOSION_DAMAGE_TYPE, EXPLOSION_WEAPON_TYPE)
if stunDuration > 0 then
call Stun.apply(picked, stunDuration, false)
endif
endif
endloop
call DestroyGroup(tempGroup)
/* Remove the black hole's special effects */
call DestroyEffect(this.sfx1)
call DestroyEffect(this.sfx2)
call DestroyEffect(this.sfx3)
/* Explode black hole, remove special effects, and stun units */
static if LIBRARY_DummyRecycler then
call RecycleDummy(this.dummy)
set this.dummy = GetRecycledDummyAnyAngle(tempCenterX, tempCenterY, 0.00)
call DummyAddRecycleTimer(this.dummy, 2.00)
else
call RemoveUnit(this.dummy)
set this.dummy = CreateUnit(Player(14), DUMMY_ID, tempCenterX, tempCenterY, 0.00)
call UnitApplyTimedLife(this.dummy, 'BTLF', 2.00)
endif
call SetUnitScale(this.dummy, tempRadius*EXPLOSION_SCALE*0.01, 0.00, 0.00)
call DestroyEffect(AddSpecialEffectTarget(SFX_MODEL_4, this.dummy, ATTACH_POINT_4))
/* Recycling spell instance */
set this.elapsed = 0.00
set this.stageId = 0
set this.targetGroup = null
set this.caster = null
set this.owner = null
set this.dummy = null
set this.sfx1 = null
set this.sfx2 = null
set this.sfx3 = null
set this.next.prev = this.prev
set this.prev.next = this.next
set this.recycler = thistype(0).recycler
set thistype(0).recycler = this
/* If this is the only black hole instance left, pause the timer to stop
running the periodic actions */
if thistype(0).next == 0 then
call PauseTimer(GetExpiredTimer())
endif
endif
set this = this.next
endloop
endmethod
/*
This method sets up the necessary spell data and cache those
values that remain constant throughout the instance's execution
duration so that it won't need to be refered repeatedly in the
periodic method */
private static method setup takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(thistype(0).recycler == 0, "SpinningBlackHole", "allocate()", "thistype", 0, "Overflow")
endif
/* Allocate a new instance and add it to the linked-list */
set this = thistype(0).recycler
set thistype(0).recycler = this.recycler
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
/* Initialize the necessary data */
set this.caster = GetTriggerUnit()
set this.owner = GetTriggerPlayer()
set this.centerX = GetSpellTargetX()
set this.centerY = GetSpellTargetY()
static if LIBRARY_DummyRecycler then
set this.dummy = GetRecycledDummy(this.centerX, this.centerY, 0, 0)
else
set this.dummy = CreateUnit(Player(14), DUMMY_ID, this.centerX, this.centerY, 0)
endif
set tempLevel = GetUnitAbilityLevel(this.caster, SPELL_ID)
set this.level = tempLevel
set this.duration = Duration(tempLevel)
set this.radius = Radius(tempLevel)
set this.growthDuration = RadiusIncreaseDuration(tempLevel)
set this.radiusIncrease = RadiusIncrease(tempLevel)/this.growthDuration*0.01*PERIOD
set this.smallRadius = SingularityRadius(tempLevel)
set this.damagePerInterval = PullDamagePerSecond(tempLevel)*PERIOD
set this.percentDamagePerInterval = PullDamagePerSecondPercentage(tempLevel)*0.01*PERIOD
set this.sfx1 = AddSpecialEffectTarget(SFX_MODEL_1, this.dummy, ATTACH_POINT_1)
set this.sfx2 = AddSpecialEffectTarget(SFX_MODEL_2, this.dummy, ATTACH_POINT_2)
set this.sfx3 = AddSpecialEffectTarget(SFX_MODEL_3, this.dummy, ATTACH_POINT_3)
set this.targetGroup = CreateGroup()
set this.stageId = 1
static if SHRINK_UNIT_SIZE then
set this.scaleReduction = UnitScaleReduction(tempLevel)
endif
/* Scaling the black hole's model */
call SetUnitScale(this.dummy, this.radius*0.01, 0, 0)
/* If this is the only instance, start the timer to run the periodic actions */
if this.prev == 0 then
call TimerStart(staticTimer, PERIOD, true, function thistype.periodic)
endif
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method onCast takes nothing returns nothing
if GetSpellAbilityId() == SPELL_ID then
call setup()
endif
endmethod
endif
/*
Spell data initialization */
private static method onInit takes nothing returns nothing
static if LIBRARY_UnitDex then
local code onDeindex = function thistype.onDeindex
endif
static if not LIBRARY_ResourcePreloader then
local unit u = CreateUnit(Player(15), DUMMY_ID, 0, 0, 0)
endif
/* Setup spell event */
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.setup)
elseif LIBRARY_RegisterPlayerUnitEvent then
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
else
local code onCast = function thistype.onCast
local trigger t = CreateTrigger()
local integer i = 16
loop
set i = i - 1
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
exitwhen i == 0
endloop
call TriggerAddCondition(t, Filter(onCast))
set t = null
endif
static if LIBRARY_UnitDex then
call OnUnitDeindex(onDeindex)
endif
/* Preload spell resources */
static if LIBRARY_ResourcePreloader then
call PreloadEffect(SFX_MODEL_1)
call PreloadEffect(SFX_MODEL_2)
call PreloadEffect(SFX_MODEL_3)
call PreloadEffect(SFX_MODEL_4)
call PreloadEffect(PULL_SFX_MODEL)
call PreloadAbility(SPELL_ID)
call PreloadUnit(DUMMY_ID)
else
call DestroyEffect(AddSpecialEffectTarget(SFX_MODEL_1, u, "origin"))
call DestroyEffect(AddSpecialEffectTarget(SFX_MODEL_2, u, "origin"))
call DestroyEffect(AddSpecialEffectTarget(SFX_MODEL_3, u, "origin"))
call DestroyEffect(AddSpecialEffectTarget(SFX_MODEL_4, u, "origin"))
call DestroyEffect(AddSpecialEffectTarget(PULL_SFX_MODEL, u, "origin"))
call UnitAddAbility(u, SPELL_ID)
call RemoveUnit(u)
set u = null
endif
static if USE_TARGET_SFX and STATIC_PULL_SFX and LIBRARY_Table then
set table = TableArray[0x2000]
endif
set zAxis = vector.create(0.00, 0.00, 1.00)
loop
exitwhen this == 8190
set this.recycler = this + 1
set this = this + 1
endloop
set this.recycler = 0
endmethod
endstruct
endlibrary