Name | Type | is_array | initial_value |
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library_once Boomerang initializer Init requires GroupUtils, xefx, DestructableLib, IsTerrainWalkable
private keyword Boomerang // DO NOT TOUCH; configuration is below
// Credits:
// - Ciebron for the inspiration
// - -JonNny for reporting some bugs
// - Atideva for reporting a bug
// - Rising_Dusk for his GroupUtils library
// - Anitarf for his IsTerrainWalkable library
// - Vexorian for JassHelper and xe
// - PipeDream for Grimoire
// - PitzerMike for JassNewGenPack and DestructableLib
// - MindWorX for JassNewGenPack
// - SFilip for TESH
globals
private constant real TICK = 1./40 // granulation of boomerang movement
private constant integer AID = 'A000' // the ability triggering this spell
private real array DAMAGE // damage dealt by the boomerang to units it hits
private real array DAMAGE_ABSORPTION // the damage dealt is reduced by this much everytime the boomerang hits a unit or a tree
private constant boolean DAMAGE_ABSORPTION_RELATIVE = true // is DAMAGE_ABSORPTION to be treated as an absolute value or a value relative to the current damage
private constant real DAMAGE_BOUNDARY = 10. // once the damage is lower or equal to this, the boomerang stops flying
private constant string BOOMERANG_MODEL = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl" // this is the model representing the boomerang
private constant real BOOMERANG_COLLSIZE = 96. // the AoE in which units are damaged by the boomerang
private constant real BOOMERANG_SIZE = 1.25 // the scaling of the boomerang
private constant real BOOMERANG_HEIGHT = 64. // the Z height of the boomerang
private constant real array BOOMERANG_SPEED // the speed at which the boomerang moves // due to limitations this is only an approximation
private constant real BOOMERANG_FOCUS = 2. // the higher this number the more focused is the path of the boomerang. Any value greater than 0 should work. Values below 2 might not look so good.
private constant boolean ALLOW_MULTIPLE_HITS = true // if this is true, the boomerang can hit units more than once on his path
private constant boolean USE_RIGHT_BOOMERANG = true // just avoid setting both to false, okay?
private constant boolean USE_LEFT_BOOMERANG = true
private constant boolean COLLIDE_WITH_GROUND = true // do boomerangs collide with unwalkable terrain?
private real array MIN_RANGE // minimum throwing distance for the boomerang
private constant boolean IGNORE_TREES = false // if true, the boomerang will just fly through the trees without doing anything
private constant boolean KILL_TREES = true // if true, trees are killed once the boomerang hits one, if this is false, the boomerang stops flying
private constant string HIT_FX = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl" // when the boomerang hits a unit, this effect is spawned on the unit hit
private constant string HIT_FX_ATTPT = "chest" // the beforementioned effect will be attached to this point
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC // the attack type of the damage the boomerang deals
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC // the damage type of the damage the boomerang deals
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_METAL_MEDIUM_SLICE // sound when boomerang hits a unit
endglobals
private function Damage takes integer level returns real // PROXY
return DAMAGE[level]
endfunction
private function Damage_Absorption takes integer level returns real // PROXY
return DAMAGE_ABSORPTION[level]
endfunction
private function Boomerang_Speed takes integer level returns real // PROXY
return BOOMERANG_SPEED[level]
endfunction
private function Min_Range takes integer level returns real // PROXY
return MIN_RANGE[level]
endfunction
private function SetUpSpellData takes nothing returns nothing
set DAMAGE[1] = 200. // initially deals 200 damage
set DAMAGE[2] = 275.
set DAMAGE[3] = 350.
set DAMAGE_ABSORPTION[1] = 0.16 // lowers damage dealt by 16% of current damage.
set DAMAGE_ABSORPTION[2] = 0.12
set DAMAGE_ABSORPTION[3] = 0.08
set BOOMERANG_SPEED[1] = 600.
set BOOMERANG_SPEED[2] = 600.
set BOOMERANG_SPEED[3] = 600.
set MIN_RANGE[1] = 200.
set MIN_RANGE[2] = 200.
set MIN_RANGE[3] = 200.
endfunction
private function ValidTarget takes unit target, Boomerang this returns boolean
return IsUnitType(target, UNIT_TYPE_DEAD) == false /*
*/ and IsUnitType(target, UNIT_TYPE_STRUCTURE) == false /*
*/ and IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false /*
*/ and not IsUnitInGroup(target, this.damagedUnits) /*
*/ and IsUnitEnemy(target, GetOwningPlayer(this.caster))
endfunction
// This is shit. Don't touch shit.
globals
private rect R
private Boomerang tmpBoomerang
private constant boolean ANGLE_DIRECTION_FORWARD = true
private constant boolean ANGLE_DIRECTION_REVERSE = false
endglobals
private struct Boomerang
unit caster
unit target
integer level
real angleCurrent
boolean angleDirection
real targetX
real targetY
xefx dummy
boolean active
real damage
group damagedUnits
boolean overTree
boolean markedForDestruction
static boolexpr DamageFilter
static boolexpr TreeFilter
static boolexpr LightTreeFilter
private integer index
private static thistype array Structs
private static timer T = CreateTimer()
private static integer Count = 0
private static method onInit takes nothing returns nothing
set DamageFilter = Filter(function thistype.DamageFilterFunc)
set TreeFilter = Filter(function thistype.TreeFilterFunc)
set LightTreeFilter = Filter(function thistype.LightTreeFilterFunc)
endmethod
method reduceDamage takes nothing returns nothing
static if DAMAGE_ABSORPTION_RELATIVE then
set damage = damage * (1 - Damage_Absorption(level))
else
set damage = damage - Damage_Absorption(level)
endif
if damage <= DAMAGE_BOUNDARY then
set markedForDestruction = true
endif
endmethod
method onDestroy takes nothing returns nothing
set caster = null
set target = null
call dummy.destroy()
call ReleaseGroup(damagedUnits)
// clean your struct here
set Count = Count - 1
set Structs[index] = Structs[Count]
set Structs[index].index = .index
if Count == 0 then
call PauseTimer(T)
endif
endmethod
static method UnitDistCheck takes nothing returns nothing
local unit u = GetEnumUnit()
local real dx = tmpBoomerang.dummy.x - GetUnitX(u)
local real dy = tmpBoomerang.dummy.y - GetUnitY(u)
if (dx*dx + dy*dy) > (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then
call GroupRemoveUnit(tmpBoomerang.damagedUnits, u)
endif
set u = null
endmethod
static method DamageFilterFunc takes nothing returns boolean
local unit u = GetFilterUnit()
if tmpBoomerang.markedForDestruction then
return false
endif
// check if unit is a valid target for damage
if ValidTarget(u, tmpBoomerang) then
// damage the unit; if the unit for some reason cant be damaged, dont continue
if UnitDamageTarget(tmpBoomerang.caster, u, tmpBoomerang.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE) then
call DestroyEffect(AddSpecialEffectTarget(HIT_FX, u, HIT_FX_ATTPT))
call GroupAddUnit(tmpBoomerang.damagedUnits, u)
call tmpBoomerang.reduceDamage()
endif
endif
set u = null
return false
endmethod
static method TreeFilterFunc takes nothing returns boolean
local destructable d = GetFilterDestructable()
local real x
local real y
local real dx
local real dy
if tmpBoomerang.markedForDestruction then
// short circuit logic after the damage boundary has been reached
return false
endif
// filter out dead and non-tree destructables
if (not IsDestructableDead(d)) and IsDestructableTree(d) then
set tmpBoomerang.overTree = true
set x = GetWidgetX(d)
set y = GetWidgetY(d)
set dx = tmpBoomerang.dummy.x - x
set dy = tmpBoomerang.dummy.y - y
// tree must be inside the collision radius
if (dx*dx + dy*dy) <= (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then
static if KILL_TREES then
call KillDestructable(d)
call tmpBoomerang.reduceDamage()
else
// if Trees may not be destroyed, destroy the boomerang instead
set tmpBoomerang.markedForDestruction = true
endif
endif
endif
set d = null
return false
endmethod
static method LightTreeFilterFunc takes nothing returns boolean
local destructable d = GetFilterDestructable()
if not IsDestructableDead(d) and IsDestructableTree(d) then
set tmpBoomerang.overTree = true
endif
set d = null
return false
endmethod
private static method Callback takes nothing returns nothing
local integer i = Count - 1
local thistype this
local real x
local real y
local real launchX
local real launchY
local real deltaX
local real deltaY
local real distance
local real angleBase
local real angleIncrement
local real offset
loop
exitwhen i < 0
set this = Structs[i]
set tmpBoomerang = this
//
// make the boomerang home, even if the caster moves
if this.target != null then
set this.targetX = GetUnitX(this.target)
set this.targetY = GetUnitY(this.target)
endif
set launchX = GetUnitX(this.caster)
set launchY = GetUnitY(this.caster)
set deltaX = this.targetX - launchX
set deltaY = this.targetY - launchY
set distance = SquareRoot(deltaX*deltaX + deltaY*deltaY)
set angleBase = Atan2(deltaY, deltaX) - ((bj_PI / BOOMERANG_FOCUS) / 2)
// functions for moving the boomerang:
// r(a)=distance*Sin(BOOMERANG_FOCUS*a) // a is the angle and goes from 90 to 0 // distance from center point
// x(a)=Cos(a)*r(a) // x and y coordinates in relation to the location it was cast.
// y(a)=Sin(a)*r(a) // note that i inlined some things to allow casting the boomerang in all directions from any point on the map
set offset = distance * Sin(BOOMERANG_FOCUS * this.angleCurrent)
set x = launchX + (Cos(angleBase + this.angleCurrent) * offset)
set y = launchY + (Sin(angleBase + this.angleCurrent) * offset)
set this.dummy.x = x
set this.dummy.y = y
static if ALLOW_MULTIPLE_HITS then
call ForGroup(this.damagedUnits, function thistype.UnitDistCheck)
endif
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, BOOMERANG_COLLSIZE, DamageFilter)
static if not IGNORE_TREES then
set this.overTree = false
call MoveRectTo(R, x, y)
call EnumDestructablesInRect(R, TreeFilter, null)
endif
static if COLLIDE_WITH_GROUND then
static if IGNORE_TREES then
set this.overTree = false
call MoveRectTo(R, x, y)
call EnumDestructablesInRect(R, LightTreeFilter, null)
endif
if not IsTerrainWalkable(x, y) and not this.overTree then
set this.markedForDestruction = true
endif
endif
set angleIncrement = TICK * ((bj_PI / BOOMERANG_FOCUS) / 2) * (Boomerang_Speed(this.level) / distance)
if this.angleDirection == ANGLE_DIRECTION_FORWARD then
set this.angleCurrent = this.angleCurrent + angleIncrement
else
set this.angleCurrent = this.angleCurrent - angleIncrement
endif
if this.angleCurrent <= 0 or this.angleCurrent >= (bj_PI / BOOMERANG_FOCUS) or IsUnitType(this.caster, UNIT_TYPE_DEAD) == true then
set this.markedForDestruction = true
endif
if this.markedForDestruction then
call this.destroy()
endif
set i = i - 1
endloop
endmethod
static method create takes unit caster, unit target, real targetX, real targetY, boolean direction returns thistype
local thistype this = allocate()
local real dx
local real dy
local real distance
local real angleBase
set this.caster = caster
if target == null or target == caster then
set this.target = null
set this.targetX = targetX
set this.targetY = targetY
else
set this.target = target
set this.targetX = GetUnitX(target)
set this.targetY = GetUnitY(target)
endif
set dx = this.targetX - GetUnitX(caster)
set dy = this.targetY - GetUnitY(caster)
set this.level = GetUnitAbilityLevel(this.caster, AID)
set distance = SquareRoot(dx*dx + dy*dy)
if distance == 0.0 then
set angleBase = (GetUnitFacing(this.caster) * bj_DEGTORAD)
else
set angleBase = Atan2(dy, dx)
endif
if distance < Min_Range(this.level) then
// enforce the minimum distance
set distance = Min_Range(this.level)
set this.targetX = GetUnitX(caster) + (distance * Cos(angleBase))
set this.targetY = GetUnitY(caster) + (distance * Sin(angleBase))
endif
if direction == ANGLE_DIRECTION_FORWARD then
set this.angleCurrent = 0.
else
set this.angleCurrent = (bj_PI / BOOMERANG_FOCUS)
endif
set this.angleDirection = direction
set this.damage = Damage(this.level)
set this.dummy = xefx.create(GetUnitX(caster), GetUnitY(caster), 0)
set this.dummy.fxpath = BOOMERANG_MODEL
set this.dummy.scale = BOOMERANG_SIZE
set this.dummy.z = BOOMERANG_HEIGHT
set this.damagedUnits = NewGroup()
set this.overTree = false
set this.markedForDestruction = false
// initialize the struct here
set Structs[Count] = this
set this.index = Count
if Count == 0 then
call TimerStart(T, TICK, true, function thistype.Callback)
endif
set Count = Count + 1
return this
endmethod
endstruct
private function SpellCond takes nothing returns boolean
return GetSpellAbilityId() == AID
endfunction
private function SpellAction takes nothing returns nothing
local unit caster = GetTriggerUnit()
local unit target = GetSpellTargetUnit()
local real targetX = GetSpellTargetX()
local real targetY = GetSpellTargetY()
static if USE_RIGHT_BOOMERANG then
call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_FORWARD)
endif
static if USE_LEFT_BOOMERANG then
call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_REVERSE)
endif
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function SpellCond))
call TriggerAddAction(t, function SpellAction)
call SetUpSpellData()
set R = Rect(0, 0, 2 * BOOMERANG_COLLSIZE, 2 * BOOMERANG_COLLSIZE)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library_once Boomerang initializer Init requires GroupUtils, xefx, DestructableLib, IsTerrainWalkable
private keyword Boomerang // DO NOT TOUCH; configuration is below
// Credits:
// - Ciebron for the inspiration
// - -JonNny for reporting some bugs
// - Atideva for reporting a bug
// - Rising_Dusk for his GroupUtils library
// - Anitarf for his IsTerrainWalkable library
// - Vexorian for JassHelper and xe
// - PipeDream for Grimoire
// - PitzerMike for JassNewGenPack and DestructableLib
// - MindWorX for JassNewGenPack
// - SFilip for TESH
globals
private constant real TICK = 1./40 // granulation of boomerang movement
private constant integer AID = 'A000' // the ability triggering this spell
private constant boolean DAMAGE_ABSORPTION_RELATIVE = true // is DAMAGE_ABSORPTION to be treated as an absolute value or a value relative to the current damage
private constant real DAMAGE_BOUNDARY = 10. // once the damage is lower or equal to this, the boomerang stops flying
private constant string BOOMERANG_MODEL = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl" // this is the model representing the boomerang
private constant real BOOMERANG_COLLSIZE = 96. // the AoE in which units are damaged by the boomerang
private constant real BOOMERANG_SIZE = 1.25 // the scaling of the boomerang
private constant real BOOMERANG_HEIGHT = 64. // the Z height of the boomerang
private constant real BOOMERANG_FOCUS = 2. // the higher this number the more focused is the path of the boomerang. Any value greater than 0 should work. Values below 2 might not look so good.
private constant boolean ALLOW_MULTIPLE_HITS = true // if this is true, the boomerang can hit units more than once on his path
private constant boolean USE_RIGHT_BOOMERANG = true // just avoid setting both to false, okay?
private constant boolean USE_LEFT_BOOMERANG = true
private constant boolean COLLIDE_WITH_GROUND = true // do boomerangs collide with unwalkable terrain?
private constant boolean IGNORE_TREES = false // if true, the boomerang will just fly through the trees without doing anything
private constant boolean KILL_TREES = true // if true, trees are killed once the boomerang hits one, if this is false, the boomerang stops flying
private constant string HIT_FX = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl" // when the boomerang hits a unit, this effect is spawned on the unit hit
private constant string HIT_FX_ATTPT = "chest" // the beforementioned effect will be attached to this point
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC // the attack type of the damage the boomerang deals
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC // the damage type of the damage the boomerang deals
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_METAL_MEDIUM_SLICE // sound when boomerang hits a unit
endglobals
private function Damage takes integer level returns real
return 125. + level * 75
endfunction
private function Damage_Absorption takes integer level returns real
return 0.2 - level * 0.04
endfunction
private function Boomerang_Speed takes integer level returns real
return 600.
endfunction
private function Min_Range takes integer level returns real
return 200.
endfunction
private function ValidTarget takes unit target, Boomerang this returns boolean
return IsUnitType(target, UNIT_TYPE_DEAD) == false /*
*/ and IsUnitType(target, UNIT_TYPE_STRUCTURE) == false /*
*/ and IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false /*
*/ and not IsUnitInGroup(target, this.damagedUnits) /*
*/ and IsUnitEnemy(target, GetOwningPlayer(this.caster))
endfunction
// This is shit. Don't touch shit.
globals
private rect R
private Boomerang tmpBoomerang
private constant boolean ANGLE_DIRECTION_FORWARD = true
private constant boolean ANGLE_DIRECTION_REVERSE = false
endglobals
private struct Boomerang
unit caster
unit target
integer level
real angleCurrent
boolean angleDirection
real targetX
real targetY
xefx dummy
boolean active
real damage
group damagedUnits
boolean overTree
boolean markedForDestruction
static boolexpr DamageFilter
static boolexpr TreeFilter
static boolexpr LightTreeFilter
private integer index
private static thistype array Structs
private static timer T = CreateTimer()
private static integer Count = 0
private static method onInit takes nothing returns nothing
set DamageFilter = Filter(function thistype.DamageFilterFunc)
set TreeFilter = Filter(function thistype.TreeFilterFunc)
set LightTreeFilter = Filter(function thistype.LightTreeFilterFunc)
endmethod
method reduceDamage takes nothing returns nothing
static if DAMAGE_ABSORPTION_RELATIVE then
set damage = damage * (1 - Damage_Absorption(level))
else
set damage = damage - Damage_Absorption(level)
endif
if damage <= DAMAGE_BOUNDARY then
set markedForDestruction = true
endif
endmethod
method onDestroy takes nothing returns nothing
set caster = null
set target = null
call dummy.destroy()
call ReleaseGroup(damagedUnits)
// clean your struct here
set Count = Count - 1
set Structs[index] = Structs[Count]
set Structs[index].index = .index
if Count == 0 then
call PauseTimer(T)
endif
endmethod
static method UnitDistCheck takes nothing returns nothing
local unit u = GetEnumUnit()
local real dx = tmpBoomerang.dummy.x - GetUnitX(u)
local real dy = tmpBoomerang.dummy.y - GetUnitY(u)
if (dx*dx + dy*dy) > (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then
call GroupRemoveUnit(tmpBoomerang.damagedUnits, u)
endif
set u = null
endmethod
static method DamageFilterFunc takes nothing returns boolean
local unit u = GetFilterUnit()
if tmpBoomerang.markedForDestruction then
return false
endif
// check if unit is a valid target for damage
if ValidTarget(u, tmpBoomerang) then
// damage the unit; if the unit for some reason cant be damaged, dont continue
if UnitDamageTarget(tmpBoomerang.caster, u, tmpBoomerang.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE) then
call DestroyEffect(AddSpecialEffectTarget(HIT_FX, u, HIT_FX_ATTPT))
call GroupAddUnit(tmpBoomerang.damagedUnits, u)
call tmpBoomerang.reduceDamage()
endif
endif
set u = null
return false
endmethod
static method TreeFilterFunc takes nothing returns boolean
local destructable d = GetFilterDestructable()
local real x
local real y
local real dx
local real dy
if tmpBoomerang.markedForDestruction then
// short circuit logic after the damage boundary has been reached
return false
endif
// filter out dead and non-tree destructables
if (not IsDestructableDead(d)) and IsDestructableTree(d) then
set tmpBoomerang.overTree = true
set x = GetWidgetX(d)
set y = GetWidgetY(d)
set dx = tmpBoomerang.dummy.x - x
set dy = tmpBoomerang.dummy.y - y
// tree must be inside the collision radius
if (dx*dx + dy*dy) <= (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then
static if KILL_TREES then
call KillDestructable(d)
call tmpBoomerang.reduceDamage()
else
// if Trees may not be destroyed, destroy the boomerang instead
set tmpBoomerang.markedForDestruction = true
endif
endif
endif
set d = null
return false
endmethod
static method LightTreeFilterFunc takes nothing returns boolean
local destructable d = GetFilterDestructable()
if not IsDestructableDead(d) and IsDestructableTree(d) then
set tmpBoomerang.overTree = true
endif
set d = null
return false
endmethod
private static method Callback takes nothing returns nothing
local integer i = Count - 1
local thistype this
local real x
local real y
local real launchX
local real launchY
local real deltaX
local real deltaY
local real distance
local real angleBase
local real angleIncrement
local real offset
loop
exitwhen i < 0
set this = Structs[i]
set tmpBoomerang = this
//
// make the boomerang home, even if the caster moves
if this.target != null then
set this.targetX = GetUnitX(this.target)
set this.targetY = GetUnitY(this.target)
endif
set launchX = GetUnitX(this.caster)
set launchY = GetUnitY(this.caster)
set deltaX = this.targetX - launchX
set deltaY = this.targetY - launchY
set distance = SquareRoot(deltaX*deltaX + deltaY*deltaY)
set angleBase = Atan2(deltaY, deltaX) - ((bj_PI / BOOMERANG_FOCUS) / 2)
// functions for moving the boomerang:
// r(a)=distance*Sin(BOOMERANG_FOCUS*a) // a is the angle and goes from 90 to 0 // distance from center point
// x(a)=Cos(a)*r(a) // x and y coordinates in relation to the location it was cast.
// y(a)=Sin(a)*r(a) // note that i inlined some things to allow casting the boomerang in all directions from any point on the map
set offset = distance * Sin(BOOMERANG_FOCUS * this.angleCurrent)
set x = launchX + (Cos(angleBase + this.angleCurrent) * offset)
set y = launchY + (Sin(angleBase + this.angleCurrent) * offset)
set this.dummy.x = x
set this.dummy.y = y
static if ALLOW_MULTIPLE_HITS then
call ForGroup(this.damagedUnits, function thistype.UnitDistCheck)
endif
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, BOOMERANG_COLLSIZE, DamageFilter)
static if not IGNORE_TREES then
set this.overTree = false
call MoveRectTo(R, x, y)
call EnumDestructablesInRect(R, TreeFilter, null)
endif
static if COLLIDE_WITH_GROUND then
static if IGNORE_TREES then
set this.overTree = false
call MoveRectTo(R, x, y)
call EnumDestructablesInRect(R, LightTreeFilter, null)
endif
if not IsTerrainWalkable(x, y) and not this.overTree then
set this.markedForDestruction = true
endif
endif
set angleIncrement = TICK * ((bj_PI / BOOMERANG_FOCUS) / 2) * (Boomerang_Speed(this.level) / distance)
if this.angleDirection == ANGLE_DIRECTION_FORWARD then
set this.angleCurrent = this.angleCurrent + angleIncrement
else
set this.angleCurrent = this.angleCurrent - angleIncrement
endif
if this.angleCurrent <= 0 or this.angleCurrent >= (bj_PI / BOOMERANG_FOCUS) or IsUnitType(this.caster, UNIT_TYPE_DEAD) == true then
set this.markedForDestruction = true
endif
if this.markedForDestruction then
call this.destroy()
endif
set i = i - 1
endloop
endmethod
static method create takes unit caster, unit target, real targetX, real targetY, boolean direction returns thistype
local thistype this = allocate()
local real dx
local real dy
local real distance
local real angleBase
set this.caster = caster
if target == null or target == caster then
set this.target = null
set this.targetX = targetX
set this.targetY = targetY
else
set this.target = target
set this.targetX = GetUnitX(target)
set this.targetY = GetUnitY(target)
endif
set dx = this.targetX - GetUnitX(caster)
set dy = this.targetY - GetUnitY(caster)
set this.level = GetUnitAbilityLevel(this.caster, AID)
set distance = SquareRoot(dx*dx + dy*dy)
if distance == 0.0 then
set angleBase = (GetUnitFacing(this.caster) * bj_DEGTORAD)
else
set angleBase = Atan2(dy, dx)
endif
if distance < Min_Range(this.level) then
// enforce the minimum distance
set distance = Min_Range(this.level)
set this.targetX = GetUnitX(caster) + (distance * Cos(angleBase))
set this.targetY = GetUnitY(caster) + (distance * Sin(angleBase))
endif
if direction == ANGLE_DIRECTION_FORWARD then
set this.angleCurrent = 0.
else
set this.angleCurrent = (bj_PI / BOOMERANG_FOCUS)
endif
set this.angleDirection = direction
set this.damage = Damage(this.level)
set this.dummy = xefx.create(GetUnitX(caster), GetUnitY(caster), 0)
set this.dummy.fxpath = BOOMERANG_MODEL
set this.dummy.scale = BOOMERANG_SIZE
set this.dummy.z = BOOMERANG_HEIGHT
set this.damagedUnits = NewGroup()
set this.overTree = false
set this.markedForDestruction = false
// initialize the struct here
set Structs[Count] = this
set this.index = Count
if Count == 0 then
call TimerStart(T, TICK, true, function thistype.Callback)
endif
set Count = Count + 1
return this
endmethod
endstruct
private function SpellCond takes nothing returns boolean
return GetSpellAbilityId() == AID
endfunction
private function SpellAction takes nothing returns nothing
local unit caster = GetTriggerUnit()
local unit target = GetSpellTargetUnit()
local real targetX = GetSpellTargetX()
local real targetY = GetSpellTargetY()
static if USE_RIGHT_BOOMERANG then
call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_FORWARD)
endif
static if USE_LEFT_BOOMERANG then
call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_REVERSE)
endif
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function SpellCond))
call TriggerAddAction(t, function SpellAction)
set R = Rect(0, 0, 2 * BOOMERANG_COLLSIZE, 2 * BOOMERANG_COLLSIZE)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library IsTerrainWalkable initializer Init
//*****************************************************************
//* IsTerrainWalkable
//*
//* rewritten in vJass by: Anitarf
//* original implementation: Vexorian
//*
//* A function for checking if a point is pathable for ground
//* units (it does so by attempting to move an item there and
//* checking where it ended up), typically used to stop sliding
//* units before they end up stuck in trees. If the point is not
//* pathable, the function will also determine the nearest point
//* that is (the point where the item ends up).
//*****************************************************************
globals
// this value is how far from a point the item may end up for the point to be considered pathable
private constant real MAX_RANGE = 10.0
// the following two variables are set to the position of the item after each pathing check
// that way, if a point isn't pathable, these will be the coordinates of the nearest point that is
public real X = 0.0
public real Y = 0.0
// END OF CALIBRATION SECTION
// ================================================================
private rect r
private item check
private item array hidden
private integer hiddenMax = 0
endglobals
private function Init takes nothing returns nothing
set check = CreateItem('ciri',0,0)
call SetItemVisible(check,false)
set r = Rect(0.0,0.0,128.0,128.0)
endfunction
private function HideBothersomeItem takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set hidden[hiddenMax]=GetEnumItem()
call SetItemVisible(hidden[hiddenMax],false)
set hiddenMax=hiddenMax+1
endif
endfunction
// ================================================================
function IsTerrainWalkable takes real x, real y returns boolean
// first, hide any items in the area so they don't get in the way of our item
call MoveRectTo(r, x,y)
call EnumItemsInRect(r,null,function HideBothersomeItem)
// try to move the check item and get it's coordinates
call SetItemPosition(check,x,y)//this unhides the item...
set X = GetItemX(check)
set Y = GetItemY(check)
call SetItemVisible(check,false)//...so we must hide it again
// before returning, unhide any items that got hidden at the start
loop
exitwhen hiddenMax<=0
set hiddenMax=hiddenMax-1
call SetItemVisible(hidden[hiddenMax],true)
set hidden[hiddenMax]=null
endloop
// return pathability status
return (x-X)*(x-X)+(y-Y)*(y-Y) < MAX_RANGE*MAX_RANGE
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library DestructableLib initializer Initialization
//* ============================================================================ *
//* Made by PitzerMike *
//* *
//* I made this to detect if a destructable is a tree or not. It works not only *
//* for the standard trees but also for custom destructables created with the *
//* object editor. It uses a footie as a dummy with the goul's harvest ability. *
//* The dummy ids can be changed though. I also added the IsDestructableDead *
//* function for completeness. *
//* ============================================================================ *
globals
private constant integer DUMMY_UNIT_ID = 'hfoo' // footman
private constant integer HARVEST_ID = 'Ahrl' // ghouls harvest
private constant player OWNING_PLAYER = Player(15)
private unit dummy = null
endglobals
function IsDestructableDead takes destructable dest returns boolean
return GetWidgetLife(dest) <= 0.405
endfunction
function IsDestructableTree takes destructable dest returns boolean
local boolean result = false
if (dest != null) then
call PauseUnit(dummy, false)
set result = IssueTargetOrder(dummy, "harvest", dest)
call PauseUnit(dummy, true) // stops order
endif
return result
endfunction
private function Initialization takes nothing returns nothing
set dummy = CreateUnit(OWNING_PLAYER, DUMMY_UNIT_ID, 0.0, 0.0, 0.0)
call ShowUnit(dummy, false) // cannot enumerate
call UnitAddAbility(dummy, HARVEST_ID)
call UnitAddAbility(dummy, 'Aloc') // unselectable, invulnerable
call PauseUnit(dummy, true)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'h000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
library xefx initializer init requires xebasic
//**************************************************
// xefx 0.6
// --------
// Recommended: ARGB (adds ARGBrecolor method)
// For your movable fx needs
//
//**************************************************
//==================================================
globals
private constant integer MAX_INSTANCES = 8190 //change accordingly.
private constant real RECYCLE_DELAY = 4.0
//recycling, in order to show the effect correctly, must wait some time before
//removing the unit.
private timer recycler
private timer NOW
endglobals
private struct recyclebin extends array
unit u
real schedule
static recyclebin end=0
static recyclebin begin=0
static method Recycle takes nothing returns nothing
call RemoveUnit(.begin.u) //this unit is private, systems shouldn't mess with it.
set .begin.u=null
set .begin=recyclebin(integer(.begin)+1)
if(.begin==.end) then
set .begin=0
set .end=0
else
call TimerStart(recycler, .begin.schedule-TimerGetElapsed(NOW), false, function recyclebin.Recycle)
endif
endmethod
endstruct
private function init takes nothing returns nothing
set recycler=CreateTimer()
set NOW=CreateTimer()
call TimerStart(NOW,43200,true,null)
endfunction
struct xefx[MAX_INSTANCES]
public integer tag=0
private unit dummy
private effect fx=null
private real zang=0.0
private integer r=255
private integer g=255
private integer b=255
private integer a=255
private integer abil=0
static method create takes real x, real y, real facing returns xefx
local xefx this=xefx.allocate()
set this.dummy= CreateUnit(Player(15), XE_DUMMY_UNITID, x,y, facing*bj_RADTODEG)
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
return this
endmethod
method operator owner takes nothing returns player
return GetOwningPlayer(this.dummy)
endmethod
method operator owner= takes player p returns nothing
call SetUnitOwner(this.dummy,p,false)
endmethod
method operator teamcolor= takes playercolor c returns nothing
call SetUnitColor(this.dummy,c)
endmethod
method operator scale= takes real value returns nothing
call SetUnitScale(this.dummy,value,value,value)
endmethod
//! textmacro XEFX_colorstuff takes colorname, colorvar
method operator $colorname$ takes nothing returns integer
return this.$colorvar$
endmethod
method operator $colorname$= takes integer value returns nothing
set this.$colorvar$=value
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
//! endtextmacro
//! runtextmacro XEFX_colorstuff("red","r")
//! runtextmacro XEFX_colorstuff("green","g")
//! runtextmacro XEFX_colorstuff("blue","b")
//! runtextmacro XEFX_colorstuff("alpha","a")
method recolor takes integer r, integer g , integer b, integer a returns nothing
set this.r=r
set this.g=g
set this.b=b
set this.a=a
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
implement optional ARGBrecolor
method operator abilityid takes nothing returns integer
return this.abil
endmethod
method operator abilityid= takes integer a returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(a!=0) then
call UnitAddAbility(this.dummy,a)
endif
set this.abil=a
endmethod
method flash takes string fx returns nothing
call DestroyEffect(AddSpecialEffectTarget(fx,this.dummy,"origin"))
endmethod
method operator xyangle takes nothing returns real
return GetUnitFacing(this.dummy)*bj_DEGTORAD
endmethod
method operator xyangle= takes real value returns nothing
call SetUnitFacing(this.dummy,value*bj_RADTODEG)
endmethod
method operator zangle takes nothing returns real
return this.zang
endmethod
method operator zangle= takes real value returns nothing
local integer i=R2I(value*bj_RADTODEG+90.5)
set this.zang=value
if(i>=180) then
set i=179
elseif(i<0) then
set i=0
endif
call SetUnitAnimationByIndex(this.dummy, i )
endmethod
method operator x takes nothing returns real
return GetUnitX(this.dummy)
endmethod
method operator y takes nothing returns real
return GetUnitY(this.dummy)
endmethod
method operator z takes nothing returns real
return GetUnitFlyHeight(this.dummy)
endmethod
method operator z= takes real value returns nothing
call SetUnitFlyHeight(this.dummy,value,0)
endmethod
method operator x= takes real value returns nothing
call SetUnitX(this.dummy,value)
endmethod
method operator y= takes real value returns nothing
call SetUnitY(this.dummy,value)
endmethod
method operator fxpath= takes string newpath returns nothing
if (this.fx!=null) then
call DestroyEffect(this.fx)
endif
if (newpath=="") then
set this.fx=null
else
set this.fx=AddSpecialEffectTarget(newpath,this.dummy,"origin")
endif
endmethod
private method onDestroy takes nothing returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(this.fx!=null) then
call DestroyEffect(this.fx)
set this.fx=null
endif
if (recyclebin.end==MAX_INSTANCES) then
call TimerStart(recycler,0,false,function recyclebin.Recycle)
call ExplodeUnitBJ(this.dummy)
else
set recyclebin.end.u=this.dummy
set recyclebin.end.schedule=TimerGetElapsed(NOW)+RECYCLE_DELAY
set recyclebin.end= recyclebin( integer(recyclebin.end)+1)
if( recyclebin.end==1) then
call TimerStart(recycler, RECYCLE_DELAY, false, function recyclebin.Recycle)
endif
call SetUnitOwner(this.dummy,Player(15),false)
endif
set this.dummy=null
endmethod
endstruct
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library GroupUtils
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a simple implementation of a stack for groups that need to
//* be in the user's control for greater than an instant of time. Additionally,
//* this library provides a single, global group variable for use with user-end
//* enumerations. It is important to note that users should not be calling
//* DestroyGroup() on the global group, since then it may not exist for when it
//* it is next needed.
//*
//* The group stack removes the need for destroying groups and replaces it with
//* a recycling method.
//* function NewGroup takes nothing returns group
//* function ReleaseGroup takes group g returns boolean
//* function GroupRefresh takes group g returns nothing
//*
//* NewGroup grabs a currently unused group from the stack or creates one if the
//* stack is empty. You can use this group however you'd like, but always
//* remember to call ReleaseGroup on it when you are done with it. If you don't
//* release it, it will 'leak' and your stack may eventually overflow if you
//* keep doing that.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hash table. 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.
//*
globals
//* Group for use with all instant enumerations
group ENUM_GROUP = CreateGroup()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Assorted constants
private constant integer MAX_HANDLE_COUNT = 408000
private constant integer MIN_HANDLE_ID = 0x100000
//* Arrays and counter for the group stack
private group array Groups
private integer array Status[MAX_HANDLE_COUNT]
private integer Count = 0
endglobals
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
set Status[GetHandleId(Groups[Count])-MIN_HANDLE_ID] = 1
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer stat = Status[GetHandleId(g)-MIN_HANDLE_ID]
local boolean b = true
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Null groups cannot be released")
set b = false
elseif stat == 0 then
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Group not part of stack")
set b = false
elseif stat == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Groups cannot be multiply released")
set b = false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Max groups achieved, destroying group")
call DestroyGroup(g)
set b = false
else
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
set Status[GetHandleId(g)-MIN_HANDLE_ID] = 2
endif
return b
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library Messages initializer Init
private function CreditsProxy takes nothing returns nothing
call ExecuteFunc(SCOPE_PRIVATE+"Credits")
endfunction
private function Commands takes nothing returns nothing
call ClearTextMessages()
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, "|cff00bfffCommands:")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-reset|r: Spawns some footmen around you.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-level |cff0000e0<level>|r: Sets the level of your hero to the specified level.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " Note that you cannot decrease your hero's level this way.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-handleid|r: Creates a location, displays its index - 0x1000001 and destroys it.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-commands|r: Shows this list.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-credits|r: Shows the credits.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-clear|r: Clears all text-messages.")
if GetExpiredTimer()!=null then
call TimerStart(GetExpiredTimer(), 60., false, function CreditsProxy)
endif
endfunction
private function Credits takes nothing returns nothing
call ClearTextMessages()
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, "|cff00bfffCredits:")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Ciebron|r for his inspiration")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00-JonNny|r for reporting some bugs")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Atideva|r for reporting a bug")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Rising_Dusk|r for his GroupUtils library")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Anitarf|r for his IsTerrainWalkable library")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Vexorian|r for JassHelper and xe")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00PipeDream|r for Grimoire")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00PitzerMike|r for JassNewGenPack and DestructableLib")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00MindWorX|r for JassNewGenPack")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00SFilip|r for TESH")
if GetExpiredTimer()!=null then
call TimerStart(GetExpiredTimer(), 60., false, function Commands)
endif
endfunction
private function ClearActions takes nothing returns nothing
call ClearTextMessages()
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-clear", true)
call TriggerAddAction(t, function ClearActions)
set t=CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-commands", true)
call TriggerAddAction(t, function Commands)
set t=CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-credits", true)
call TriggerAddAction(t, function Credits)
call TimerStart(CreateTimer(), 0., false, function Commands)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library Basic initializer Init
globals
unit HERO
endglobals
function H2I takes handle h returns integer
return GetHandleId(h)
endfunction
private function Init takes nothing returns nothing
set HERO=CreateUnit(Player(0), 'HERO', 0,0,0)
call SetWidgetLife(HERO, GetWidgetLife(HERO)/2)
call PanCameraToTimed(0,0,0)
call FogEnable(false)
call FogMaskEnable(false)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library Reset initializer Init
private function Actions takes nothing returns nothing
local real r
set r=0
loop
call CreateUnit(Player(12), 'hfoo', 0+(256*Cos(r)), 512+(256*Sin(r)), 0)
set r=r+(bj_PI/4)
exitwhen r>=(7*bj_PI)/4
endloop
set r=0
loop
call CreateUnit(Player(12), 'hfoo', 512+(256*Cos(r)), 0+(256*Sin(r)), 0)
set r=r+(bj_PI/4)
exitwhen r>=(7*bj_PI)/4
endloop
set r=0
loop
call CreateUnit(Player(12), 'hfoo', 0+(256*Cos(r)), -512+(256*Sin(r)), 0)
set r=r+(bj_PI/4)
exitwhen r>=(7*bj_PI)/4
endloop
set r=0
loop
call CreateUnit(Player(12), 'hfoo', -512+(256*Cos(r)), 0+(256*Sin(r)), 0)
set r=r+(bj_PI/4)
exitwhen r>=(7*bj_PI)/4
endloop
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerAddAction(t, function Actions)
call TriggerRegisterPlayerChatEvent(t, Player(0), "-reset", true)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library LevelUp initializer Init uses Basic
private function LevelAction takes nothing returns nothing
local integer newlvl
local string s = GetEventPlayerChatString()
if SubString(s, 0, 7)=="-level " then
set newlvl=S2I(SubString(s, 7, StringLength(s)))
if newlvl>GetUnitLevel(HERO) and newlvl<=10 then
call SetHeroLevel(HERO, newlvl, true)
else
debug call BJDebugMsg("LevelUp - Wrong new level!")
endif
else
debug call BJDebugMsg("LevelUp - Don't try to trick my triggers!")
endif
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-level ", false)
call TriggerAddAction(t, function LevelAction)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
function Trig_handleid_Actions takes nothing returns nothing
local location l=Location(0,0)
call BJDebugMsg("Max Handle ID: "+I2S(H2I(l)-0x100001))
call RemoveLocation(l)
set l=null
endfunction
//===========================================================================
function InitTrig_handleid takes nothing returns nothing
set gg_trg_handleid = CreateTrigger( )
call TriggerRegisterPlayerChatEvent(gg_trg_handleid, Player(0), "-handleid", true)
call TriggerAddAction( gg_trg_handleid, function Trig_handleid_Actions )
endfunction