scope Init initializer InitFunction
private function RandomX takes nothing returns real
return GetRandomReal(GetRectMinX(bj_mapInitialPlayableArea), GetRectMaxX(bj_mapInitialPlayableArea))
endfunction
private function RandomY takes nothing returns real
return GetRandomReal(GetRectMinY(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea))
endfunction
private function A takes nothing returns nothing
local unit u = GetTriggerUnit()
call CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), RandomX(), RandomY(), GetRandomReal(0, 360))
call TriggerSleepAction(10)
call RemoveUnit(u)
set u = null
endfunction
private function B takes nothing returns nothing
call SetCameraField(CAMERA_FIELD_ZOFFSET, GetCameraField(CAMERA_FIELD_ZOFFSET) + 200, 0)
endfunction
private function InitFunction takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'H001', 0, 0, 0)
local player p = Player(1)
local real dist = 1300
local integer creepcount = 20
local integer campcount = 15
local integer i = 0
local integer f = 360
local real x
local real y
local integer a
local real facing
local trigger t1 = CreateTrigger()
local trigger t2 = CreateTrigger()
call FogEnable(false)
call FogMaskEnable(false)
call SetPlayerState(p, PLAYER_STATE_GIVES_BOUNTY, 1)
call SetHeroLevel(u, 10, false)
call SelectUnit(u, true )
loop
exitwhen i == f
set i = i + f/campcount
set x = dist*Cos(i*bj_DEGTORAD)
set y = dist*Sin(i*bj_DEGTORAD)
set facing = bj_RADTODEG*Atan2(y, x)
set a = 0
loop
set a = a + 1
call CreateUnit(p, 'hpea', x, y, facing)
exitwhen a == creepcount
endloop
endloop
set u = null
call TriggerRegisterAnyUnitEventBJ(t1, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddAction(t1, function A)
call TriggerRegisterPlayerEvent(t2, Player(0), EVENT_PLAYER_END_CINEMATIC)
call TriggerAddAction(t2, function B)
set t1 = null
set t2 = null
endfunction
endscope
Name | Type | is_array | initial_value |
library Geysers /*
*/uses /*
*/TorrentArray /* https://www.hiveworkshop.com/threads/291207/
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
|---------------------------------|
| Spell Configuration Description |
|---------------------------------|
Creates a linear array of Geysers, sending tall columns of hot water that toss
units within the vicinity upwards, and damage them over a short duration.
*/
public struct Linear extends array
/*=================================================================*/
/* Spell Configuration */
/*=================================================================*/
/*-----------------------------------------------------------------*/
/* Static Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Basic Configuration */
/*******************************************************************/
/*
* Activation spell's rawcode
*/
static constant integer SPELL_ABILITY_ID = 'ATOR'
/*
* Determines the type of spell event (See SpellEvent library for
* more options)
*/
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
/*
* Determines if the tossed units are stunned or not (Normally,
* you should set this to true)
*/
static constant boolean PAUSE_TOSSED_TARGETS = true
/*
* Determines if the toss unit feature is enabled or not
*/
static constant boolean TOSS_TARGETS = true
/*
* Determines if the torrent borders will be hidden from the
* enemies' vision
*/
static constant boolean BLIND_ENEMIES = true
/*
* If true, the target ground's location will serve as the basis
* for where the location of the first torrent will deviate from
* but if false, the caster's location will serve as the basis
* of deviation instead. This might be good to enable if you're
* configuring a circular of a spiral array of torrents so that
* it will have its center/origin at the target location instead
* of the caster's.
*/
static constant boolean TARGET_IS_CENTER = false
/*
* Determines the attack type of the damage by the torrent
*/
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
/*
* Determines the damage type of the damage by the torrent
*/
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
/*
* Determines the weapon type of the damage by the torrent
*/
static constant weapontype WEAPON_TYPE = null
/*******************************************************************/
/* Spell Aesthetics */
/*******************************************************************/
/*
* Determines the model of the SFX when a torrent is set up
*/
static constant string SUMMON_SFX_MODEL = ""
/*
* Determines the SFXModel used for the torrent
*/
static constant string EXPLOSION_SFX_MODEL = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
/*
* Determines the SFXModel used for the spell borders
*/
static constant string BORDER_SFX_MODEL = "Abilities\\Weapons\\ZigguratMissile\\ZigguratMissile.mdl"
/*
* Determines the SFXModel used to attach to the tossed targets
*/
static constant string TOSSED_TARGET_SFX_MODEL = ""
/*
* Determines the SFXModel used to attach to the affected units
* upon landing
*/
static constant string LANDED_TARGET_SFX_MODEL = "Abilities\\Spells\\Items\\AIob\\AIobSpecialArt.mdl"
/*
* Determines the attachment point for tossedTargetSfxModel
*/
static constant string TOSSED_TARGET_SFX_ATTACH_POINT = ""
/*
* Determines the attachment point for landedTargetSfxModel
*/
static constant string LANDED_TARGET_SFX_ATTACH_POINT = "overhead"
/*
* Determines the animation played by the affected units when
* being thrown on air (Default value is an empty string)
*/
static constant string TOSSED_TARGET_ANIMATION = ""
/*-----------------------------------------------------------------*/
/* Dynamic Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Spell Mechanics */
/*******************************************************************/
/*
* Determines the torrent count
*/
static constant method torrentCount takes integer level returns integer
return 10 + 0*level
endmethod
/*
* Determines the delay of the first torrent's explosion after
* its appearance
*/
static constant method explosionDelay takes integer level returns real
return 2.00 + 0.00*level
endmethod
/*
* Determines the time interval of the next torrent release
*/
static constant method explosionInterval takes integer level returns real
return 0.25 + 0.00*level
endmethod
/*
* Determines the distance between the torrents along the axis of the
* line between the caster and the target
*/
static constant method torrentPositionOffset takes integer level returns real
return 300.00 + 0.00*level
endmethod
/*
* Determines the angle deflection of the torrents' location
* with respect to the caster as the origin
*/
static constant method torrentPositionAngleDeflection takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* Determines the duration of the torrent
*/
static constant method tossDuration takes integer level returns real
return 1.75 + 0.00*level
endmethod
/*
* Determines the radius area affected by torrent
*/
static constant method torrentRadius takes integer level returns real
return 200.00 + 0.00*level
endmethod
/*
* Determines the damage done by the torrent upon its explosion
*/
static constant method explosionDamage takes integer level returns real
return 75.00 + 25.00*level
endmethod
/*
* Determines the damage dealt per second to the affected units
*/
static constant method damagePerSecond takes integer level returns real
return 5.00 + 5.00*level
endmethod
/*
* Determines the maximum height of the thrown units
*/
static constant method torrentHeight takes integer level returns real
return 650.00 + 0.00*level
endmethod
/*
* Determines the distance of the border special effect from
* each other
*/
static constant method borderPositionOffset takes integer level returns real
return 60.00 + 0.00*level
endmethod
/*
* The code for filtering target units
*/
static method targetFilter takes unit target, unit caster returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
*/not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
*/not IsUnitType(target, UNIT_TYPE_ETHEREAL)
endmethod
/*-----------------------------------------------------------------*/
/* Extension Configuration */
/* (The following methods are optional, it's better to delete */
/* them if they're not needed) */
/*-----------------------------------------------------------------*/
/*
* The code that will run upon each torrent's explosion
*/
static method onTorrentExplode takes Torrent this, integer level returns nothing
/*
* -> Put onExplode responses here
*/
endmethod
/*
* The code that will run when each torrent vanishes
*/
static method onTorrentVanish takes Torrent this, integer level returns nothing
/*
* -> Put onVanish responses here
*/
endmethod
/*=================================================================*/
/* End of Spell Configuration */
/*=================================================================*/
implement TorrentArrayConfiguration
endstruct
endlibrary
library MeteorShower /*
*/uses /*
*/TorrentArray /* https://www.hiveworkshop.com/threads/291207/
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/StunSystem /* https://www.hiveworkshop.com/threads/196749/
|---------------------------------|
| Spell Configuration Description |
|---------------------------------|
Calls down a circular array of meteors that damage and stun units within their
area of impact. Units are also burned afterwards, taking damage over time.
*/
public struct Circular extends array
/*=================================================================*/
/* Spell Configuration */
/*=================================================================*/
/*-----------------------------------------------------------------*/
/* Static Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Basic Configuration */
/*******************************************************************/
/*
* Activation spell's rawcode
*/
static constant integer SPELL_ABILITY_ID = 'AMET'
/*
* Determines the type of spell event (See SpellEvent library for
* more options)
*/
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
/*
* Determines if the tossed units are stunned or not (Normally,
* you should set this to true)
*/
static constant boolean PAUSE_TOSSED_TARGETS = false
/*
* Determines if the toss unit feature is enabled or not
*/
static constant boolean TOSS_TARGETS = true
/*
* Determines if the torrent borders will be hidden from the
* enemies' vision
*/
static constant boolean BLIND_ENEMIES = false
/*
* If true, the target ground's location will serve as the basis
* for where the location of the first torrent will deviate from
* but if false, the caster's location will serve as the basis
* of deviation instead. This might be good to enable if you're
* configuring a circular of a spiral array of torrents so that
* it will have its center/origin at the target location instead
* of the caster's.
*/
static constant boolean TARGET_IS_CENTER = true
/*
* Determines the attack type of the damage by the torrent
*/
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
/*
* Determines the damage type of the damage by the torrent
*/
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
/*
* Determines the weapon type of the damage by the torrent
*/
static constant weapontype WEAPON_TYPE = null
/*******************************************************************/
/* Spell Aesthetics */
/*******************************************************************/
/*
* Determines the model of the SFX when a torrent is set up
*/
static constant string SUMMON_SFX_MODEL = "Units\\Demon\\Infernal\\InfernalBirth.mdl"
/*
* Determines the SFXModel used for the torrent
*/
static constant string EXPLOSION_SFX_MODEL = ""
/*
* Determines the SFXModel used for the spell borders
*/
static constant string BORDER_SFX_MODEL = "Environment\\UndeadBuildingFire\\UndeadLargeBuildingFire2.mdl"
/*
* Determines the SFXModel used to attach to the tossed targets
*/
static constant string TOSSED_TARGET_SFX_MODEL = "Environment\\LargeBuildingFire\\LargeBuildingFire1.mdl"
/*
* Determines the SFXModel used to attach to the affected units
* upon landing
*/
static constant string LANDED_TARGET_SFX_MODEL = ""
/*
* Determines the attachment point for tossedTargetSfxModel
*/
static constant string TOSSED_TARGET_SFX_ATTACH_POINT = "origin"
/*
* Determines the attachment point for landedTargetSfxModel
*/
static constant string LANDED_TARGET_SFX_ATTACH_POINT = ""
/*
* Determines the animation played by the affected units when
* being thrown on air (Default value is an empty string)
*/
static constant string TOSSED_TARGET_ANIMATION = ""
/*-----------------------------------------------------------------*/
/* Dynamic Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Spell Mechanics */
/*******************************************************************/
/*
* Determines the torrent count
*/
static constant method torrentCount takes integer level returns integer
return 10 + 0*level
endmethod
/*
* Determines the delay of the first torrent's explosion after
* its appearance
*/
static constant method explosionDelay takes integer level returns real
return 1.00 + 0.00*level
endmethod
/*
* Determines the time interval of the next torrent release
*/
static constant method explosionInterval takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* Determines the distance between the torrents along the axis of the
* line between the caster and the target
*/
static constant method torrentPositionOffset takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* Determines the angle deflection of the torrents' location
* with respect to the caster as the origin
*/
static constant method torrentPositionAngleDeflection takes integer level returns real
return 36.00 + 0.00*level
endmethod
/*
* Determines the duration of the torrent
*/
static constant method tossDuration takes integer level returns real
return 3.00 + 2.00*level
endmethod
/*
* Determines the radius area affected by torrent
*/
static constant method torrentRadius takes integer level returns real
return 175.00 + 0.00*level
endmethod
/*
* Determines the damage done by the torrent upon its explosion
*/
static constant method explosionDamage takes integer level returns real
return 75.00 + 25.00*level
endmethod
/*
* Determines the damage dealt per second to the affected units
*/
static constant method damagePerSecond takes integer level returns real
return 20.00 + 0.00*level
endmethod
/*
* Determines the maximum height of the thrown units
*/
static constant method torrentHeight takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* Determines the distance of the border special effect from
* each other
*/
static constant method borderPositionOffset takes integer level returns real
return 60.00 + 0.00*level
endmethod
/*
* Determines the stun duration
*/
static constant method stunDuration takes integer level returns real
return 0.50 + 0.50*level
endmethod
/*
* The code for filtering target units
*/
static method targetFilter takes unit target, unit caster returns boolean
return UnitAlive(target) and /*
*/IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
*/not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
*/not IsUnitType(target, UNIT_TYPE_ETHEREAL)
endmethod
/*-----------------------------------------------------------------*/
/* Extension Configuration */
/* (The following methods are optional, it's better to delete */
/* them if they're not needed) */
/*-----------------------------------------------------------------*/
/*
* The code that will run upon each torrent's explosion
*/
static method onTorrentExplode takes Torrent this, integer level returns nothing
local group targetGroup = this.targetGroup
local unit u
loop
set u = FirstOfGroup(targetGroup)
exitwhen u == null
/* It's okay to empty this group (unless you have multiple
handlers) because it will be refilled later */
call GroupRemoveUnit(targetGroup, u)
call Stun.apply(u, stunDuration(level), false)
endloop
set targetGroup = null
endmethod
/*
* The code that will run when each torrent vanishes
*/
static method onTorrentVanish takes Torrent this, integer level returns nothing
/*
* -> Put onVanish responses here
*/
endmethod
/*=================================================================*/
/* End of Spell Configuration */
/*=================================================================*/
implement TorrentArrayConfiguration
endstruct
endlibrary
library EruptionHelix /*
*/uses /*
*/TorrentArray /* https://www.hiveworkshop.com/threads/291207/
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/StunSystem /* https://www.hiveworkshop.com/threads/196749/
|---------------------------------|
| Spell Configuration Description |
|---------------------------------|
Creates a spiral chain of explosions that damage and stun units in their
vicinity
*/
public struct Spiral extends array
/*=================================================================*/
/* Spell Configuration */
/*=================================================================*/
/*-----------------------------------------------------------------*/
/* Static Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Basic Configuration */
/*******************************************************************/
/*
* Activation spell's rawcode
*/
static constant integer SPELL_ABILITY_ID = 'AERP'
/*
* Determines the type of spell event (See SpellEvent library for
* more options)
*/
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
/*
* Determines if the tossed units are stunned or not (Normally,
* you should set this to true)
*/
static constant boolean PAUSE_TOSSED_TARGETS = false
/*
* Determines if the toss unit feature is enabled or not
*/
static constant boolean TOSS_TARGETS = false
/*
* Determines if the torrent borders will be hidden from the
* enemies' vision
*/
static constant boolean BLIND_ENEMIES = false
/*
* If true, the target ground's location will serve as the basis
* for where the location of the first torrent will deviate from
* but if false, the caster's location will serve as the basis
* of deviation instead. This might be good to enable if you're
* configuring a circular of a spiral array of torrents so that
* it will have its center/origin at the target location instead
* of the caster's.
*/
static constant boolean TARGET_IS_CENTER = true
/*
* Determines the attack type of the damage by the torrent
*/
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
/*
* Determines the damage type of the damage by the torrent
*/
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
/*
* Determines the weapon type of the damage by the torrent
*/
static constant weapontype WEAPON_TYPE = null
/*******************************************************************/
/* Spell Aesthetics */
/*******************************************************************/
/*
* Determines the model of the SFX when a torrent is set up
*/
static constant string SUMMON_SFX_MODEL = "Abilities\\Spells\\Human\\SpellSteal\\SpellStealMissile.mdl"
/*
* Determines the SFXModel used for the torrent
*/
static constant string EXPLOSION_SFX_MODEL = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
/*
* Determines the SFXModel used for the spell borders
*/
static constant string BORDER_SFX_MODEL = ""
/*
* Determines the SFXModel used to attach to the tossed targets
*/
static constant string TOSSED_TARGET_SFX_MODEL = "Abilities\\Spells\\Items\\AIfb\\AIfbSpecialArt.mdl"
/*
* Determines the SFXModel used to attach to the affected units
* upon landing
*/
static constant string LANDED_TARGET_SFX_MODEL = ""
/*
* Determines the attachment point for tossedTargetSfxModel
*/
static constant string TOSSED_TARGET_SFX_ATTACH_POINT = "head"
/*
* Determines the attachment point for landedTargetSfxModel
*/
static constant string LANDED_TARGET_SFX_ATTACH_POINT = ""
/*
* Determines the animation played by the affected units when
* being thrown on air (Default value is an empty string)
*/
static constant string TOSSED_TARGET_ANIMATION = ""
/*-----------------------------------------------------------------*/
/* Dynamic Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Spell Mechanics */
/*******************************************************************/
/*
* Determines the torrent count
*/
static constant method torrentCount takes integer level returns integer
return 80 + 0*level
endmethod
/*
* Determines the delay of the first torrent's explosion after
* its appearance
*/
static constant method explosionDelay takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* Determines the time interval of the next torrent release
*/
static constant method explosionInterval takes integer level returns real
return 0.02 + 0.00*level
endmethod
/*
* Determines the distance between the torrents along the axis of the
* line between the caster and the target
*/
static constant method torrentPositionOffset takes integer level returns real
return 10.00 + 0.00*level
endmethod
/*
* Determines the angle deflection of the torrents' location
* with respect to the caster as the origin
*/
static constant method torrentPositionAngleDeflection takes integer level returns real
return -18.00 + 0.00*level
endmethod
/*
* Determines the duration of the torrent
*/
static constant method tossDuration takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* Determines the radius area affected by torrent
*/
static constant method torrentRadius takes integer level returns real
return 100.00 + 0.00*level
endmethod
/*
* Determines the damage done by the torrent upon its explosion
*/
static constant method explosionDamage takes integer level returns real
return 150.00 + 50.00*level
endmethod
/*
* Determines the damage dealt per second to the affected units
*/
static constant method damagePerSecond takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* Determines the maximum height of the thrown units
*/
static constant method torrentHeight takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* Determines the distance of the border special effect from
* each other
*/
static constant method borderPositionOffset takes integer level returns real
return 60.00 + 0.00*level
endmethod
/*
* Determines the stun duration
*/
static constant method stunDuration takes integer level returns real
return 0.50 + 0.50*level
endmethod
/*
* The code for filtering target units
*/
static method targetFilter takes unit target, unit caster returns boolean
return UnitAlive(target) and /*
*/IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
*/not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
*/not IsUnitType(target, UNIT_TYPE_ETHEREAL)
endmethod
/*-----------------------------------------------------------------*/
/* Extension Configuration */
/* (The following methods are optional, it's better to delete */
/* them if they're not needed) */
/*-----------------------------------------------------------------*/
/*
* The code that will run upon each torrent's explosion
*/
static method onTorrentExplode takes Torrent this, integer level returns nothing
local group targetGroup = this.targetGroup
local unit u
loop
set u = FirstOfGroup(targetGroup)
exitwhen u == null
/* It's okay to empty this group because it will be
refilled later */
call GroupRemoveUnit(targetGroup, u)
call Stun.apply(u, stunDuration(level), false)
endloop
set targetGroup = null
endmethod
/*
* The code that will run when each torrent vanishes
*/
static method onTorrentVanish takes Torrent this, integer level returns nothing
/*
* -> Put onVanish responses here
*/
endmethod
/*=================================================================*/
/* End of Spell Configuration */
/*=================================================================*/
implement TorrentArrayConfiguration
endstruct
endlibrary
library EarthWaterAndFire /*
*/uses /*
*/TorrentArray /* https://www.hiveworkshop.com/threads/291207/
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/SpellCloner /* https://www.hiveworkshop.com/threads/324157/
*/StunSystem /* https://www.hiveworkshop.com/threads/196749/
*/MeteorShower /*
*/Geysers /*
*/EruptionHelix /*
|---------------------------------|
| Spell Configuration Description |
|---------------------------------|
A combination of the three elemental spells
*/
/*=====================================================================*/
/* Spell Configuration */
/*=====================================================================*/
/*
* Activation spell's rawcode
*/
globals
private constant integer ABILITY_ID = 'AEWF'
endglobals
/*
* Radius of the spell
*/
private function SpellRadius takes integer level returns real
return 400.00 + 0.00*level
endfunction
public struct Earth extends array
/*-----------------------------------------------------------------*/
/* Static Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Basic Configuration */
/*******************************************************************/
static constant integer SPELL_ABILITY_ID = ABILITY_ID
/*
* Determines the type of spell event (See SpellEvent library for
* more options)
*/
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
/*
* Determines if the tossed units are stunned or not (Normally,
* you should set this to true)
*/
static constant boolean PAUSE_TOSSED_TARGETS = MeteorShower_Circular.PAUSE_TOSSED_TARGETS
/*
* Determines if the toss unit feature is enabled or not
*/
static constant boolean TOSS_TARGETS = MeteorShower_Circular.TOSS_TARGETS
/*
* Determines if the torrent borders will be hidden from the
* enemies' vision
*/
static constant boolean BLIND_ENEMIES = MeteorShower_Circular.BLIND_ENEMIES
/*
* If true, the target ground's location will serve as the basis
* for where the location of the first torrent will deviate from
* but if false, the caster's location will serve as the basis
* of deviation instead. This might be good to enable if you're
* configuring a circular of a spiral array of torrents so that
* it will have its center/origin at the target location instead
* of the caster's.
*/
static constant boolean TARGET_IS_CENTER = false
/*
* Determines the attack type of the damage by the torrent
*/
static constant attacktype ATTACK_TYPE = MeteorShower_Circular.ATTACK_TYPE
/*
* Determines the damage type of the damage by the torrent
*/
static constant damagetype DAMAGE_TYPE = MeteorShower_Circular.DAMAGE_TYPE
/*
* Determines the weapon type of the damage by the torrent
*/
static constant weapontype WEAPON_TYPE = MeteorShower_Circular.WEAPON_TYPE
/*******************************************************************/
/* Spell Aesthetics */
/*******************************************************************/
/*
* Determines the model of the SFX when a torrent is set up
*/
static constant string SUMMON_SFX_MODEL = MeteorShower_Circular.SUMMON_SFX_MODEL
/*
* Determines the SFXModel used for the torrent
*/
static constant string EXPLOSION_SFX_MODEL = MeteorShower_Circular.EXPLOSION_SFX_MODEL
/*
* Determines the SFXModel used for the spell borders
*/
static constant string BORDER_SFX_MODEL = MeteorShower_Circular.BORDER_SFX_MODEL
/*
* Determines the SFXModel used to attach to the tossed targets
*/
static constant string TOSSED_TARGET_SFX_MODEL = MeteorShower_Circular.TOSSED_TARGET_SFX_MODEL
/*
* Determines the SFXModel used to attach to the affected units
* upon landing
*/
static constant string LANDED_TARGET_SFX_MODEL = MeteorShower_Circular.LANDED_TARGET_SFX_MODEL
/*
* Determines the attachment point for tossedTargetSfxModel
*/
static constant string TOSSED_TARGET_SFX_ATTACH_POINT = MeteorShower_Circular.TOSSED_TARGET_SFX_ATTACH_POINT
/*
* Determines the attachment point for landedTargetSfxModel
*/
static constant string LANDED_TARGET_SFX_ATTACH_POINT = MeteorShower_Circular.LANDED_TARGET_SFX_ATTACH_POINT
/*
* Determines the animation played by the affected units when
* being thrown on air (Default value is an empty string)
*/
static constant string TOSSED_TARGET_ANIMATION = MeteorShower_Circular.TOSSED_TARGET_ANIMATION
/*-----------------------------------------------------------------*/
/* Dynamic Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Spell Mechanics */
/*******************************************************************/
/*
* Determines the torrent count
*/
static constant method torrentCount takes integer level returns integer
return MeteorShower_Circular.torrentCount(level)
endmethod
/*
* Determines the delay of the first torrent's explosion after
* its appearance
*/
static constant method explosionDelay takes integer level returns real
return MeteorShower_Circular.explosionDelay(level)
endmethod
/*
* Determines the time interval of the next torrent release
*/
static constant method explosionInterval takes integer level returns real
return MeteorShower_Circular.explosionInterval(level)
endmethod
/*
* Determines the distance between the torrents along the axis of the
* line between the caster and the target
*/
static constant method torrentPositionOffset takes integer level returns real
return MeteorShower_Circular.torrentPositionOffset(level)
endmethod
/*
* Determines the angle deflection of the torrents' location
* with respect to the caster as the origin
*/
static constant method torrentPositionAngleDeflection takes integer level returns real
return MeteorShower_Circular.torrentPositionAngleDeflection(level)
endmethod
/*
* Determines the duration of the torrent
*/
static constant method tossDuration takes integer level returns real
return MeteorShower_Circular.tossDuration(level)
endmethod
/*
* Determines the radius area affected by torrent
*/
static constant method torrentRadius takes integer level returns real
return MeteorShower_Circular.torrentRadius(level)
endmethod
/*
* Determines the damage done by the torrent upon its explosion
*/
static constant method explosionDamage takes integer level returns real
return MeteorShower_Circular.explosionDamage(level)
endmethod
/*
* Determines the damage dealt per second to the affected units
*/
static constant method damagePerSecond takes integer level returns real
return MeteorShower_Circular.damagePerSecond(level)
endmethod
/*
* Determines the maximum height of the thrown units
*/
static constant method torrentHeight takes integer level returns real
return MeteorShower_Circular.torrentHeight(level)
endmethod
/*
* Determines the distance of the border special effect from
* each other
*/
static constant method borderPositionOffset takes integer level returns real
return MeteorShower_Circular.borderPositionOffset(level)
endmethod
/*
* The code for filtering target units
*/
static method targetFilter takes unit target, unit caster returns boolean
return MeteorShower_Circular.targetFilter(target, caster)
endmethod
/*-----------------------------------------------------------------*/
/* Extension Configuration */
/* (The following methods are optional, it's better to delete */
/* them if they're not needed) */
/*-----------------------------------------------------------------*/
/*
* The code that will run upon each torrent's explosion
*/
static method onTorrentExplode takes Torrent this, integer level returns nothing
call MeteorShower_Circular.onTorrentExplode(this, level)
endmethod
/*
* The code that will run when each torrent vanishes
*/
static method onTorrentVanish takes Torrent this, integer level returns nothing
call MeteorShower_Circular.onTorrentVanish(this, level)
endmethod
implement TorrentArrayConfiguration
endstruct
public struct Water extends array
/*-----------------------------------------------------------------*/
/* Static Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Basic Configuration */
/*******************************************************************/
static constant integer SPELL_ABILITY_ID = ABILITY_ID
/*
* Determines the type of spell event (See SpellEvent library for
* more options)
*/
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
/*
* Determines if the tossed units are stunned or not (Normally,
* you should set this to true)
*/
static constant boolean PAUSE_TOSSED_TARGETS = Geysers_Linear.PAUSE_TOSSED_TARGETS
/*
* Determines if the toss unit feature is enabled or not
*/
static constant boolean TOSS_TARGETS = Geysers_Linear.TOSS_TARGETS
/*
* Determines if the torrent borders will be hidden from the
* enemies' vision
*/
static constant boolean BLIND_ENEMIES = Geysers_Linear.BLIND_ENEMIES
/*
* If true, the target ground's location will serve as the basis
* for where the location of the first torrent will deviate from
* but if false, the caster's location will serve as the basis
* of deviation instead. This might be good to enable if you're
* configuring a circular of a spiral array of torrents so that
* it will have its center/origin at the target location instead
* of the caster's.
*/
static constant boolean TARGET_IS_CENTER = false
/*
* Determines the attack type of the damage by the torrent
*/
static constant attacktype ATTACK_TYPE = Geysers_Linear.ATTACK_TYPE
/*
* Determines the damage type of the damage by the torrent
*/
static constant damagetype DAMAGE_TYPE = Geysers_Linear.DAMAGE_TYPE
/*
* Determines the weapon type of the damage by the torrent
*/
static constant weapontype WEAPON_TYPE = Geysers_Linear.WEAPON_TYPE
/*******************************************************************/
/* Spell Aesthetics */
/*******************************************************************/
/*
* Determines the model of the SFX when a torrent is set up
*/
static constant string SUMMON_SFX_MODEL = Geysers_Linear.SUMMON_SFX_MODEL
/*
* Determines the SFXModel used for the torrent
*/
static constant string EXPLOSION_SFX_MODEL = Geysers_Linear.EXPLOSION_SFX_MODEL
/*
* Determines the SFXModel used for the spell borders
*/
static constant string BORDER_SFX_MODEL = Geysers_Linear.BORDER_SFX_MODEL
/*
* Determines the SFXModel used to attach to the tossed targets
*/
static constant string TOSSED_TARGET_SFX_MODEL = Geysers_Linear.TOSSED_TARGET_SFX_MODEL
/*
* Determines the SFXModel used to attach to the affected units
* upon landing
*/
static constant string LANDED_TARGET_SFX_MODEL = Geysers_Linear.LANDED_TARGET_SFX_MODEL
/*
* Determines the attachment point for tossedTargetSfxModel
*/
static constant string TOSSED_TARGET_SFX_ATTACH_POINT = Geysers_Linear.TOSSED_TARGET_SFX_ATTACH_POINT
/*
* Determines the attachment point for landedTargetSfxModel
*/
static constant string LANDED_TARGET_SFX_ATTACH_POINT = Geysers_Linear.LANDED_TARGET_SFX_ATTACH_POINT
/*
* Determines the animation played by the affected units when
* being thrown on air (Default value is an empty string)
*/
static constant string TOSSED_TARGET_ANIMATION = Geysers_Linear.TOSSED_TARGET_ANIMATION
/*-----------------------------------------------------------------*/
/* Dynamic Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Spell Mechanics */
/*******************************************************************/
/*
* Determines the torrent count
*/
static constant method torrentCount takes integer level returns integer
return 1 + MeteorShower_Circular.torrentCount(level)/2
endmethod
/*
* Determines the delay of the first torrent's explosion after
* its appearance
*/
static constant method explosionDelay takes integer level returns real
return 1.25 + 0.00*level
endmethod
/*
* Determines the time interval of the next torrent release
*/
static constant method explosionInterval takes integer level returns real
return 0.20 + 0.00*level
endmethod
/*
* Determines the distance between the torrents along the axis of the
* line between the caster and the target
*/
static constant method torrentPositionOffset takes integer level returns real
return MeteorShower_Circular.torrentPositionOffset(level)
endmethod
/*
* Determines the angle deflection of the torrents' location
* with respect to the caster as the origin
*/
static constant method torrentPositionAngleDeflection takes integer level returns real
return MeteorShower_Circular.torrentPositionAngleDeflection(level)
endmethod
/*
* Determines the duration of the torrent
*/
static constant method tossDuration takes integer level returns real
return Geysers_Linear.tossDuration(level)
endmethod
/*
* Determines the radius area affected by torrent
*/
static constant method torrentRadius takes integer level returns real
return MeteorShower_Circular.torrentRadius(level)
endmethod
/*
* Determines the damage done by the torrent upon its explosion
*/
static constant method explosionDamage takes integer level returns real
return Geysers_Linear.explosionDamage(level)
endmethod
/*
* Determines the damage dealt per second to the affected units
*/
static constant method damagePerSecond takes integer level returns real
return Geysers_Linear.damagePerSecond(level)
endmethod
/*
* Determines the maximum height of the thrown units
*/
static constant method torrentHeight takes integer level returns real
return Geysers_Linear.torrentHeight(level)
endmethod
/*
* Determines the distance of the border special effect from
* each other
*/
static constant method borderPositionOffset takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* The code for filtering target units
*/
static method targetFilter takes unit target, unit caster returns boolean
return Geysers_Linear.targetFilter(target, caster)
endmethod
/*-----------------------------------------------------------------*/
/* Extension Configuration */
/* (The following methods are optional, it's better to delete */
/* them if they're not needed) */
/*-----------------------------------------------------------------*/
/*
* The code that will run upon each torrent's explosion
*/
static method onTorrentExplode takes Torrent this, integer level returns nothing
call Geysers_Linear.onTorrentExplode(this, level)
endmethod
/*
* The code that will run when each torrent vanishes
*/
static method onTorrentVanish takes Torrent this, integer level returns nothing
call Geysers_Linear.onTorrentVanish(this, level)
endmethod
implement TorrentArrayConfiguration
endstruct
public struct Fire extends array
/*-----------------------------------------------------------------*/
/* Static Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Basic Configuration */
/*******************************************************************/
static constant integer SPELL_ABILITY_ID = ABILITY_ID
/*
* Determines the type of spell event (See SpellEvent library for
* more options)
*/
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
/*
* Determines if the tossed units are stunned or not (Normally,
* you should set this to true)
*/
static constant boolean PAUSE_TOSSED_TARGETS = EruptionHelix_Spiral.PAUSE_TOSSED_TARGETS
/*
* Determines if the toss unit feature is enabled or not
*/
static constant boolean TOSS_TARGETS = EruptionHelix_Spiral.TOSS_TARGETS
/*
* Determines if the torrent borders will be hidden from the
* enemies' vision
*/
static constant boolean BLIND_ENEMIES = EruptionHelix_Spiral.BLIND_ENEMIES
/*
* If true, the target ground's location will serve as the basis
* for where the location of the first torrent will deviate from
* but if false, the caster's location will serve as the basis
* of deviation instead. This might be good to enable if you're
* configuring a circular of a spiral array of torrents so that
* it will have its center/origin at the target location instead
* of the caster's.
*/
static constant boolean TARGET_IS_CENTER = false
/*
* Determines the attack type of the damage by the torrent
*/
static constant attacktype ATTACK_TYPE = EruptionHelix_Spiral.ATTACK_TYPE
/*
* Determines the damage type of the damage by the torrent
*/
static constant damagetype DAMAGE_TYPE = EruptionHelix_Spiral.DAMAGE_TYPE
/*
* Determines the weapon type of the damage by the torrent
*/
static constant weapontype WEAPON_TYPE = EruptionHelix_Spiral.WEAPON_TYPE
/*******************************************************************/
/* Spell Aesthetics */
/*******************************************************************/
/*
* Determines the model of the SFX when a torrent is set up
*/
static constant string SUMMON_SFX_MODEL = EruptionHelix_Spiral.SUMMON_SFX_MODEL
/*
* Determines the SFXModel used for the torrent
*/
static constant string EXPLOSION_SFX_MODEL = EruptionHelix_Spiral.EXPLOSION_SFX_MODEL
/*
* Determines the SFXModel used for the spell borders
*/
static constant string BORDER_SFX_MODEL = EruptionHelix_Spiral.BORDER_SFX_MODEL
/*
* Determines the SFXModel used to attach to the tossed targets
*/
static constant string TOSSED_TARGET_SFX_MODEL = EruptionHelix_Spiral.TOSSED_TARGET_SFX_MODEL
/*
* Determines the SFXModel used to attach to the affected units
* upon landing
*/
static constant string LANDED_TARGET_SFX_MODEL = EruptionHelix_Spiral.LANDED_TARGET_SFX_MODEL
/*
* Determines the attachment point for tossedTargetSfxModel
*/
static constant string TOSSED_TARGET_SFX_ATTACH_POINT = EruptionHelix_Spiral.TOSSED_TARGET_SFX_MODEL
/*
* Determines the attachment point for landedTargetSfxModel
*/
static constant string LANDED_TARGET_SFX_ATTACH_POINT = EruptionHelix_Spiral.LANDED_TARGET_SFX_ATTACH_POINT
/*
* Determines the animation played by the affected units when
* being thrown on air (Default value is an empty string)
*/
static constant string TOSSED_TARGET_ANIMATION = EruptionHelix_Spiral.TOSSED_TARGET_ANIMATION
/*-----------------------------------------------------------------*/
/* Dynamic Configuration */
/*-----------------------------------------------------------------*/
/*******************************************************************/
/* Spell Mechanics */
/*******************************************************************/
/*
* Determines the torrent count
*/
static constant method torrentCount takes integer level returns integer
return Water.torrentCount(level)
endmethod
/*
* Determines the delay of the first torrent's explosion after
* its appearance
*/
static constant method explosionDelay takes integer level returns real
return Water.explosionDelay(level)
endmethod
/*
* Determines the time interval of the next torrent release
*/
static constant method explosionInterval takes integer level returns real
return Water.explosionInterval(level)
endmethod
/*
* Determines the distance between the torrents along the axis of the
* line between the caster and the target
*/
static constant method torrentPositionOffset takes integer level returns real
return Water.torrentPositionOffset(level)
endmethod
/*
* Determines the angle deflection of the torrents' location
* with respect to the caster as the origin
*/
static constant method torrentPositionAngleDeflection takes integer level returns real
return -Water.torrentPositionAngleDeflection(level)
endmethod
/*
* Determines the duration of the torrent
*/
static constant method tossDuration takes integer level returns real
return EruptionHelix_Spiral.tossDuration(level)
endmethod
/*
* Determines the radius area affected by torrent
*/
static constant method torrentRadius takes integer level returns real
return EruptionHelix_Spiral.torrentRadius(level)
endmethod
/*
* Determines the damage done by the torrent upon its explosion
*/
static constant method explosionDamage takes integer level returns real
return EruptionHelix_Spiral.explosionDamage(level)
endmethod
/*
* Determines the damage dealt per second to the affected units
*/
static constant method damagePerSecond takes integer level returns real
return EruptionHelix_Spiral.damagePerSecond(level)
endmethod
/*
* Determines the maximum height of the thrown units
*/
static constant method torrentHeight takes integer level returns real
return EruptionHelix_Spiral.torrentHeight(level)
endmethod
/*
* Determines the distance of the border special effect from
* each other
*/
static constant method borderPositionOffset takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* The code for filtering target units
*/
static method targetFilter takes unit target, unit caster returns boolean
return EruptionHelix_Spiral.targetFilter(target, caster)
endmethod
/*-----------------------------------------------------------------*/
/* Extension Configuration */
/* (The following methods are optional, it's better to delete */
/* them if they're not needed) */
/*-----------------------------------------------------------------*/
/*
* The code that will run upon each torrent's explosion
*/
static method onTorrentExplode takes Torrent this, integer level returns nothing
call EruptionHelix_Spiral.onTorrentExplode(this, level)
endmethod
/*
* The code that will run when each torrent vanishes
*/
static method onTorrentVanish takes Torrent this, integer level returns nothing
call EruptionHelix_Spiral.onTorrentVanish(this, level)
endmethod
implement TorrentArrayConfiguration
endstruct
/*=====================================================================*/
/* End of Spell Configuration */
/*=====================================================================*/
/*
* Just a simple spell modifier:
* Sets the target point to <spellRadius()> units in front of the caster
*/
public struct SpellModifier extends array
private static method onSpellEvent takes nothing returns nothing
local real angle
local real radius
if GetEventSpellAbilityId() == ABILITY_ID then
set angle = GetUnitFacing(GetEventSpellCaster())*bj_DEGTORAD
set radius = SpellRadius(GetEventSpellLevel())
call SpellOverridePointTargetParams(GetEventSpellLevel(), GetEventSpellCaster(), GetEventSpellTargetX() + radius*Cos(angle), GetEventSpellTargetY() + radius*Sin(angle))
endif
endmethod
implement SpellEventGeneric
endstruct
endlibrary
library TorrentArray /* v2.0.0
*/requires /*
*/TorrentSystem /* https://www.hiveworkshop.com/threads/torrentsystem-v2-0b.305301/
*/SpellEvent /* https://www.hiveworkshop.com/threads/spellevent.301895/
*/SpellCloner /* https://www.hiveworkshop.com/threads/324157/
*/Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
*/optional WorldBounds /* https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
*/optional ResourcePreloader /* https://www.hiveworkshop.com/threads/snippet-resource-preloader.287358/
Resource Link: https://www.hiveworkshop.com/threads/291207/
|---------|
| Credits |
|---------|
- AGD (Author; TorrentSystem, SpellEvent, SpellCloner, ResourcePreloader)
- Bribe (Table)
- Nestharus (WorldBounds)
|-----------|
| Importing |
|-----------|
- Import the dummy.mdx model to your map
- Copy the dummy unit and the Torrent ability in Object Editor
- Copy the trigger category containing this script and its dependencies
- Configure the spell's data in the default configuration library or alternatively,
make your own one
|--------------------|
| Configuration Tips |
|--------------------|
The configuration allows you to do many possible tricks. One of these is that it
allows you to configure the shape of the torrent array.
1. Linear Torrent Array
This is the default setup of the configuration in the Geysers sample library. If
(TARGET_IS_CENTER) is true, the location of the first torrent is twice the
distance between the caster and the target ground, else, the location is exactly
the target ground.
Conditions:
- torrentCount(level) > 1
- torrentPositionOffset(level) != 0.00
- torrentPositionAngleDeflection(level) == 0.00
2. Circular Torrent Array
If (TARGET_IS_CENTER) is true, the center of the circle is the target ground,
else, the caster's position.
Conditions:
- torrentCount(level) > 1
- torrentPositionOffset(level) == 0.00
- torrentPositionAngleDeflection(level) == 360.00/torrentCount(level)
3. Spiral Torrent Array
If (TARGET_IS_CENTER) is true, the center of the spiral is the target ground,
else, the caster's position.
Conditions:
- torrentCount(level) > 1
- torrentPositionOffset(level) != 0.00
- torrentPositionAngleDeflection(level) != 0.00
|-------------------|
| Configuration API |
|-------------------|
- Each new configuration must be inside a struct
- If you use a private struct inside a library, make sure to make that library
require this library to ensure proper initialization order
struct YourSpellConfiguration extends array
/*
* Activation spell's rawcode
*/
static constant integer SPELL_ID
/*
* Determines the type of spell event (See SpellEvent library for
* more options)
*/
static constant integer SPELL_EVENT_TYPE
/*
* Determines if the tossed units are stunned or not (Normally,
* you should set this to true)
*/
static constant boolean PAUSE_TOSSED_TARGETS
/*
* Determines if the toss unit feature is enabled or not
*/
static constant boolean TOSS_TARGETS
/*
* Determines if the torrent borders will be hidden from the
* enemies' vision
*/
static constant boolean BLIND_ENEMIES
/*
* If true, the target ground's location will serve as the basis
* for where the location of the first torrent will deviate from
* but if false, the caster's location will serve as the basis
* of deviation instead. This might be good to enable if you're
* configuring a circular of a spiral array of torrents so that
* it will have its center/origin at the target location instead
* of the caster's.
*/
static constant boolean TARGET_IS_CENTER
/*
* Determines the attack type of the damage by the torrent
*/
static constant attacktype ATTACK_TYPE
/*
* Determines the damage type of the damage by the torrent
*/
static constant damagetype DAMAGE_TYPE
/*
* Determines the weapon type of the damage by the torrent
*/
static constant weapontype WEAPON_TYPE
/*
* Determines the model of the SFX when a torrent is set up
*/
static constant string SUMMON_SFX_MODEL
/*
* Determines the SFXModel used for the torrent
*/
static constant string EXPLOSION_SFX_MODEL
/*
* Determines the SFXModel used for the spell borders
*/
static constant string BORDER_SFX_MODEL
/*
* Determines the SFXModel used to attach to the tossed targets
*/
static constant string TOSSED_TARGET_SFX_MODEL
/*
* Determines the SFXModel used to attach to the affected units
* upon landing
*/
static constant string LANDED_TARGET_SFX_MODEL
/*
* Determines the attachment point for tossedTargetSfxModel
*/
static constant string TOSSED_TARGET_SFX_ATTACH_POINT
/*
* Determines the attachment point for landedTargetSfxModel
*/
static constant string LANDED_TARGET_SFX_ATTACH_POINT
/*
* Determines the animation played by the affected units when
* being thrown on air (Default value is an empty string)
*/
static constant string TOSSED_TARGET_ANIMATION
/*
* Determines the torrent count
*/
static constant method torrentCount takes integer level returns integer
/*
* Determines the delay of the first torrent's explosion after
* its appearance
*/
static constant method explosionDelay takes integer level returns real
/*
* Determines the time interval of the next torrent release
*/
static constant method explosionInterval takes integer level returns real
/*
* Determines the distance between the torrents along the axis of the
* line between the caster and the target
*/
static constant method torrentPositionOffset takes integer level returns real
/*
* Determines the angle deflection of the torrents' location
* with respect to the caster as the origin
*/
static constant method torrentPositionAngleDeflection takes integer level returns real
/*
* Determines the duration of the torrent
*/
static constant method tossDuration takes integer level returns real
/*
* Determines the radius area affected by torrent
*/
static constant method torrentRadius takes integer level returns real
/*
* Determines the damage done by the torrent upon its appearance
*/
static constant method explosionDamage takes integer level returns real
/*
* Determines the damage dealt per second to the affected units
*/
static constant method damagePerSecond takes integer level returns real
/*
* Determines the maximum height of the thrown units
*/
static constant method torrentHeight takes integer level returns real
/*
* Determines the distance of the border special effect from
* each other
*/
static constant method borderPositionOffset takes integer level returns real
/*
* The code for filtering target units
*/
static method targetFilter takes unit target, unit caster returns boolean
/*-----------------------------------------------------------------*/
/* Optional Configuration Methods */
/*-----------------------------------------------------------------*/
/*
* The code that will run upon each torrent's explosion
*/
static method onTorrentExplode takes Torrent this, integer level returns nothing
/*
* The code that will run when each torrent vanishes
*/
static method onTorrentVanish takes Torrent this, integer level returns nothing
implement TorrentArrayConfiguration
endstruct
*===============================================================================================*/
private keyword Init
private struct TorrentList extends array
readonly Torrent torrent
readonly thistype prev
readonly thistype next
private static thistype array stack
private static method allocate takes nothing returns thistype
local thistype node = stack[0]
if stack[node] == 0 then
debug if node == JASS_MAX_ARRAY_SIZE - 2 then
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, "|cffff0000ERROR:|R TorrentList.allocate(): Overflow")
debug return 0
debug endif
set node = node + 1
set stack[0] = node
else
set stack[0] = stack[node]
set stack[node] = 0
endif
return node
endmethod
private method deallocate takes nothing returns nothing
set stack[this] = stack[0]
set stack[0] = stack[this]
endmethod
method insert takes Torrent torrent returns nothing
local thistype node = allocate()
local thistype next = this.next
set node.prev = this
set node.next = next
set next.prev = node
set this.next = node
set node.torrent = torrent
endmethod
method remove takes nothing returns Torrent
set this.next.prev = this.prev
set this.prev.next = this.next
call this.deallocate()
return this.torrent
endmethod
static method create takes nothing returns thistype
local thistype node = allocate()
set node.prev = node
set node.next = node
return node
endmethod
method destroy takes nothing returns nothing
set this.prev = 0
set this.next = 0
call this.deallocate()
endmethod
endstruct
private struct TorrentArray extends array
implement SpellClonerHeader
private static constant real SPELL_PERIOD = 1.00 // Not used
integer torrentCount
boolean pauseTossedTargets
boolean tossTargets
boolean blindEnemies
boolean targetIsCenter
real explosionDelay
real explosionInterval
real torrentPositionOffset
real torrentPositionAngleDeflection
real tossDuration
real torrentRadius
real explosionDamage
real damagePerSecond
real torrentHeight
real borderPositionOffset
attacktype attackType
damagetype damageType
weapontype weaponType
boolexpr filterExpr
string summonSfxModel
string explosionSfxModel
string borderSfxModel
string tossedTargetSfxModel
string landedTargetSfxModel
string tossedTargetSfxAttachPoint
string landedTargetSfxAttachPoint
string tossedTargetAnimation
boolexpr torrentExplosionHandler
boolexpr torrentVanishHandler
static if not LIBRARY_WorldBounds then
private static real minX
private static real maxX
private static real minY
private static real maxY
endif
private static key table
private static trigger evaluator = CreateTrigger()
readonly static thistype triggerInstance = 0
readonly integer level
private static method getBoundedValue takes real value, real min, real max returns real
if value < min then
return min
elseif value > max then
return max
endif
return value
endmethod
private static method onPeriod takes nothing returns nothing
local timer expired = GetExpiredTimer()
local TorrentList node = Table(table)[-GetHandleId(expired)]
set triggerInstance = node
call Torrent(node.next.remove()).explode(thistype(node).tossTargets)
set triggerInstance = 0
if node.next == node then
/* If the last Torrent in the list has exploded, destroy
this spell instance. */
call Table(table).remove(-GetHandleId(expired))
call node.destroy()
call PauseTimer(expired)
call DestroyTimer(expired)
endif
set expired = null
endmethod
private static method beginChainExplosion takes nothing returns nothing
local timer expired = GetExpiredTimer()
call TimerStart(expired, thistype(Table(table)[-GetHandleId(expired)]).explosionInterval, true, function thistype.onPeriod)
set expired = null
endmethod
private method onSpellEffect takes nothing returns nothing
local timer spellTimer = CreateTimer()
local real casterX = GetUnitX(GetEventSpellCaster())
local real casterY = GetUnitY(GetEventSpellCaster())
local real dx = GetEventSpellTargetX() - casterX
local real dy = GetEventSpellTargetY() - casterY
local real ds = SquareRoot(dx*dx + dy*dy)
local real angle = 0
local real arcTan
local real distance
local real centerX
local real centerY
local real x
local real y
local integer i
local integer borders
local Torrent torrent
set this.level = GetEventSpellLevel()
if this.borderPositionOffset > 0.00 then
set borders = R2I(2.00*this.torrentRadius*bj_PI/this.borderPositionOffset)
else
set borders = 0
endif
if this.blindEnemies and IsUnitEnemy(GetEventSpellCaster(), GetLocalPlayer()) then
/* Hide the torrent borders from enemies' vision */
set this.borderSfxModel = ""
endif
if ds == 0.00 then
set arcTan = GetUnitFacing(GetEventSpellCaster())*bj_DEGTORAD
else
set arcTan = Atan2(dy, dx)
endif
/* Setup torrents in their designated location */
loop
exitwhen this.torrentCount == 0
set this.torrentCount = this.torrentCount - 1
set angle = arcTan + (this.torrentPositionAngleDeflection*this.torrentCount)*bj_DEGTORAD
set distance = ds + this.torrentPositionOffset*this.torrentCount
if this.targetIsCenter then
set centerX = GetEventSpellTargetX() + distance*Cos(angle)
set centerY = GetEventSpellTargetY() + distance*Sin(angle)
else
set centerX = casterX + distance*Cos(angle)
set centerY = casterY + distance*Sin(angle)
endif
/* Implement map bounds */
static if LIBRARY_WorldBounds then
set centerX = getBoundedValue(centerX, WorldBounds.minX, WorldBounds.maxX)
set centerY = getBoundedValue(centerY, WorldBounds.minY, WorldBounds.maxY)
else
set centerX = getBoundedValue(centerX, minX, maxX)
set centerY = getBoundedValue(centerY, minY, maxY)
endif
/* Create the torrent and setup the neccessary data */
set torrent = Torrent.create(centerX, centerY, this.torrentRadius, borders, this.explosionSfxModel, this.borderSfxModel, this.summonSfxModel)
set torrent.caster = GetEventSpellCaster()
set torrent.explosionDamage = this.explosionDamage
set torrent.damagePerSecond = this.damagePerSecond
set torrent.height = this.torrentHeight
set torrent.duration = this.tossDuration
set torrent.attackType = this.attackType
set torrent.damageType = this.damageType
set torrent.weaponType = this.weaponType
set torrent.filterExpr = this.filterExpr
set torrent.pauseTargets = this.pauseTossedTargets
set torrent.tossedTargetSfxModel = this.tossedTargetSfxModel
set torrent.landedTargetSfxModel = this.landedTargetSfxModel
set torrent.tossedTargetSfxAttachPoint = this.tossedTargetSfxAttachPoint
set torrent.landedTargetSfxAttachPoint = this.landedTargetSfxAttachPoint
set torrent.tossedTargetAnimation = this.tossedTargetAnimation
call torrent.registerExplosionHandler(this.torrentExplosionHandler)
call torrent.registerVanishHandler(this.torrentVanishHandler)
call TorrentList(this).insert(torrent)
endloop
set Table(table)[-GetHandleId(spellTimer)] = this
call TimerStart(spellTimer, RMaxBJ(this.explosionDelay - this.explosionInterval, 0.00), false, function thistype.beginChainExplosion)
set spellTimer = null
endmethod
/* After casting, determine the ability casted and then loop
through all its configuration sets */
private static method onSpellStart takes nothing returns thistype
local thistype node = TorrentList.create()
call node.initSpellConfiguration(GetEventSpellAbilityId())
/* Proceed if the torrent count is 1 or more */
if node.torrentCount > 0 then
call node.onSpellEffect()
else
call TorrentList(node).destroy()
endif
return 0
endmethod
private method onSpellPeriodic takes nothing returns boolean
return false
endmethod
private method onSpellEnd takes nothing returns nothing
endmethod
implement SpellEventEx
implement SpellClonerFooter
static if not LIBRARY_WorldBounds then
private static method init takes nothing returns nothing
/* Setup Map Bounds for the spell */
local rect bounds = GetWorldBounds()
set minX = GetRectMinX(bounds)
set minY = GetRectMinY(bounds)
set maxX = GetRectMaxX(bounds)
set maxY = GetRectMaxY(bounds)
call RemoveRect(bounds)
set bounds = null
endmethod
implement Init
endif
endstruct
static if not LIBRARY_WorldBounds then
private module Init
private static method onInit takes nothing returns nothing
call init()
endmethod
endmodule
endif
private function InitConfiguration takes integer torrentCount, boolean pauseTossedTargets, boolean tossTargets, boolean blindEnemies, boolean targetIsCenter/*
*/, attacktype attackType, damagetype damageType, weapontype weaponType, string summonSfxModel, string explosionSfxModel, string borderSfxModel, string tossedTargetSfxModel/*
*/, string landedTargetSfxModel, string tossedTargetSfxAttachPoint, string landedTargetSfxAttachPoint, string tossedTargetAnimation, real explosionDelay, real explosionInterval/*
*/, real torrentPositionOffset, real torrentPositionAngleDeflection, real tossDuration, real torrentRadius, real explosionDamage, real damagePerSecond/*
*/, real torrentHeight, real borderPositionOffset, code targetFilterFunc, code torrentExplosionHandler, code torrentVanishHandler returns nothing
local TorrentArray node = TorrentArray.configuredInstance
set node.torrentCount = IMaxBJ(0, torrentCount)
set node.pauseTossedTargets = pauseTossedTargets
set node.tossTargets = tossTargets
set node.blindEnemies = blindEnemies
set node.targetIsCenter = targetIsCenter
set node.attackType = attackType
set node.damageType = damageType
set node.weaponType = weaponType
set node.summonSfxModel = summonSfxModel
set node.explosionSfxModel = explosionSfxModel
set node.borderSfxModel = borderSfxModel
set node.tossedTargetSfxModel = tossedTargetSfxModel
set node.landedTargetSfxModel = landedTargetSfxModel
set node.tossedTargetSfxAttachPoint = tossedTargetSfxAttachPoint
set node.landedTargetSfxAttachPoint = landedTargetSfxAttachPoint
set node.tossedTargetAnimation = tossedTargetAnimation
set node.explosionDelay = explosionDelay
set node.explosionInterval = explosionInterval
set node.torrentPositionOffset = torrentPositionOffset
set node.torrentPositionAngleDeflection = torrentPositionAngleDeflection
set node.tossDuration = tossDuration
set node.torrentRadius = torrentRadius
set node.explosionDamage = explosionDamage
set node.damagePerSecond = damagePerSecond
set node.torrentHeight = torrentHeight
set node.borderPositionOffset = borderPositionOffset
set node.filterExpr = Filter(targetFilterFunc)
set node.torrentExplosionHandler = Filter(torrentExplosionHandler)
set node.torrentVanishHandler = Filter(torrentVanishHandler)
endfunction
/*
* Configuration Module
* - Transforms a struct into a TorrentArray configuration
*/
module TorrentArrayConfiguration
private static method targetFilterFunc takes nothing returns boolean
return targetFilter(GetFilterUnit(), Torrent.triggerInstance.caster)
endmethod
private static method explosionHandler takes nothing returns nothing
static if thistype.onTorrentExplode.exists then
call onTorrentExplode(Torrent.triggerInstance, TorrentArray.triggerInstance.level)
endif
endmethod
private static method vanishHandler takes nothing returns nothing
static if thistype.onTorrentVanish.exists then
call onTorrentVanish(Torrent.triggerInstance, TorrentArray.triggerInstance.level)
endif
endmethod
private static method configurationFunction takes nothing returns nothing
local integer level = GetEventSpellLevel()
call InitConfiguration(/*
*/torrentCount(level) , PAUSE_TOSSED_TARGETS , TOSS_TARGETS,/*
*/BLIND_ENEMIES , TARGET_IS_CENTER , ATTACK_TYPE, DAMAGE_TYPE,/*
*/WEAPON_TYPE , SUMMON_SFX_MODEL , EXPLOSION_SFX_MODEL,/*
*/BORDER_SFX_MODEL , TOSSED_TARGET_SFX_MODEL , LANDED_TARGET_SFX_MODEL,/*
*/TOSSED_TARGET_SFX_ATTACH_POINT , LANDED_TARGET_SFX_ATTACH_POINT , TOSSED_TARGET_ANIMATION,/*
*/explosionDelay(level) , explosionInterval(level) , torrentPositionOffset(level),/*
*/torrentPositionAngleDeflection(level) , tossDuration(level) , torrentRadius(level),/*
*/explosionDamage(level) , damagePerSecond(level) , torrentHeight(level),/*
*/borderPositionOffset(level) , function thistype.targetFilterFunc, function thistype.explosionHandler, function thistype.vanishHandler)
endmethod
private static method onInit takes nothing returns nothing
call TorrentArray.create(thistype.typeid, SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.configurationFunction)
static if LIBRARY_ResourcePreloader then
call PreloadEffect(SUMMON_SFX_MODEL)
call PreloadEffect(EXPLOSION_SFX_MODEL)
call PreloadEffect(BORDER_SFX_MODEL)
call PreloadEffect(TOSSED_TARGET_SFX_MODEL)
call PreloadEffect(LANDED_TARGET_SFX_MODEL)
endif
endmethod
endmodule
endlibrary
library TorrentSystem /* v2.1.0
*/uses /*
*/StunSystem /* https://www.hiveworkshop.com/threads/196749/
*/Table /* https://www.hiveworkshop.com/threads/188084/
*/UnitDex /* https://www.hiveworkshop.com/threads/248209/
*/optional DummyRecycler /* https://www.hiveworkshop.com/threads/277659/
*/optional AutoFly /* https://www.hiveworkshop.com/threads/249422/
*/optional WorldBounds /* https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
Resource Link: https://www.hiveworkshop.com/threads/305301/
*///! novjass
|=========|
| Credits |
|=========|
/*
Author:
- AGD
Dependendies:
- iAyanami (StunSystem)
- Nestharus (WorldBounds)
- Bribe (Table)
- Flux (DummyRecycler)
- TriggerHappy (UnitDex, AutoFly)
Other:
- Aniki (Finding bugs)
- Vexorian (dummy.mdx)
- IceFrog (Original Concept)
*/
|===========|
| Importing |
|===========|
/*
1. Import the dummy.mdx model into your map.
2. Copy the "TorrentSystem" library into your map.
3. Configure the necessary data in the system configuration below.
Note:
Torrent instances is based on the index of dummy units. You should make sure that dummy units used
by this system are not excluded from unit indexers.
*/
|=====|
| API |
|=====|
struct Torrent/*
*/readonly static Torrent triggerInstance /* The exploding/vanishing torrent instance (Only use inside a torrent event handler)
*/readonly group targetGroup /* The group containing the targets of the <triggerInstance> (Only use inside a torrent event handler)
*/unit caster /* The unit considered as the owner of the Torrent
*/real height /* Height of the tossed targets
*/real duration /* Duration of the Torrent's explosion
*/real explosionDamage /* Initial damage caused by the Torrent
*/real damagePerSecond /* Damage dealt per second to the tossed targets
*/attacktype attackType /* AttackType of the damage from the Torrent
*/damagetype damageType /* DamageType of the damage from the Torrent
*/weapontype weaponType /* WeaponType of the damage from the Torrent
*/boolexpr filterExpr /* The boolexpr for filtering target units
*/boolean pauseTargets /* Determines if tossed targets are paused (Normally, you should set this to true)
*/string targetStaticSfxModel /* Sfx model attached to the targets while being tossed
*/string targetStaticSfxAttachPoint /* Sfx model attachment point for <targetStaticSfxModel>
*/string landedTargetSfxModel /* Sfx model attached to the landed targets
*/string landedTargetSfxAttachPoint /* Sfx model attachment point for <landedTargetSfxModel>
*/string tossedTargetAnimation /* Animation played by the tossed units
*/static method create takes real centerX, real centerY, real radius, integer borderCount, string model, string borderSfxModel, string onSummonSfxModel returns thistype/*
- Creates a new Torrent instance
- Arguments:
* centerX - The x-coordinate of the Torrent's center
* centerY - The y-coordinate of the Torrent's center
* radius - The radius of the Torrent
* borderCount - number of special effects created in the Torrent's circumference
* model - Sfx model for the Torrent's explosion
* borderSfxModel - Sfx model for the Torrent's borders
* onSummonSfxModel - Sfx model for the Torrent's appearance/creation
*/method destroy takes nothing returns nothing/*
- Destroys a Torrent instance
*/method explode takes boolean tossUnits returns nothing/*
- Explodes a Torrent instance, dealing initial damage to the units within its area, and tossing them while damaging them over time if <tossUnits>
is true
- The Torrent instance is then destroyed
*/method explodeTimed takes real delay, boolean tossUnits returns nothing/*
- Explodes a torrent after a certain delay
*/method registerExplosionHandler takes boolexpr expr returns nothing/*
- Registers a boolexpr that will run upon the Torrent's explosion (when explode() is called)
*/method registerVanishHandler takes boolexpr expr returns nothing/*
- Registers a boolexpr that will run upon the Torrent's disappearance, after tossed units are landed
- This will not run when <tossUnits> is false
*/method registerExplosionHandlerFunc takes code handler returns nothing/*
*/method registerVanishHandlerFunc takes code handler returns nothing/*
- Same to the methods above but takes code instead
*/method clearExplosionHandlers takes nothing returns nothing/*
*/method clearVanishHandlers takes nothing returns nothing/*
*///! endnovjass
native UnitAlive takes unit u returns boolean
/*
This serves as the default target filter flags whenever a new
Torrent instance is created. You can delete this module if
it's not needed. By default, all units except the caster are
allowed as targets. */
private module DefaultTargetFilters
private static method targetFilter takes nothing returns boolean
return GetFilterUnit() != Torrent.triggerInstance.caster
endmethod
endmodule
private module TorrentModule
/*================ SYSTEM CONFIGURATION ================*/
/*
The static timer timeout in real seconds
Default value: 1.00/32.00 (32 FPS) */
static constant real TIMEOUT = 0.031250000
/*
The delay before dummy units are removed
Make sure there's enough time for the special
effects to play its death animation
Default value: 2.00 */
static constant real DUMMY_DURATION = 2.00
/*
The owner of the dummy units
Default value: Player(14) */
static constant player DUMMY_OWNER = Player(14)
/*
The rawcode of the dummy units */
static constant integer DUMMY_ID = 'h000'
/*================ END OF CONFIGURATION ================*/
static if not LIBRARY_WorldBounds then
private static real mapMinX
private static real mapMaxX
private static real mapMinY
private static real mapMaxY
endif
private static TableArray table
private static timer staticTimer = CreateTimer()
private static group tempGroup = CreateGroup()
private static group refreshGroup
private static boolean clearGroup
private trigger onExplodeTrigger
private trigger onVanishTrigger
private integer borderCount
private real radius
private real centerX
private real centerY
private real elapsed
private real damagePerInterval
private group tossedGroup
private string model
private string borderModel
private thistype prev
private thistype next
readonly static thistype triggerInstance = 0
readonly group targetGroup
boolexpr filterExpr
boolean pauseTargets
real height
real duration
real explosionDamage
attacktype attackType
damagetype damageType
weapontype weaponType
string tossedTargetSfxModel
string landedTargetSfxModel
string tossedTargetSfxAttachPoint
string landedTargetSfxAttachPoint
string tossedTargetAnimation
unit caster
private static method getBoundedValue takes real value, real min, real max returns real
if value < min then
return min
elseif value > max then
return max
endif
return value
endmethod
private method evaluate takes trigger whichTrigger returns nothing
local thistype prevInstance = triggerInstance
set triggerInstance = this
call TriggerEvaluate(whichTrigger)
set triggerInstance = prevInstance
endmethod
private method destroyBorders takes nothing returns nothing
local integer count
if .borderModel != null then
set .borderModel = null
/* Iterate through all border special effects and destroy
each one */
set count = .borderCount
loop
exitwhen count == 0
call DestroyEffect(table[this].effect[count])
set count = count - 1
endloop
endif
call DestroyEffect(table[this].effect[0])
set .borderCount = 0
endmethod
method destroy takes nothing returns nothing
/* Check if this torrent tosses its targets
If yes, make sure it has finished its business
before destroying */
if .tossedGroup == null or .elapsed >= .duration then
/* If the torrent borders haven't been destroyed yet, do so now */
call .destroyBorders()
call table[this].flush()
static if LIBRARY_DummyRecycler then
call DummyAddRecycleTimer(GetUnitById(this), DUMMY_DURATION)
else
call UnitApplyTimedLife(GetUnitById(this), 'BTLF', DUMMY_DURATION)
endif
call DestroyTrigger(.onExplodeTrigger)
call DestroyTrigger(.onVanishTrigger)
call DestroyGroup(.targetGroup)
call DestroyGroup(.tossedGroup)
set .pauseTargets = false
set .caster = null
set .centerX = 0.00
set .centerY = 0.00
set .radius = 0.00
set .height = 0.00
set .duration = 0.00
set .explosionDamage = 0.00
set .damagePerSecond = 0.00
set .attackType = null
set .damageType = null
set .weaponType = null
set .model = null
set .tossedTargetSfxModel = null
set .landedTargetSfxModel = null
set .tossedTargetSfxAttachPoint = null
set .landedTargetSfxAttachPoint = null
set .filterExpr = null
set .onExplodeTrigger = null
set .onVanishTrigger = null
set .targetGroup = null
if .tossedGroup != null then
call DestroyGroup(.tossedGroup)
set .tossedGroup = null
set .elapsed = 0.00
set .next.prev = .prev
set .prev.next = .next
if thistype(0).next == 0 then
call PauseTimer(staticTimer)
endif
endif
endif
endmethod
private static method periodic takes nothing returns nothing
local thistype this = thistype(0).next
local integer id
local effect sfx
local group g
local unit u
local unit dummy
local real time
local real speed
local player owner
local boolean recreate
local boolean staticSfx
/* Iterate through all currently exploding torrents and
do actions */
loop
exitwhen this == 0
set .elapsed = .elapsed + TIMEOUT
set owner = GetOwningPlayer(.caster)
if .elapsed < .duration then
set time = 2.00*.elapsed/.duration
set speed = 2.00*.height
set dummy = GetUnitById(this)
/* Adjust units' fly height and deal damage over time */
loop
set u = FirstOfGroup(.tossedGroup)
exitwhen u == null
call GroupRemoveUnit(.tossedGroup, u)
call GroupAddUnit(tempGroup, u)
if speed > 0.00 then
call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u) + speed*time - 0.5*speed*time*time, 0)
endif
if .caster == null then
call UnitDamageTarget(dummy, u, .damagePerInterval, true, false, .attackType, .damageType, .weaponType)
else
call UnitDamageTarget(.caster, u, .damagePerInterval, true, false, .attackType, .damageType, .weaponType)
endif
if .tossedTargetSfxModel != null and not UnitAlive(u) then
set id = GetHandleId(u)
set sfx = table[this].effect[id]
if sfx != null then
call DestroyEffect(sfx)
call table[this].effect.remove(id)
endif
endif
if .pauseTargets then
call Stun.apply(u, 0.01, false)
endif
endloop
set g = tempGroup
set tempGroup = .tossedGroup
set .tossedGroup = g
set g = null
set dummy = null
else
set recreate = .onVanishTrigger != null and .targetGroup == null
if recreate then
set .targetGroup = CreateGroup()
endif
loop
set u = FirstOfGroup(.tossedGroup)
exitwhen u == null
call GroupRemoveUnit(.tossedGroup, u)
if recreate then
call GroupAddUnit(.targetGroup, u)
endif
if .landedTargetSfxModel != null then
call DestroyEffect(AddSpecialEffectTarget(.landedTargetSfxModel, u, .landedTargetSfxAttachPoint))
endif
if .tossedTargetSfxModel != null then
set sfx = table[this].effect[GetHandleId(u)]
if sfx != null then
call DestroyEffect(sfx)
set sfx = null
endif
endif
/* Reset tossed units' fly height to default upon landing */
call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0.00)
endloop
if recreate then
/* Fire torrent disappearance handler */
call .evaluate(.onVanishTrigger)
endif
call .destroy()
endif
set this = .next
endloop
endmethod
method explode takes boolean tossUnits returns nothing
local unit dummy = GetUnitById(this)
local player owner = GetOwningPlayer(.caster)
local boolean evaluateExplosion
local thistype prevInstance
local unit u
call .destroyBorders()
set tossUnits = tossUnits and .duration >= 2.00*TIMEOUT
if tossUnits then
/* Add this torrent instance to the periodic loop actions */
set .next = 0
set .prev = thistype(0).prev
set thistype(0).prev = this
set .prev.next = this
set .tossedGroup = CreateGroup()
if .prev == 0 then
call TimerStart(staticTimer, TIMEOUT, true, function thistype.periodic)
endif
endif
if .model != null then
/* Creating torrent explosion SFX */
call DestroyEffect(AddSpecialEffectTarget(.model, dummy, "origin"))
endif
set evaluateExplosion = .onExplodeTrigger != null
if evaluateExplosion then
set .targetGroup = CreateGroup()
endif
set prevInstance = triggerInstance
set triggerInstance = this
call GroupEnumUnitsInRange(tempGroup, .centerX, .centerY, .radius, .filterExpr)
set triggerInstance = prevInstance
loop
set u = FirstOfGroup(tempGroup)
exitwhen u == null
call GroupRemoveUnit(tempGroup, u)
if evaluateExplosion then
call GroupAddUnit(.targetGroup, u)
endif
if tossUnits then
call GroupAddUnit(.tossedGroup, u)
static if not LIBRARY_AutoFly then
if UnitAddAbility(u, 'Arav') and UnitRemoveAbility(u, 'Arav') then
endif
endif
call SetUnitAnimation(u, .tossedTargetAnimation)
if .tossedTargetSfxModel != null then
set table[this].effect[GetHandleId(u)] = AddSpecialEffectTarget(.tossedTargetSfxModel, u, .tossedTargetSfxAttachPoint)
endif
elseif .tossedTargetSfxModel != null then
call DestroyEffect(AddSpecialEffectTarget(.tossedTargetSfxModel, u, .tossedTargetSfxAttachPoint))
endif
if .caster == null then
call UnitDamageTarget(dummy, u, .explosionDamage, true, false, .attackType, .damageType, .weaponType)
else
call UnitDamageTarget(.caster, u, .explosionDamage, true, false, .attackType, .damageType, .weaponType)
endif
endloop
if evaluateExplosion then
/* Fire the torrent's explosion handler */
call .evaluate(.onExplodeTrigger)
call GroupClear(.targetGroup)
endif
if not tossUnits then
/* If <tossUnits> is false, destroy instantly */
call .destroy()
endif
set owner = null
set dummy = null
endmethod
private static method onExplodeTimerExpire takes nothing returns nothing
local timer expired = GetExpiredTimer()
local integer timerId = GetHandleId(expired)
local thistype node = table[0][timerId]
if node > 0 then
call node.explode(true)
else
call thistype(-node).explode(false)
endif
call table[0].remove(timerId)
call DestroyTimer(expired)
set expired = null
endmethod
method explodeTimed takes real delay, boolean tossUnits returns nothing
local timer t = CreateTimer()
if tossUnits then
set table[0][GetHandleId(t)] = this
else
set table[0][GetHandleId(t)] = -this
endif
call TimerStart(t, delay, false, function thistype.onExplodeTimerExpire)
set t = null
endmethod
static method create takes real centerX, real centerY, real radius, integer borderCount, string model, string borderSfxModel, string onSummonSfxModel returns thistype
static if LIBRARY_DummyRecycler then
local unit dummy = GetRecycledDummyAnyAngle(centerX, centerY, 0)
else
local unit dummy = CreateUnit(DUMMY_OWNER, DUMMY_ID, centerX, centerY, 0)
endif
local thistype this = GetUnitId(dummy)
local real deltaAngle
local real angle
/* Initialize default target filters */
static if thistype.targetFilter.exists then
set .filterExpr = Filter(function thistype.targetFilter)
endif
set .pauseTargets = true
set .centerX = centerX
set .centerY = centerY
set .borderCount = borderCount
set .radius = radius
set .model = model
set .borderModel = borderSfxModel
if borderSfxModel != null and borderCount > 0 then
/* Adjust the radius a bit for the border effects' location
to fit with the radius */
set radius = radius - 20
set deltaAngle = 2.00*bj_PI/borderCount
loop
exitwhen borderCount == 0
set angle = deltaAngle*borderCount
/* Implement map bounds */
static if LIBRARY_WorldBounds then
set table[this].effect[borderCount] = AddSpecialEffect(borderSfxModel, getBoundedValue(centerX + radius*Cos(angle), WorldBounds.minX, WorldBounds.maxX), getBoundedValue(centerY + radius*Sin(angle), WorldBounds.minY, WorldBounds.maxY))
else
set table[this].effect[borderCount] = AddSpecialEffect(borderSfxModel, getBoundedValue(centerX + radius*Cos(angle), minX, maxX), getBoundedValue(centerY + radius*Sin(angle), minY, maxY))
endif
set borderCount = borderCount - 1
endloop
endif
call SetUnitScale(dummy, radius*0.01 , 0, 0)
set table[this].effect[0] = AddSpecialEffectTarget(onSummonSfxModel, dummy, "origin")
set .caster = dummy
set dummy = null
return this
endmethod
method registerExplosionHandler takes boolexpr expr returns nothing
if expr != null then
if .onExplodeTrigger == null then
set .onExplodeTrigger = CreateTrigger()
endif
call TriggerAddCondition(.onExplodeTrigger, expr)
endif
endmethod
method registerVanishHandler takes boolexpr expr returns nothing
if expr != null then
if .onVanishTrigger == null then
set .onVanishTrigger = CreateTrigger()
endif
call TriggerAddCondition(.onVanishTrigger, expr)
endif
endmethod
method registerExplosionHandlerFunc takes code handler returns nothing
call .registerExplosionHandler(Filter(handler))
return
endmethod
method registerVanishHandlerFunc takes code handler returns nothing
call .registerVanishHandler(Filter(handler))
return
endmethod
method clearExplosionHandlers takes nothing returns nothing
call DestroyTrigger(.onExplodeTrigger)
set .onExplodeTrigger = null
endmethod
method clearVanishHandlers takes nothing returns nothing
call DestroyTrigger(.onVanishTrigger)
set .onVanishTrigger = null
endmethod
method operator damagePerSecond= takes real dps returns nothing
set .damagePerInterval = dps*TIMEOUT
endmethod
method operator damagePerSecond takes nothing returns real
return .damagePerInterval/TIMEOUT
endmethod
/* Clean groups from removed units which is more efficient than
regularly cleaning groups per tossing period */
private static method onDeindex takes nothing returns boolean
local thistype this = thistype(0).next
local unit deindexed = GetIndexedUnit()
loop
exitwhen this == 0
call GroupRemoveUnit(.tossedGroup, deindexed)
set this = .next
endloop
set deindexed = null
return false
endmethod
private static method onInit takes nothing returns nothing
/* Setup map bounds */
static if not LIBRARY_WorldBounds then
local rect bounds = GetWorldBounds()
set mapMinX = GetRectMinX(bounds)
set mapMinY = GetRectMinY(bounds)
set mapMaxX = GetRectMaxX(bounds)
set mapMaxY = GetRectMaxY(bounds)
call RemoveRect(bounds)
set bounds = null
endif
set table = TableArray[JASS_MAX_ARRAY_SIZE]
call OnUnitDeindex(function thistype.onDeindex)
endmethod
endmodule
struct Torrent extends array
implement TorrentModule
endstruct
endlibrary
library SpellEvent /* v1.4.3 https://www.hiveworkshop.com/threads/301895/
*/uses /*
*/optional Table /* https://www.hiveworkshop.com/threads/188084/
*/optional RegisterPlayerUnitEvent /* https://www.hiveworkshop.com/threads/250266/
*/optional ResourcePreloader /* https://www.hiveworkshop.com/threads/287358/
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*///! novjass
/*
A library that eases and expands the possibilities of custom spells development.
Core Features:
1. Two-phase spell event handlers
2. Ability to manually trigger a spell event
3. Event response values overriding
4. Spell development template
1.) Spell event handlers are grouped into two: generic handlers that runs for every spell, and ability-specific
handlers. All generic handlers run first. Within them, you can do things such as changing the event parameters
(caster, target, etc.) as well as preventing the ability-specific handlers from running. The second phase are
the specific handlers which are for the spell developers to define the mechanics of the spell.
Note: Generic handlers only run for spells that have existing ability-specific handlers. This is because
generic handlers are intended as custom-spell modifier, not an ability handler. If you want to catch an event
that runs for just any ability (including normal OE abilities), you can easily use (in fact, you should) the
blizzard native events instead.
2.) You can invoke a spell event to run and define the parameters manually. This removes the need for dummy
casters in most cases.
3.) Within the generic event handlers, you can override the event parameters. The change will only affect the
ability-specific handlers.
4.) This library provides a framework for the flow of spell through the use of modules. This removes from the
spell developers the additional work of manual spell event registration, spell instance allocation, and other
minute tasks such as storing and looping through each active spell instance.
*/
|=========|
| Credits |
|=========|
/*
- AGD (Author)
- Bribe, Nestharus (SpellEffectEvent concept)
- Anitarf (Original SpellEvent Idea)
*/
|=========|
| Structs |
|=========|
/*
*/struct Spell extends array/*
*/static constant thistype GENERIC /* You can also use this like 'Spell.GENERIC.registerEventHandlers()'
Event Responses:
*/readonly static integer ABILITY_ID /*
*/readonly static integer EVENT_TYPE /*
*/readonly static integer ORDER_TYPE /*
*/readonly static integer LEVEL /*
*/readonly static player TRIGGER_PLAYER /*
*/readonly static unit TRIGGER_UNIT /*
*/readonly static unit TARGET_UNIT /*
*/readonly static item TARGET_ITEM /*
*/readonly static destructable TARGET_DEST /*
*/readonly static real TARGET_X /* Returns the x-coordinate of the caster if the spell is a 'No-target' ability
*/readonly static real TARGET_Y /* Returns the y-coordinate of the caster if the spell is a 'No-target' ability
Fields:
*/readonly integer abilityId/*
- Rawcode of the activation ability for the spell
Methods:
*/static method operator [] takes integer spellId returns Spell/*
- Returns a Spell instance based on the given activation-ability rawcode which can be used for event handler registrations
*/method setEventFlag takes integer eventType, boolean flag returns nothing/*
*/method getEventFlag takes integer eventType returns boolean/*
- Disables/Enables certain event types from running for a Spell (These flags are <true> by default)
*/method executeNoTargetEvent takes integer eventType, integer level, unit caster returns nothing/*
*/method executePointTargetEvent takes integer eventType, integer level, unit caster, real targetX, real targetY returns nothing/*
*/method executeSingleTargetEvent takes integer eventType, integer level, unit caster, widget target returns nothing/*
- Manually triggers a spell event
*/static method overrideNoTargetParams takes integer level, unit caster returns nothing/*
*/static method overridePointTargetParams takes integer level, unit caster, real targetX, real targetY returns nothing/*
*/static method overrideSingleTargetParams takes integer level, unit caster, widget target returns nothing/*
- Overrides the values of the event response variables (Only effective when called inside a generic event handler)
- The values are only overriden in the ability-specific spell event handlers
*/method registerEventHandler takes integer eventType, code handler returns nothing/*
*/method unregisterEventHandler takes integer eventType, code handler returns nothing/*
*/method clearEventHandlers takes integer eventType returns nothing/*
*/method clearHandlers takes nothing returns nothing/*
- Manages ability-specific spell event handlers
*/static method registerGenericEventHandler takes integer eventType, code handler returns nothing/*
*/static method unregisterGenericEventHandler takes integer eventType, code handler returns nothing/*
*/static method clearGenericEventHandlers takes integer eventType returns nothing/*
*/static method clearGenericHandlers takes nothing returns nothing/*
- Manages generic spell event handlers
*/
|===========|
| Variables |
|===========|
/*
Spell Event Types
*/constant integer EVENT_SPELL_CAST/*
*/constant integer EVENT_SPELL_CHANNEL/*
*/constant integer EVENT_SPELL_EFFECT/*
*/constant integer EVENT_SPELL_ENDCAST/*
*/constant integer EVENT_SPELL_FINISH/*
Spell Order Types
*/constant integer SPELL_ORDER_TYPE_TARGET/*
*/constant integer SPELL_ORDER_TYPE_POINT/*
*/constant integer SPELL_ORDER_TYPE_IMMEDIATE/*
*/
|===========|
| Functions |
|===========|
/*
Equivalent functions for the methods above
(Event Responses)
*/constant function GetEventSpellAbilityId takes nothing returns integer/*
*/constant function GetEventSpellEventType takes nothing returns integer/*
*/constant function GetEventSpellOrderType takes nothing returns integer/*
*/constant function GetEventSpellLevel takes nothing returns integer/*
*/constant function GetEventSpellUser takes nothing returns player/*
*/constant function GetEventSpellCaster takes nothing returns unit/*
*/constant function GetEventSpellTargetUnit takes nothing returns unit/*
*/constant function GetEventSpellTargetItem takes nothing returns item/*
*/constant function GetEventSpellTargetDest takes nothing returns destructable/*
*/constant function GetEventSpellTargetX takes nothing returns real/*
*/constant function GetEventSpellTargetY takes nothing returns real/*
*/function SetSpellEventFlag takes integer abilId, integer eventType, boolean flag returns nothing/*
*/function GetSpellEventFlag takes integer abilId, integer eventType returns boolean/*
*/function SpellExecuteNoTargetEvent takes integer abilId, integer eventType, integer level, unit caster returns nothing/*
*/function SpellExecutePointTargetEvent takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY returns nothing/*
*/function SpellExecuteSingleTargetEvent takes integer abilId, integer eventType, integer level, unit caster, widget target returns nothing/*
*/function SpellOverrideNoTargetParams takes integer level, unit caster returns nothing/*
*/function SpellOverridePointTargetParams takes integer level, unit caster, real targetX, real targetY returns nothing/*
*/function SpellOverrideSingleTargetParams takes integer level, unit caster, widget target returns nothing/*
*/function SpellRegisterEventHandler takes integer spellId, integer eventType, code handler returns nothing/*
*/function SpellUnregisterEventHandler takes integer spellId, integer eventType, code handler returns nothing/*
*/function SpellClearEventHandlers takes integer spellId, integer eventType returns nothing/*
*/function SpellClearHandlers takes integer spellId returns nothing/*
*/function SpellRegisterGenericEventHandler takes integer eventType, code handler returns nothing/*
*/function SpellUnregisterGenericEventHandler takes integer eventType, code handler returns nothing/*
*/function SpellClearGenericEventHandlers takes integer eventType returns nothing/*
*/function SpellClearGenericHandlers takes nothing returns nothing/*
*/
|=========|
| Modules |
|=========|
/*
Automates spell event handler registration at map initialization
Modules <SpellEvent> and <SpellEventEx> cannot both be implemented in the same struct
*/module SpellEvent/*
> Uses a single timer for all active spell instances. Standard module designed for
periodic spells with high-frequency timeout (<= 0.5 seconds)
Fields:
*/readonly thistype prev/*
*/readonly thistype next/*
- Spell instances links
- Readonly attribute is only effective outside the implementing struct, though
users are also not supposed to change these values from inside the struct
Public methods:
*/static method registerSpellEvent takes integer spellId, integer eventType returns nothing/*
- Manually registers a spell rawcode to trigger spell events
- Can be used for spells that involves more than one abilityId
Member interfaces:
- Should be declared above the module implementation
*/static integer SPELL_ID /* Ability rawcode
*/static integer SPELL_EVENT_TYPE /* Spell event type
*/static real SPELL_PERIOD /* Spell periodic actions execution period
*/method onSpellStart takes nothing returns thistype/*
- Runs right after the spell event fires
- Returning zero or a negative value will not run the periodic operations for that instance
- You can return a value different from the original value of 'this'
- The value returned will be added to the list of instances that will run onSpellPeriodic().
*/method onSpellPeriodic takes nothing returns boolean/*
- Runs periodically after the spell event fires until it returns false
*/method onSpellEnd takes nothing returns nothing/*
- Runs after method onSpellPeriodic() returns false
*/module SpellEventEx/*
> Uses 1 timer for each active spell instance. A module specifically designed for
periodic spells with low-frequency timeout (> 0.5 seconds) as it does not affect
the accuracy of the first 'tick' of the periodic operations. Here, you always
need to manually allocate/deallocate you spell instances.
Public methods:
*/static method registerSpellEvent takes integer spellId, integer eventType returns nothing/*
- Manually registers a spell rawcode to trigger spell events
- Can be used for spells that involves more than one abilityId
Member interfaces:
- Should be declared above the module implementation
*/static integer SPELL_ID /* Ability rawcode
*/static integer SPELL_EVENT_TYPE /* Spell event type
*/static real SPELL_PERIOD /* Spell periodic actions execution period
*/static method onSpellStart takes nothing returns thistype/*
- Runs right after the spell event fires
- User should manually allocate the spell instance and use it as a return value of this method
- Returning zero or a negative value will not run the periodic operations for that instance
*/method onSpellPeriodic takes nothing returns boolean/*
- Runs periodically after the spell event fires until it returns false
*/method onSpellEnd takes nothing returns nothing/*
- Runs after method onSpellPeriodic() returns false
- User must manually deallocate the spell instance inside this method
*/module SpellEventGeneric/*
Member interfaces (All optional):
- Should be declared above the module implementation
*/static method onSpellEvent takes nothing returns nothing/*
- Runs on any generic spell event
*/static method onSpellCast takes nothing returns nothing/*
*/static method onSpellChannel takes nothing returns nothing/*
*/static method onSpellEffect takes nothing returns nothing/*
*/static method onSpellEndcast takes nothing returns nothing/*
*/static method onSpellFinish takes nothing returns nothing/*
- Runs on certain spell events
*///! endnovjass
/*=================================== SYSTEM CODE ===================================*/
globals
constant integer EVENT_SPELL_CAST = 0x1
constant integer EVENT_SPELL_CHANNEL = 0x2
constant integer EVENT_SPELL_EFFECT = 0x4
constant integer EVENT_SPELL_ENDCAST = 0x8
constant integer EVENT_SPELL_FINISH = 0x10
constant integer SPELL_ORDER_TYPE_TARGET = 0x12
constant integer SPELL_ORDER_TYPE_POINT = 0x123
constant integer SPELL_ORDER_TYPE_IMMEDIATE = 0x1234
endglobals
globals
private integer tempOrderType = 0
private integer tempLevel = 0
private player tempTriggerPlayer = null
private unit tempTriggerUnit = null
private widget tempTarget = null
private real tempTargetX = 0.00
private real tempTargetY = 0.00
private boolexpr bridgeExpr
private integer array eventType
private integer array eventIndex
endglobals
private keyword Init
static if DEBUG_MODE then
private function IsValidEventType takes integer eventType returns boolean
return eventType > 0 and eventType <= (EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
endfunction
private function IsEventSingleFlag takes integer eventType returns boolean
return eventType == EVENT_SPELL_CAST or/*
*/ eventType == EVENT_SPELL_CHANNEL or/*
*/ eventType == EVENT_SPELL_EFFECT or/*
*/ eventType == EVENT_SPELL_ENDCAST or/*
*/ eventType == EVENT_SPELL_FINISH
endfunction
private function AssertError takes boolean condition, string methodName, string structName, integer instance, string message returns nothing
static if LIBRARY_ErrorMessage then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, instance, message)
endif
endfunction
endif
/*===================================================================================*/
private struct Hashtable extends array
static if LIBRARY_Table then
readonly static TableArray table
static method save takes integer index, integer key, integer value returns nothing
set table[index][key] = value
endmethod
static method load takes integer index, integer key returns integer
return table[index][key]
endmethod
static method remove takes integer index, integer key returns nothing
call table[index].remove(key)
endmethod
static method flushChild takes integer index returns nothing
call table[index].flush()
endmethod
static method init takes nothing returns nothing
set table = TableArray[JASS_MAX_ARRAY_SIZE]
endmethod
else
readonly static hashtable table = InitHashtable()
static method save takes integer index, integer key, integer value returns nothing
call SaveInteger(table, index, key, value)
endmethod
static method load takes integer index, integer key returns integer
return LoadInteger(table, index, key)
endmethod
static method remove takes integer index, integer key returns nothing
call RemoveSavedInteger(table, index, key)
endmethod
static method flushChild takes integer index returns nothing
call FlushChildHashtable(table, index)
endmethod
endif
endstruct
private module List
readonly thistype prev
readonly thistype next
method operator empty takes nothing returns boolean
return this.next == this
endmethod
method insert takes thistype node returns nothing
local thistype next = this.next
set node.prev = this
set node.next = next
set next.prev = node
set this.next = node
endmethod
method remove takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
endmethod
endmodule
/*
* One Allocator for the whole library. Yes, it would be unlikely for this system to
* reach JASS_MAX_ARRAY_SIZE instances of allocated nodes at a single time.
*
* Need to use custom Alloc because of the updated value for JASS_MAX_ARRAY_SIZE
* Credits to MyPad for the allocation algorithm
*/
private struct Node extends array
private static thistype array stack
static method allocate takes nothing returns thistype
local thistype node = stack[0]
if stack[node] == 0 then
debug call AssertError(node == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", "thistype", node, "Overflow")
set node = node + 1
set stack[0] = node
else
set stack[0] = stack[node]
set stack[node] = 0
endif
return node
endmethod
method deallocate takes nothing returns nothing
debug call AssertError(this == 0, "deallocate()", "thistype", 0, "Null node")
debug call AssertError(stack[this] > 0, "deallocate()", "thistype", this, "Double-free")
set stack[this] = stack[0]
set stack[0] = this
endmethod
endstruct
private struct ConditionList extends array
triggercondition handle
implement List
method makeHead takes nothing returns nothing
set this.prev = this
set this.next = this
endmethod
endstruct
private struct ExprList extends array
boolexpr handle
method operator conditionList takes nothing returns ConditionList
return this
endmethod
implement List
static method create takes nothing returns thistype
local thistype node = Node.allocate()
set node.prev = node
set node.next = node
return node
endmethod
method destroy takes nothing returns nothing
local thistype node = this.next
loop
exitwhen node == this
set node.handle = null
call node.remove()
call Node(node).deallocate()
set node = node.next
endloop
set this.prev = 0
set this.next = 0
call Node(this).deallocate()
endmethod
endstruct
private struct Handler extends array
readonly trigger trigger
boolean overrideParams
boolean disableBackExpr
private integer index
private static ExprList array genericList
private method operator exprList takes nothing returns ExprList
return this
endmethod
/*
* You might think that the process of registering handlers are expensive in performance
* due to constant rebuilding of triggerconditions each time, but setting up proper spell
* handlers are seldom done (often only once per spell) and a large part of them are done
* at map initialization.
*/
method updateHandlers takes nothing returns nothing
local ExprList exprNode = genericList[this.index].next
local ConditionList conditionNode
call TriggerClearConditions(this.trigger)
if exprNode != genericList[this.index].prev then
loop
exitwhen exprNode == genericList[this.index]
set conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
call TriggerAddCondition(this.trigger, exprNode.handle)
set conditionNode = conditionNode.next
endloop
set exprNode = exprNode.next
endloop
endif
set exprNode = this.exprList.next
loop
exitwhen exprNode == this.exprList
set conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
set conditionNode.handle = TriggerAddCondition(this.trigger, exprNode.handle)
set conditionNode = conditionNode.next
endloop
set exprNode = exprNode.next
endloop
endmethod
static method bridge takes nothing returns boolean
local integer triggerId = GetHandleId(GetTriggeringTrigger())
local thistype node = Hashtable.load(0, triggerId)
local trigger tempTrig
if node.disableBackExpr then
if node.exprList.next != node.exprList then
set tempTrig = node.trigger
set node.trigger = CreateTrigger()
call Hashtable.save(0, GetHandleId(node.trigger), node)
call node.updateHandlers()
call TriggerClearConditions(tempTrig)
call DestroyTrigger(tempTrig)
call Hashtable.remove(0, triggerId)
set tempTrig = null
endif
return false
endif
return node.exprList.next != node.exprList and node.overrideParams
endmethod
static method create takes integer eventIndex returns thistype
local thistype node = ExprList.create()
set node.index = eventIndex
return node
endmethod
method destroy takes nothing returns nothing
if not this.exprList.empty then
call Hashtable.remove(0, GetHandleId(this.trigger))
call DestroyTrigger(this.trigger)
set this.trigger = null
endif
set this.index = 0
call Hashtable.flushChild(this)
call this.exprList.destroy()
endmethod
static method registerGeneric takes integer eventIndex, boolexpr expr returns nothing
local integer exprId = GetHandleId(expr)
local ExprList exprNode = Hashtable.load(genericList[eventIndex], exprId)
local ConditionList conditionNode = Node.allocate()
if exprNode == 0 then
set exprNode = Node.allocate()
set exprNode.handle = expr
call exprNode.conditionList.makeHead()
call genericList[eventIndex].prev.prev.insert(exprNode)
call Hashtable.save(genericList[eventIndex], exprId, exprNode)
endif
call exprNode.conditionList.prev.insert(conditionNode)
endmethod
static method unregisterGeneric takes integer eventIndex, integer exprId returns nothing
local ExprList exprNode = Hashtable.load(genericList[eventIndex], exprId)
local ConditionList conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
call conditionNode.remove()
set conditionNode = conditionNode.next
endloop
call Hashtable.remove(genericList[eventIndex], exprId)
set exprNode.handle = null
call exprNode.remove()
call Node(exprNode).deallocate()
endmethod
static method clearGeneric takes integer eventIndex returns nothing
local ExprList exprNode = genericList[eventIndex].next
loop
exitwhen exprNode == genericList[eventIndex].prev
call unregisterGeneric(eventIndex, GetHandleId(exprNode.handle))
set exprNode = exprNode.next
endloop
endmethod
method register takes boolexpr expr returns nothing
local integer exprId = GetHandleId(expr)
local ExprList exprNode = Hashtable.load(this, exprId)
local ConditionList conditionNode = Node.allocate()
if this.exprList.empty then
set this.trigger = CreateTrigger()
call Hashtable.save(0, GetHandleId(this.trigger), this)
endif
if exprNode == 0 then
set exprNode = Node.allocate()
set exprNode.handle = expr
call exprNode.conditionList.makeHead()
call this.exprList.prev.insert(exprNode)
call Hashtable.save(this, exprId, exprNode)
call exprNode.conditionList.insert(conditionNode)
call this.updateHandlers()
else
call exprNode.conditionList.prev.insert(conditionNode)
if exprNode.next == this.exprList then
set conditionNode.handle = TriggerAddCondition(this.trigger, expr)
else
call this.updateHandlers()
endif
endif
endmethod
method unregister takes integer exprId returns nothing
local ExprList exprNode = Hashtable.load(this, exprId)
local ConditionList conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
call TriggerRemoveCondition(this.trigger, conditionNode.handle)
set conditionNode.handle = null
call conditionNode.remove()
call Node(conditionNode).deallocate()
set conditionNode = conditionNode.next
endloop
set exprNode.handle = null
call exprNode.remove()
call Node(exprNode).deallocate()
call Hashtable.remove(this, exprId)
if this.exprList.empty then
call Hashtable.remove(0, GetHandleId(this.trigger))
call Hashtable.flushChild(this)
call DestroyTrigger(this.trigger)
set this.trigger = null
endif
endmethod
method clear takes nothing returns nothing
local ExprList exprNode = this.exprList.next
loop
exitwhen exprNode == this.exprList
call this.unregister(GetHandleId(exprNode.handle))
set exprNode = exprNode.next
endloop
endmethod
debug static method hasGenericExpr takes integer eventIndex, boolexpr expr returns boolean
debug return Hashtable.load(genericList[eventIndex], GetHandleId(expr)) != 0
debug endmethod
debug method hasExpr takes boolexpr expr returns boolean
debug return Hashtable.load(this, GetHandleId(expr)) != 0
debug endmethod
method operator enabled= takes boolean flag returns nothing
if flag then
call EnableTrigger(this.trigger)
else
call DisableTrigger(this.trigger)
endif
endmethod
method operator enabled takes nothing returns boolean
return IsTriggerEnabled(this.trigger)
endmethod
private static method initGenericList takes integer eventIndex returns nothing
local ExprList exprNode = Node.allocate()
set exprNode.handle = bridgeExpr
call exprNode.conditionList.makeHead()
call exprNode.conditionList.insert(Node.allocate())
set genericList[eventIndex] = ExprList.create()
call genericList[eventIndex].insert(exprNode)
endmethod
static method init takes nothing returns nothing
call initGenericList(eventIndex[EVENT_SPELL_CAST])
call initGenericList(eventIndex[EVENT_SPELL_CHANNEL])
call initGenericList(eventIndex[EVENT_SPELL_EFFECT])
call initGenericList(eventIndex[EVENT_SPELL_ENDCAST])
call initGenericList(eventIndex[EVENT_SPELL_FINISH])
endmethod
endstruct
/*===================================================================================*/
struct Spell extends array
readonly static integer ABILITY_ID = 0
readonly static integer EVENT_TYPE = 0
readonly static integer ORDER_TYPE = 0
readonly static integer LEVEL = 0
readonly static player TRIGGER_PLAYER = null
readonly static unit TRIGGER_UNIT = null
readonly static unit TARGET_UNIT = null
readonly static item TARGET_ITEM = null
readonly static destructable TARGET_DEST = null
readonly static real TARGET_X = 0.00
readonly static real TARGET_Y = 0.00
readonly integer abilityId
private static integer spellCount = 0
private static Node spellKey
private static Handler array eventHandler
static method operator GENERIC takes nothing returns thistype
return Hashtable.load(spellKey, 0)
endmethod
static method operator [] takes integer abilId returns thistype
local thistype this = Hashtable.load(spellKey, abilId)
local integer offset
if this == 0 then
debug call AssertError(spellCount > R2I(JASS_MAX_ARRAY_SIZE/5), "Spell[]", "thistype", 0, "Overflow")
static if LIBRARY_ResourcePreloader then
call PreloadAbility(abilId)
endif
set spellCount = spellCount + 1
set thistype(spellCount).abilityId = abilId
call Hashtable.save(spellKey, abilId, spellCount)
set offset = (spellCount - 1)*5
set eventHandler[offset + eventIndex[EVENT_SPELL_CAST]] = Handler.create(eventIndex[EVENT_SPELL_CAST])
set eventHandler[offset + eventIndex[EVENT_SPELL_CHANNEL]] = Handler.create(eventIndex[EVENT_SPELL_CHANNEL])
set eventHandler[offset + eventIndex[EVENT_SPELL_EFFECT]] = Handler.create(eventIndex[EVENT_SPELL_EFFECT])
set eventHandler[offset + eventIndex[EVENT_SPELL_ENDCAST]] = Handler.create(eventIndex[EVENT_SPELL_ENDCAST])
set eventHandler[offset + eventIndex[EVENT_SPELL_FINISH]] = Handler.create(eventIndex[EVENT_SPELL_FINISH])
return spellCount
endif
return this
endmethod
static method registerGenericEventHandler takes integer eventType, code handler returns nothing
local boolexpr expr = Filter(handler)
local integer eventId = 0x10
local integer node
debug call AssertError(not IsValidEventType(eventType), "registerGenericEventHandler()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
call Handler.registerGeneric(eventIndex[eventId], expr)
set node = spellCount
loop
exitwhen node == 0
set node = node - 1
call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
endloop
endif
set eventId = eventId/2
endloop
set expr = null
endmethod
static method unregisterGenericEventHandler takes integer eventType, code handler returns nothing
local boolexpr expr = Filter(handler)
local integer eventId = 0x10
local integer node
debug call AssertError(not IsValidEventType(eventType), "unregisterGenericEventHandler()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
debug call AssertError(not Handler.hasGenericExpr(eventIndex[eventId], expr), "unregisterGenericEventHandler()", "thistype", 0, "EventType(" + I2S(eventType) + "): Code is not registered")
call Handler.unregisterGeneric(eventIndex[eventId], GetHandleId(expr))
set node = spellCount
loop
exitwhen node == 0
set node = node - 1
call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
endloop
endif
set eventId = eventId/2
endloop
set expr = null
endmethod
static method clearGenericEventHandlers takes integer eventType returns nothing
local integer eventId = 0x10
local integer node
debug call AssertError(not IsValidEventType(eventType), "clearGenericEventHandlers()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
call Handler.clearGeneric(eventIndex[eventId])
set node = spellCount
loop
exitwhen node == 0
set node = node - 1
call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
endloop
endif
set eventId = eventId/2
endloop
endmethod
static method clearGenericHandlers takes nothing returns nothing
call clearGenericEventHandlers(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
endmethod
method registerEventHandler takes integer eventType, code handler returns nothing
local boolexpr expr = Filter(handler)
local integer offset = (this - 1)*5
local integer eventId = 0x10
debug call AssertError((this) < 1 or (this) > spellCount, "registerEventHandler()", "thistype", this, "Invalid Spell instance")
debug call AssertError(not IsValidEventType(eventType), "registerEventHandler()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
if this == GENERIC then
call registerGenericEventHandler(eventType, handler)
else
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
call eventHandler[offset + eventIndex[eventId]].register(expr)
endif
set eventId = eventId/2
endloop
endif
set expr = null
endmethod
method unregisterEventHandler takes integer eventType, code handler returns nothing
local boolexpr expr = Filter(handler)
local integer offset = (this - 1)*5
local integer eventId = 0x10
debug call AssertError((this) < 1 or (this) > spellCount, "unregisterEventHandler()", "thistype", this, "Invalid Spell instance")
debug call AssertError(not IsValidEventType(eventType), "unregisterEventHandler()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
if this == GENERIC then
call unregisterGenericEventHandler(eventType, handler)
else
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
debug call AssertError(not eventHandler[offset + eventIndex[eventId]].hasExpr(expr), "registerEventHandler()", "thistype", this, "EventType(" + I2S(eventType) + "): Code is already unregistered")
call eventHandler[offset + eventIndex[eventId]].unregister(GetHandleId(expr))
endif
set eventId = eventId/2
endloop
endif
set expr = null
endmethod
method clearEventHandlers takes integer eventType returns nothing
local integer offset = (this - 1)*5
local integer eventId = 0x10
debug call AssertError((this) < 1 or (this) > spellCount, "SpellEvent", "clearEventHandlers()", this, "Invalid Spell instance")
debug call AssertError(not IsValidEventType(eventType), "SpellEvent", "clearEventHandlers()", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
if this == GENERIC then
call clearGenericEventHandlers(eventType)
else
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
call eventHandler[offset + eventIndex[eventId]].clear()
endif
set eventId = eventId/2
endloop
endif
endmethod
method clearHandlers takes nothing returns nothing
debug call AssertError((this) < 1 or (this) > spellCount, "clearHandlers()", "thistype", this, "Invalid Spell instance")
if this == GENERIC then
call this.clearGenericHandlers()
else
call this.clearEventHandlers(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
endif
endmethod
method setEventFlag takes integer eventType, boolean flag returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "setEventFlag()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
set eventHandler[(this - 1)*5 + eventIndex[eventType]].enabled = flag
endmethod
method getEventFlag takes integer eventType returns boolean
debug call AssertError(not IsEventSingleFlag(eventType), "getEventFlag()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
return eventHandler[(this - 1)*5 + eventIndex[eventType]].enabled
endmethod
method operator handlersDisabled= takes boolean disabled returns nothing
if Spell.ABILITY_ID != 0 then
set eventHandler[(this - 1)*5 + eventIndex[Spell.EVENT_TYPE]].disableBackExpr = disabled
endif
endmethod
method operator handlersDisabled takes nothing returns boolean
if Spell.ABILITY_ID == 0 then
return false
endif
return eventHandler[(this - 1)*5 + eventIndex[Spell.EVENT_TYPE]].disableBackExpr
endmethod
private static method onOverrideParams takes nothing returns nothing
if Handler.bridge() then
set ORDER_TYPE = tempOrderType
set LEVEL = tempLevel
set TRIGGER_PLAYER = GetOwningPlayer(tempTriggerUnit)
set TRIGGER_UNIT = tempTriggerUnit
set TARGET_X = tempTargetX
set TARGET_Y = tempTargetY
static if LIBRARY_Table then
set Hashtable.table[0].widget[0] = tempTarget
set TARGET_UNIT = Hashtable.table[0].unit[0]
set TARGET_ITEM = Hashtable.table[0].item[0]
set TARGET_DEST = Hashtable.table[0].destructable[0]
else
call SaveWidgetHandle(Hashtable.table, 0, 0, tempTarget)
set TARGET_UNIT = LoadUnitHandle(Hashtable.table, 0, 0)
set TARGET_ITEM = LoadItemHandle(Hashtable.table, 0, 0)
set TARGET_DEST = LoadDestructableHandle(Hashtable.table, 0, 0)
endif
set tempOrderType = 0
set tempLevel = 0
set tempTriggerUnit = null
set tempTargetX = 0.00
set tempTargetY = 0.00
set tempTarget = null
endif
endmethod
private static method overrideParams takes integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
if ABILITY_ID != 0 then
set Handler(Hashtable.load(0, GetHandleId(GetTriggeringTrigger()))).overrideParams = true
set tempOrderType = orderType
set tempLevel = level
set tempTriggerPlayer = GetOwningPlayer(triggerUnit)
set tempTriggerUnit = triggerUnit
set tempTargetX = targetX
set tempTargetY = targetY
set tempTarget = target
endif
endmethod
static method overrideNoTargetParams takes integer level, unit triggerUnit returns nothing
call overrideParams(SPELL_ORDER_TYPE_IMMEDIATE, level, triggerUnit, null, GetUnitX(triggerUnit), GetUnitY(triggerUnit))
endmethod
static method overridePointTargetParams takes integer level, unit triggerUnit, real targetX, real targetY returns nothing
call overrideParams(SPELL_ORDER_TYPE_POINT, level, triggerUnit, null, targetX, targetY)
endmethod
static method overrideSingleTargetParams takes integer level, unit triggerUnit, widget target returns nothing
call overrideParams(SPELL_ORDER_TYPE_TARGET, level, triggerUnit, target, GetWidgetX(target), GetWidgetY(target))
endmethod
private static method executeEventHandler takes Handler eventHandler, integer currentId, boolean manualExecute, integer eventFlag, integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
local boolean disableBackExpr = eventHandler.disableBackExpr
local boolean overrideParams = eventHandler.overrideParams
local integer prevId = ABILITY_ID
local integer prevEventType = EVENT_TYPE
local integer prevOrderType = ORDER_TYPE
local integer prevLevel = LEVEL
local player prevTriggerPlayer = TRIGGER_PLAYER
local unit prevTriggerUnit = TRIGGER_UNIT
local real prevTargetX = TARGET_X
local real prevTargetY = TARGET_Y
local unit prevTargetUnit = TARGET_UNIT
local item prevTargetItem = TARGET_ITEM
local destructable prevTargetDest = TARGET_DEST
local location tempLoc
set ABILITY_ID = currentId
if manualExecute then
set EVENT_TYPE = eventFlag
set ORDER_TYPE = orderType
set LEVEL = level
set TRIGGER_PLAYER = GetOwningPlayer(triggerUnit)
set TRIGGER_UNIT = triggerUnit
set TARGET_X = targetX
set TARGET_Y = targetY
static if LIBRARY_Table then
set Hashtable.table[0].widget[0] = target
set TARGET_UNIT = Hashtable.table[0].unit[0]
set TARGET_ITEM = Hashtable.table[0].item[0]
set TARGET_DEST = Hashtable.table[0].destructable[0]
else
call SaveWidgetHandle(Hashtable.table, 0, 0, target)
set TARGET_UNIT = LoadUnitHandle(Hashtable.table, 0, 0)
set TARGET_ITEM = LoadItemHandle(Hashtable.table, 0, 0)
set TARGET_DEST = LoadDestructableHandle(Hashtable.table, 0, 0)
endif
else
set EVENT_TYPE = eventType[GetHandleId(GetTriggerEventId())]
set TRIGGER_PLAYER = GetTriggerPlayer()
set TRIGGER_UNIT = GetTriggerUnit()
set LEVEL = GetUnitAbilityLevel(TRIGGER_UNIT, ABILITY_ID)
set TARGET_UNIT = GetSpellTargetUnit()
set TARGET_ITEM = GetSpellTargetItem()
set TARGET_DEST = GetSpellTargetDestructable()
if TARGET_UNIT != null then
set TARGET_X = GetUnitX(TARGET_UNIT)
set TARGET_Y = GetUnitY(TARGET_UNIT)
set ORDER_TYPE = SPELL_ORDER_TYPE_TARGET
elseif TARGET_ITEM != null then
set TARGET_X = GetItemX(TARGET_ITEM)
set TARGET_Y = GetItemY(TARGET_ITEM)
set ORDER_TYPE = SPELL_ORDER_TYPE_TARGET
elseif TARGET_DEST != null then
set TARGET_X = GetWidgetX(TARGET_DEST)
set TARGET_Y = GetWidgetY(TARGET_DEST)
set ORDER_TYPE = SPELL_ORDER_TYPE_TARGET
else
set tempLoc = GetSpellTargetLoc()
if tempLoc == null then
/* Special Case (for some no-target spells) */
set TARGET_X = GetUnitX(TRIGGER_UNIT)
set TARGET_Y = GetUnitY(TRIGGER_UNIT)
set ORDER_TYPE = SPELL_ORDER_TYPE_IMMEDIATE
else
call RemoveLocation(tempLoc)
set tempLoc = null
set TARGET_X = GetSpellTargetX()
set TARGET_Y = GetSpellTargetY()
set ORDER_TYPE = SPELL_ORDER_TYPE_POINT
endif
endif
endif
set eventHandler.disableBackExpr = false
set eventHandler.overrideParams = false
call TriggerEvaluate(eventHandler.trigger)
set eventHandler.disableBackExpr = disableBackExpr
set eventHandler.overrideParams = overrideParams
set ABILITY_ID = prevId
set EVENT_TYPE = prevEventType
set ORDER_TYPE = prevOrderType
set LEVEL = prevLevel
set TRIGGER_PLAYER = prevTriggerPlayer
set TRIGGER_UNIT = prevTriggerUnit
set TARGET_X = prevTargetX
set TARGET_Y = prevTargetY
set TARGET_UNIT = prevTargetUnit
set TARGET_ITEM = prevTargetItem
set TARGET_DEST = prevTargetDest
set prevTriggerPlayer = null
set prevTriggerUnit = null
set prevTargetUnit = null
set prevTargetItem = null
set prevTargetDest = null
endmethod
private method executeEvent takes integer eventType, integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
local Handler handler = eventHandler[(this - 1)*5 + eventIndex[eventType]]
if handler != 0 and handler.enabled then
call executeEventHandler(handler, this.abilityId, true, eventType, orderType, level, triggerUnit, target, targetX, targetY)
endif
endmethod
method executeNoTargetEvent takes integer eventType, integer level, unit triggerUnit returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "executeNoTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
call this.executeEvent(eventType, SPELL_ORDER_TYPE_IMMEDIATE, level, triggerUnit, null, GetUnitX(triggerUnit), GetUnitY(triggerUnit))
endmethod
method executePointTargetEvent takes integer eventType, integer level, unit triggerUnit, real targetX, real targetY returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "executePointTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
call this.executeEvent(eventType, SPELL_ORDER_TYPE_POINT, level, triggerUnit, null, targetX, targetY)
endmethod
method executeSingleTargetEvent takes integer eventType, integer level, unit triggerUnit, widget target returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "executeSingleTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
call this.executeEvent(eventType, SPELL_ORDER_TYPE_TARGET, level, triggerUnit, target, GetWidgetX(target), GetWidgetY(target))
endmethod
private static method onSpellEvent takes integer eventIndex returns nothing
local integer id = GetSpellAbilityId()
local Handler handler = eventHandler[(Hashtable.load(spellKey, id) - 1)*5 + eventIndex]
if handler != 0 and handler.enabled then
call executeEventHandler(handler, id, false, 0, 0, 0, null, null, 0.00, 0.00)
endif
endmethod
private static method onSpellCast takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_CAST])
endmethod
private static method onSpellChannel takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_CHANNEL])
endmethod
private static method onSpellEffect takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_EFFECT])
endmethod
private static method onSpellEndcast takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_ENDCAST])
endmethod
private static method onSpellFinish takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_FINISH])
endmethod
private static method registerEvent takes playerunitevent whichEvent, code handler returns nothing
static if LIBRARY_RegisterPlayerUnitEvent then
call RegisterAnyPlayerUnitEvent(whichEvent, handler)
else
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, whichEvent)
call TriggerAddCondition(t, Filter(handler))
set t = null
endif
endmethod
private static method init takes nothing returns nothing
/*
* This bridge boolexpr executes in after all the generic spell handlers
* before transitioning into the ability-specific spell handlers.
* This boolexpr is responsible for disabling the ability-specific handlers
* (if requested) as well as implementing the change/overriding of the
* event parameters.
*/
local code bridgeFunc = function thistype.onOverrideParams
set bridgeExpr = Filter(bridgeFunc)
set spellKey = Node.allocate()
set spellCount = spellCount + 1
call Hashtable.save(spellKey, 0, spellCount)
set eventIndex[EVENT_SPELL_CAST] = 1
set eventIndex[EVENT_SPELL_CHANNEL] = 2
set eventIndex[EVENT_SPELL_EFFECT] = 3
set eventIndex[EVENT_SPELL_ENDCAST] = 4
set eventIndex[EVENT_SPELL_FINISH] = 5
set eventType[GetHandleId(EVENT_PLAYER_UNIT_SPELL_CAST)] = EVENT_SPELL_CAST
set eventType[GetHandleId(EVENT_PLAYER_UNIT_SPELL_CHANNEL)] = EVENT_SPELL_CHANNEL
set eventType[GetHandleId(EVENT_PLAYER_UNIT_SPELL_EFFECT)] = EVENT_SPELL_EFFECT
set eventType[GetHandleId(EVENT_PLAYER_UNIT_SPELL_ENDCAST)] = EVENT_SPELL_ENDCAST
set eventType[GetHandleId(EVENT_PLAYER_UNIT_SPELL_FINISH)] = EVENT_SPELL_FINISH
call registerEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function thistype.onSpellCast)
call registerEvent(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function thistype.onSpellChannel)
call registerEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onSpellEffect)
call registerEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onSpellEndcast)
call registerEvent(EVENT_PLAYER_UNIT_SPELL_FINISH, function thistype.onSpellFinish)
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
call Hashtable.init()
endif
call init()
call Handler.init()
endmethod
endmodule
/*===================================================================================*/
constant function GetEventSpellAbilityId takes nothing returns integer
return Spell.ABILITY_ID
endfunction
constant function GetEventSpellEventType takes nothing returns integer
return Spell.EVENT_TYPE
endfunction
constant function GetEventSpellOrderType takes nothing returns integer
return Spell.ORDER_TYPE
endfunction
constant function GetEventSpellLevel takes nothing returns integer
return Spell.LEVEL
endfunction
constant function GetEventSpellUser takes nothing returns player
return Spell.TRIGGER_PLAYER
endfunction
constant function GetEventSpellCaster takes nothing returns unit
return Spell.TRIGGER_UNIT
endfunction
constant function GetEventSpellTargetUnit takes nothing returns unit
return Spell.TARGET_UNIT
endfunction
constant function GetEventSpellTargetItem takes nothing returns item
return Spell.TARGET_ITEM
endfunction
constant function GetEventSpellTargetDest takes nothing returns destructable
return Spell.TARGET_DEST
endfunction
constant function GetEventSpellTargetX takes nothing returns real
return Spell.TARGET_X
endfunction
constant function GetEventSpellTargetY takes nothing returns real
return Spell.TARGET_Y
endfunction
function SetSpellEventFlag takes integer abilId, integer eventType, boolean flag returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "SetSpellEventFlag()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell[abilId].setEventFlag(eventType, flag)
endfunction
function GetSpellEventFlag takes integer abilId, integer eventType returns boolean
debug call AssertError(not IsEventSingleFlag(eventType), "GetSpellEventFlag()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
return Spell[abilId].getEventFlag(eventType)
endfunction
function SpellExecuteNoTargetEvent takes integer abilId, integer eventType, integer level, unit caster returns nothing
call Spell[abilId].executeNoTargetEvent(eventType, level, caster)
endfunction
function SpellExecutePointTargetEvent takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY returns nothing
call Spell[abilId].executePointTargetEvent(eventType, level, caster, targetX, targetY)
endfunction
function SpellExecuteSingleTargetEvent takes integer abilId, integer eventType, integer level, unit caster, widget target returns nothing
call Spell[abilId].executeSingleTargetEvent(eventType, level, caster, target)
endfunction
function SpellOverrideNoTargetParams takes integer level, unit caster returns nothing
call Spell.overrideNoTargetParams(level, caster)
endfunction
function SpellOverridePointTargetParams takes integer level, unit caster, real targetX, real targetY returns nothing
call Spell.overridePointTargetParams(level, caster, targetX, targetY)
endfunction
function SpellOverrideSingleTargetParams takes integer level, unit caster, widget target returns nothing
call Spell.overrideSingleTargetParams(level, caster, target)
endfunction
function SpellRegisterEventHandler takes integer abilId, integer eventType, code handler returns nothing
debug call AssertError(not IsValidEventType(eventType), "SpellRegisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell[abilId].registerEventHandler(eventType, handler)
endfunction
function SpellUnregisterEventHandler takes integer abilId, integer eventType, code handler returns nothing
debug call AssertError(not IsValidEventType(eventType), "SpellUnregisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell[abilId].unregisterEventHandler(eventType, handler)
endfunction
function SpellClearEventHandlers takes integer abilId, integer eventType returns nothing
debug call AssertError(not IsValidEventType(eventType), "SpellClearEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell[abilId].clearEventHandlers(eventType)
endfunction
function SpellClearHandlers takes integer abilId returns nothing
call Spell[abilId].clearHandlers()
endfunction
function SpellRegisterGenericEventHandler takes integer eventType, code handler returns nothing
debug call AssertError(not IsValidEventType(eventType), "SpellRegisterGenericEventHandler()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell.registerGenericEventHandler(eventType, handler)
endfunction
function SpellUnregisterGenericEventHandler takes integer eventType, code handler returns nothing
debug call AssertError(not IsValidEventType(eventType), "SpellUnregisterGenericEventHandler()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell.unregisterGenericEventHandler(eventType, handler)
endfunction
function SpellClearGenericEventHandlers takes integer eventType returns nothing
debug call AssertError(not IsValidEventType(eventType), "SpellClearGenericEventHandlers()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell.clearGenericEventHandlers(eventType)
endfunction
function SpellClearGenericHandlers takes nothing returns nothing
call Spell.clearGenericHandlers()
endfunction
/*===================================================================================*/
private function DestroyTimerEx takes timer whichTimer returns nothing
call PauseTimer(whichTimer)
call DestroyTimer(whichTimer)
endfunction
private function OnSpellEventEx takes integer node, real period, code callback returns nothing
local timer periodicTimer
if node > 0 then
set periodicTimer = CreateTimer()
call Hashtable.save(0, GetHandleId(periodicTimer), node)
call TimerStart(periodicTimer, period, true, callback)
set periodicTimer = null
endif
endfunction
private function RegisterSpell takes integer abilId, integer eventType, code onSpellEvent returns nothing
if abilId != 0 then
call Spell[abilId].registerEventHandler(eventType, onSpellEvent)
endif
endfunction
module SpellEvent
readonly thistype prev
readonly thistype next
private boolean replacement
private static method onPeriodic takes nothing returns nothing
local thistype node = thistype(0).next
if node == 0 then
/*
* For some reason, some guy tried to manually remove his node from the supposed
* readonly linked-list, without realizing that he ALMOST messed up the system..
*/
call DestroyTimerEx(GetExpiredTimer())
return
endif
loop
exitwhen node == 0
if not node.onSpellPeriodic() then
call node.onSpellEnd()
set node.next.prev = node.prev
set node.prev.next = node.next
if node.replacement then
set node.replacement = false
else
call Node(node).deallocate()
endif
if thistype(0).next == 0 then
call DestroyTimerEx(GetExpiredTimer())
endif
endif
set node = node.next
endloop
endmethod
private static method onSpellEvent takes nothing returns nothing
local thistype node = Node.allocate()
local thistype last = thistype(0).prev
local boolean prevEmpty = thistype(0).next == 0
/*
* Add the new node into the list
*/
set thistype(0).prev = node
set last.next = node
set node.prev = last
set node.next = 0
set last = node.onSpellStart()
if last != node then
/*
* If the user returned a different node than the one he was given,
* remove and deallocate the earlier node and replace it with the
* new node from the user.
*/
set node.next.prev = node.prev
set node.prev.next = node.next
call Node(node).deallocate()
if last > 0 then
set last.replacement = true
set node = thistype(0).prev
set thistype(0).prev = last
set node.next = last
set last.prev = node
set last.next = 0
endif
endif
/*
* We need to use this kind of check in case the user returned 0
* but manually added some node in the list inside onSpellStart()
*/
if prevEmpty and thistype(0).next != 0 then
call TimerStart(CreateTimer(), SPELL_PERIOD, true, function thistype.onPeriodic)
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpell(SPELL_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
endmethod
static method registerSpellEvent takes integer abilId, integer eventType returns nothing
call RegisterSpell(abilId, eventType, function thistype.onSpellEvent)
endmethod
endmodule
module SpellEventEx
private static method onPeriodic takes nothing returns nothing
local timer expired = GetExpiredTimer()
local integer handleId = GetHandleId(expired)
local thistype node = Hashtable.load(0, handleId)
if not node.onSpellPeriodic() then
call node.onSpellEnd()
call Hashtable.remove(0, handleId)
call DestroyTimerEx(expired)
endif
set expired = null
endmethod
private static method onSpellEvent takes nothing returns nothing
call OnSpellEventEx(onSpellStart(), SPELL_PERIOD, function thistype.onPeriodic)
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpell(SPELL_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
endmethod
static method registerSpellEvent takes integer abilId, integer eventType returns nothing
call RegisterSpell(abilId, eventType, function thistype.onSpellEvent)
endmethod
endmodule
module SpellEventGeneric
private static method onSpellResponse takes nothing returns nothing
static if thistype.onSpellEvent.exists then
call onSpellEvent()
endif
static if thistype.onSpellCast.exists then
if Spell.EVENT_TYPE == EVENT_SPELL_CAST then
call onSpellCast()
endif
endif
static if thistype.onSpellChannel.exists then
if Spell.EVENT_TYPE == EVENT_SPELL_CHANNEL then
call onSpellChannel()
endif
endif
static if thistype.onSpellEffect.exists then
if Spell.EVENT_TYPE == EVENT_SPELL_EFFECT then
call onSpellEffect()
endif
endif
static if thistype.onSpellEndcast.exists then
if Spell.EVENT_TYPE == EVENT_SPELL_ENDCAST then
call onSpellEndcast()
endif
endif
static if thistype.onSpellFinish.exists then
if Spell.EVENT_TYPE == EVENT_SPELL_FINISH then
call onSpellFinish()
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call SpellRegisterGenericEventHandler(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH, function thistype.onSpellResponse)
endmethod
endmodule
endlibrary
library SpellCloner /* v1.1.0 https://www.hiveworkshop.com/threads/324157/
*/uses /*
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/optional Table /* https://www.hiveworkshop.com/threads/188084/
*///! novjass
/*
CREDITS:
- AGD (Author)
- JAKEZINC (Feedbacks and suggestions, which helped bring the system into its current form)
*/
|-----|
| API |
|-----|
module SpellClonerHeader/*
- Implement this module at the top of your spell struct
*/readonly static thistype configuredInstance/*
- Use this variable inside the configuration function to refer to the spell
instance being configured
*/method initSpellConfiguration takes integer abilId returns integer/*
- Call this method at the top of you onSpellStart() method to initialize
the correct local configuration of your spell instance based on the activation
ability id
- Returns the struct type id of the struct containing the configuration
*/method loadSpellConfiguration takes integer configStructId returns nothing/*
- Call this method with the value returned by initSpellConfiguration() as the
parameter
- Like initSpellConfiguration(), loads the correct local configuration of the
spell, but based on the typeid of the configuration struct
*/module SpellClonerFooter/*
- Implement this module at the bottom of your spell struct, below your SpellEvent implementation
*/static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype/*
- Creates a new local configuration instance for the spell (Return value is obsolete)
*///! endnovjass
private keyword SpellConfigList
globals
private trigger evaluator = CreateTrigger()
private integer array eventIndex
private SpellConfigList array configStructNode
private integer configuredSpellInstance = 0
endglobals
private module Init
static if LIBRARY_Table then
readonly static TableArray table
else
static constant hashtable table = InitHashtable()
endif
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set table = TableArray[JASS_MAX_ARRAY_SIZE]
endif
set eventIndex[EVENT_SPELL_CAST] = 1
set eventIndex[EVENT_SPELL_CHANNEL] = 2
set eventIndex[EVENT_SPELL_EFFECT] = 3
set eventIndex[EVENT_SPELL_ENDCAST] = 4
set eventIndex[EVENT_SPELL_FINISH] = 5
endmethod
endmodule
private struct S extends array
implement Init
endstruct
private function SaveInt takes integer index, integer key, integer value returns nothing
static if LIBRARY_Table then
set S.table[index][key] = value
else
call SaveInteger(S.table, index, key, value)
endif
endfunction
private function LoadInt takes integer index, integer key returns integer
static if LIBRARY_Table then
return S.table[index][key]
else
return LoadInteger(S.table, index, key)
endif
endfunction
private struct SpellConfigList extends array
thistype current
readonly thistype prev
readonly thistype next
readonly integer structId
readonly boolexpr configExpr
private static thistype node = 0
method evaluateExpr takes integer spellInstance returns nothing
local integer prevInstance = configuredSpellInstance
set configuredSpellInstance = spellInstance
call TriggerAddCondition(evaluator, this.configExpr)
call TriggerEvaluate(evaluator)
call TriggerClearConditions(evaluator)
set configuredSpellInstance = prevInstance
endmethod
method insert takes integer id, boolexpr expr returns thistype
local thistype next = this.next
set node = node + 1
set node.structId = id
set node.configExpr = expr
set node.prev = this
set node.next = next
set next.prev = node
set this.next = node
return node
endmethod
static method create takes nothing returns thistype
set node = node + 1
set node.prev = node
set node.next = node
set node.current = node
return node
endmethod
endstruct
private function InitSpellConfiguration takes integer spellStructId, integer spellInstance, integer abilId returns integer
local SpellConfigList configList = LoadInt(spellStructId*5 + eventIndex[Spell.EVENT_TYPE], abilId)
local integer configStructId
set configList.current = configList.current.next
set configStructId = configList.current.structId
call configList.current.evaluateExpr(spellInstance)
if configList.current.next == configList then
set configList.current = configList
endif
return configStructId
endfunction
private function CloneSpell takes integer spellStructId, integer configStructId, integer abilId, integer eventType, code configFunc returns nothing
local SpellConfigList configList
local integer eventId = 0x10
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
set configList = LoadInt(spellStructId*5 + eventIndex[eventId], abilId)
if configList == 0 then
set configList = SpellConfigList.create()
call SaveInt(spellStructId*5 + eventIndex[eventId], abilId, configList)
endif
set configStructNode[configStructId] = configList.prev.insert(configStructId, Filter(configFunc))
endif
set eventId = eventId/2
endloop
endfunction
module SpellClonerHeader
static constant integer SPELL_ID = 0
static constant integer SPELL_EVENT_TYPE = 0
method initSpellConfiguration takes integer abilId returns integer
return InitSpellConfiguration(thistype.typeid, this, abilId)
endmethod
method loadSpellConfiguration takes integer configStructId returns nothing
call SpellConfigList(configStructNode[configStructId]).evaluateExpr(this)
endmethod
static method operator configuredInstance takes nothing returns thistype
return configuredSpellInstance
endmethod
endmodule
module SpellClonerFooter
static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype
call CloneSpell(thistype.typeid, configStructId, abilId, spellEventType, configurationFunc)
call registerSpellEvent(abilId, spellEventType)
return 0
endmethod
endmodule
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 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=GetWorldBounds()
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 ResourcePreloader /* v1.4c
*/uses /*
*/optional Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
*/optional BJObjectId /* https://www.hiveworkshop.com/threads/bjobjectid.287128/
*/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 SetUnitY(dummy, GetRectMaxY(world) + 1000)
call UnitAddAbility(dummy, 'AInv')
call UnitAddAbility(dummy, 'Avul')
call UnitRemoveAbility(dummy, 'Amov')
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 DummyRecycler /*
// DummyRecycler v1.25
// by Flux
//
// A system that recycles dummy units while considering their facing angle.
// It can be used as attachment dummies for visual effects 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 there are reports that will always leave a permanent tiny bit of
// memory (0.04 KB).
// On average, retrieving a pending Dummy is approximately 4x faster compared
// to creating a new one and recycling a Dummy compared to removing it is
// approximately 1.3x faster.
// Furthermore, if you're using a lot of "Unit has entered map" events,
// using this system will even result to even more better performance
// because retrieving Dummy units does not cause that event to run.
*/ 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.
//
// function ShowDummy takes unit u, boolean flag returns nothing
// - Shows/hides Dummy Unit without conflicting with the Locust ability.
//
//--------------------
// 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 = 'h000'
//The owner of the Dummy Unit
private constant player OWNER = Player(14)
//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)
call ShowDummy(bj_lastCreatedUnit, true)
//! 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 ShowDummy takes unit u, boolean flag returns nothing
if IsUnitHidden(u) == flag then
call ShowUnit(u, flag)
if flag and GetUnitTypeId(u) == DUMMY_ID then
call UnitRemoveAbility(u, 'Aloc')
call UnitAddAbility(u, 'Aloc')
endif
endif
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 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 AutoFly initializer onInit requires UnitDex
/***************************************************************
*
* v1.0, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* AutoFly adds & removes crow of 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 Alloc /* v1.3.1.1
*************************************************************************************
*
* */ uses /*
*
* */ optional ErrorMessage /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
* */ optional MemoryAnalysis /*
*
*************************************************************************************
*
* Minimizes code generation and global variables while maintaining
* excellent performance.
*
* local thistype this = recycler[0]
*
* if (recycler[this] == 0) then
* set recycler[0] = this + 1
* else
* set recycler[0] = recycler[this]
* endif
*
************************************************************************************
*
* module Alloc
*
* static method allocate takes nothing returns thistype
* method deallocate takes nothing returns nothing
*
* The Following Require Error Message To Be In The Map
* --------------------------------------------------------
*
* debug readonly boolean allocated
*
* The Following Require Memory Analysis To Be In The Map
* --------------------------------------------------------
*
* debug readonly integer monitorCount
* - the amount of global memory being monitored by this
* debug readonly integer monitorString
* - gets a string representation of all global memory being monitored by this
* debug readonly integer address
* - global memory address for debugging
* - used with monitor and stopMonitor
*
* debug static method calculateMemoryUsage takes nothing returns integer
* debug static method getAllocatedMemoryAsString takes nothing returns string
*
* debug method monitor takes string label, integer address returns nothing
* - monitor a global memory address with a label
* - used to identify memory leaks
* - should be memory that ought to be destroyed by the time this is destroyed
* debug method stopMonitor takes integer address returns nothing
* - stops monitoring global memory
* debug method stopMonitorValue takes handle monitoredHandle returns nothing
* - stops monitoring handle values
*
* The Following Are Used To Monitor Handle Values
*
* debug method monitor_widget takes string label, widget handleToTrack returns nothing
* debug method monitor_destructable takes string label, destructable handleToTrack returns nothing
* debug method monitor_item takes string label, item handleToTrack returns nothing
* debug method monitor_unit takes string label, unit handleToTrack returns nothing
* debug method monitor_timer takes string label, timer handleToTrack returns nothing
* debug method monitor_trigger takes string label, trigger handleToTrack returns nothing
* debug method monitor_triggercondition takes string label, triggercondition handleToTrack returns nothing
* debug method monitor_triggeraction takes string label, triggeraction handleToTrack returns nothing
* debug method monitor_force takes string label, force handleToTrack returns nothing
* debug method monitor_group takes string label, group handleToTrack returns nothing
* debug method monitor_location takes string label, location handleToTrack returns nothing
* debug method monitor_rect takes string label, rect handleToTrack returns nothing
* debug method monitor_boolexpr takes string label, boolexpr handleToTrack returns nothing
* debug method monitor_effect takes string label, effect handleToTrack returns nothing
* debug method monitor_unitpool takes string label, unitpool handleToTrack returns nothing
* debug method monitor_itempool takes string label, itempool handleToTrack returns nothing
* debug method monitor_quest takes string label, quest handleToTrack returns nothing
* debug method monitor_defeatcondition takes string label, defeatcondition handleToTrack returns nothing
* debug method monitor_timerdialog takes string label, timerdialog handleToTrack returns nothing
* debug method monitor_leaderboard takes string label, leaderboard handleToTrack returns nothing
* debug method monitor_multiboard takes string label, multiboard handleToTrack returns nothing
* debug method monitor_multiboarditem takes string label, multiboarditem handleToTrack returns nothing
* debug method monitor_dialog takes string label, dialog handleToTrack returns nothing
* debug method monitor_button takes string label, button handleToTrack returns nothing
* debug method monitor_texttag takes string label, texttag handleToTrack returns nothing
* debug method monitor_lightning takes string label, lightning handleToTrack returns nothing
* debug method monitor_image takes string label, image handleToTrack returns nothing
* debug method monitor_ubersplat takes string label, ubersplat handleToTrack returns nothing
* debug method monitor_region takes string label, region handleToTrack returns nothing
* debug method monitor_fogmodifier takes string label, fogmodifier handleToTrack returns nothing
* debug method monitor_hashtable takes string label, hashtable handleToTrack returns nothing
*
************************************************************************************/
module Alloc
/*
* stack
*/
private static integer array recycler
static if LIBRARY_MemoryAnalysis then
debug private MemoryMonitor globalAddress
debug method operator address takes nothing returns integer
debug call ThrowError(recycler[this] != -1, "Alloc", "address", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress
debug endmethod
endif
/*
* allocation
*/
static method allocate takes nothing returns thistype
local thistype this = recycler[0]
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 8192, "Alloc", "allocate", "thistype", 0, "Overflow.")
endif
if (recycler[this] == 0) then
set recycler[0] = this + 1
else
set recycler[0] = recycler[this]
endif
static if LIBRARY_ErrorMessage then
debug set recycler[this] = -1
endif
static if LIBRARY_MemoryAnalysis then
debug set globalAddress = MemoryMonitor.allocate("thistype")
endif
return this
endmethod
method deallocate takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(recycler[this] != -1, "Alloc", "deallocate", "thistype", this, "Attempted To Deallocate Null Instance.")
endif
static if LIBRARY_MemoryAnalysis then
debug call globalAddress.deallocate()
debug set globalAddress = 0
endif
set recycler[this] = recycler[0]
set recycler[0] = this
endmethod
static if LIBRARY_MemoryAnalysis then
debug method monitor takes string label, integer address returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor(label, address)
debug endmethod
debug method stopMonitor takes integer address returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "stopMonitor", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.stopMonitor(address)
debug endmethod
debug method stopMonitorValue takes handle monitoredHandle returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "stopMonitorValue", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.stopMonitorValue(monitoredHandle)
debug endmethod
debug method operator monitorCount takes nothing returns integer
debug call ThrowError(recycler[this] != -1, "Alloc", "monitorCount", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress.monitorCount
debug endmethod
debug method operator monitorString takes nothing returns string
debug call ThrowError(recycler[this] != -1, "Alloc", "monitorString", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress.monitorString
debug endmethod
debug method monitor_widget takes string label, widget handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_widget", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_widget(label, handleToTrack)
debug endmethod
debug method monitor_destructable takes string label, destructable handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_destructable", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_destructable(label, handleToTrack)
debug endmethod
debug method monitor_item takes string label, item handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_item", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_item(label, handleToTrack)
debug endmethod
debug method monitor_unit takes string label, unit handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_unit", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_unit(label, handleToTrack)
debug endmethod
debug method monitor_timer takes string label, timer handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_timer", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_timer(label, handleToTrack)
debug endmethod
debug method monitor_trigger takes string label, trigger handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_trigger", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_trigger(label, handleToTrack)
debug endmethod
debug method monitor_triggercondition takes string label, triggercondition handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_triggercondition", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_triggercondition(label, handleToTrack)
debug endmethod
debug method monitor_triggeraction takes string label, triggeraction handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_triggeraction", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_triggeraction(label, handleToTrack)
debug endmethod
debug method monitor_force takes string label, force handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_force", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_force(label, handleToTrack)
debug endmethod
debug method monitor_group takes string label, group handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_group", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_group(label, handleToTrack)
debug endmethod
debug method monitor_location takes string label, location handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_location", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_location(label, handleToTrack)
debug endmethod
debug method monitor_rect takes string label, rect handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_rect", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_rect(label, handleToTrack)
debug endmethod
debug method monitor_boolexpr takes string label, boolexpr handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_boolexpr", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_boolexpr(label, handleToTrack)
debug endmethod
debug method monitor_effect takes string label, effect handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_effect", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_effect(label, handleToTrack)
debug endmethod
debug method monitor_unitpool takes string label, unitpool handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_unitpool", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_unitpool(label, handleToTrack)
debug endmethod
debug method monitor_itempool takes string label, itempool handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_itempool", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_itempool(label, handleToTrack)
debug endmethod
debug method monitor_quest takes string label, quest handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_quest", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_quest(label, handleToTrack)
debug endmethod
debug method monitor_defeatcondition takes string label, defeatcondition handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_defeatcondition", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_defeatcondition(label, handleToTrack)
debug endmethod
debug method monitor_timerdialog takes string label, timerdialog handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_timerdialog", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_timerdialog(label, handleToTrack)
debug endmethod
debug method monitor_leaderboard takes string label, leaderboard handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_leaderboard", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_leaderboard(label, handleToTrack)
debug endmethod
debug method monitor_multiboard takes string label, multiboard handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_multiboard", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_multiboard(label, handleToTrack)
debug endmethod
debug method monitor_multiboarditem takes string label, multiboarditem handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_multiboarditem", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_multiboarditem(label, handleToTrack)
debug endmethod
debug method monitor_dialog takes string label, dialog handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_dialog", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_dialog(label, handleToTrack)
debug endmethod
debug method monitor_button takes string label, button handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_button", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_button(label, handleToTrack)
debug endmethod
debug method monitor_texttag takes string label, texttag handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_texttag", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_texttag(label, handleToTrack)
debug endmethod
debug method monitor_lightning takes string label, lightning handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_lightning", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_lightning(label, handleToTrack)
debug endmethod
debug method monitor_image takes string label, image handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_image", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_image(label, handleToTrack)
debug endmethod
debug method monitor_ubersplat takes string label, ubersplat handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_ubersplat", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_ubersplat(label, handleToTrack)
debug endmethod
debug method monitor_region takes string label, region handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_region", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_region(label, handleToTrack)
debug endmethod
debug method monitor_fogmodifier takes string label, fogmodifier handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_fogmodifier", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_fogmodifier(label, handleToTrack)
debug endmethod
debug method monitor_hashtable takes string label, hashtable handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_hashtable", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_hashtable(label, handleToTrack)
debug endmethod
static if DEBUG_MODE then
//! runtextmacro optional MEMORY_ANALYSIS_STATIC_FIELD_NEW("recycler")
static method calculateMemoryUsage takes nothing returns integer
return calculateAllocatedMemory__recycler()
endmethod
static method getAllocatedMemoryAsString takes nothing returns string
return allocatedMemoryString__recycler()
endmethod
endif
endif
/*
* analysis
*/
static if LIBRARY_ErrorMessage then
debug method operator allocated takes nothing returns boolean
debug return recycler[this] == -1
debug endmethod
endif
/*
* initialization
*/
private static method onInit takes nothing returns nothing
set recycler[0] = 1
endmethod
endmodule
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 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-opem 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 = thistype(0).prev.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
else
if this.dur < dur then
set this.dur = dur
endif
endif
endmethod
implement Init
endstruct
endlibrary
/*****************************************************************************
*
* RegisterNativeEvent v1.1.1.3
* by Bannar
*
* Storage of trigger handles for native events.
*
******************************************************************************
*
* Optional requirements:
*
* Table by Bribe
* hiveworkshop.com/threads/snippet-new-table.188084/
*
******************************************************************************
*
* Important:
*
* Avoid using TriggerSleepAction within functions registered.
* Destroy native event trigger on your own responsibility.
*
******************************************************************************
*
* Core:
*
* function IsNativeEventRegistered takes integer whichIndex, integer whichEvent returns boolean
* Whether index whichIndex has already been attached to event whichEvent.
*
* function RegisterNativeEventTrigger takes integer whichIndex, integer eventId returns boolean
* Registers whichIndex within whichEvent scope and assigns new trigger handle for it.
*
* function GetIndexNativeEventTrigger takes integer whichIndex, integer whichEvent returns trigger
* Retrieves trigger handle for event whichEvent specific to provided index whichIndex.
*
* function GetNativeEventTrigger takes integer whichEvent returns trigger
* Retrieves trigger handle for event whichEvent.
*
*
* Custom events:
*
* function CreateNativeEvent takes nothing returns integer
* Returns unique id for new event and registers it with RegisterNativeEvent.
*
* function RegisterIndexNativeEvent takes integer whichIndex, integer whichEvent, code func returns nothing
* Registers new event handler func for event whichEvent specific to index whichIndex.
*
* function RegisterNativeEvent takes integer whichEvent, code func returns nothing
* Registers new event handler func for specified event whichEvent.
*
*****************************************************************************/
library RegisterNativeEvent uses optional Table
globals
private integer eventIndex = 500 // 0-499 reserved for Blizzard native events
endglobals
private module NativeEventInit
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set table = TableArray[0x2000]
endif
endmethod
endmodule
private struct NativeEvent extends array
static if LIBRARY_Table then
static TableArray table
else
static hashtable table = InitHashtable()
endif
implement NativeEventInit
endstruct
function IsNativeEventRegistered takes integer whichIndex, integer whichEvent returns boolean
static if LIBRARY_Table then
return NativeEvent.table[whichEvent].trigger.has(whichIndex)
else
return HaveSavedHandle(NativeEvent.table, whichEvent, whichIndex)
endif
endfunction
function RegisterNativeEventTrigger takes integer whichIndex, integer whichEvent returns boolean
if not IsNativeEventRegistered(whichIndex, whichEvent) then
static if LIBRARY_Table then
set NativeEvent.table[whichEvent].trigger[whichIndex] = CreateTrigger()
else
call SaveTriggerHandle(NativeEvent.table, whichEvent, whichIndex, CreateTrigger())
endif
return true
endif
return false
endfunction
function GetIndexNativeEventTrigger takes integer whichIndex, integer whichEvent returns trigger
static if LIBRARY_Table then
return NativeEvent.table[whichEvent].trigger[whichIndex]
else
return LoadTriggerHandle(NativeEvent.table, whichEvent, whichIndex)
endif
endfunction
function GetNativeEventTrigger takes integer whichEvent returns trigger
return GetIndexNativeEventTrigger(bj_MAX_PLAYER_SLOTS, whichEvent)
endfunction
function CreateNativeEvent takes nothing returns integer
local integer eventId = eventIndex
call RegisterNativeEventTrigger(bj_MAX_PLAYER_SLOTS, eventId)
set eventIndex = eventIndex + 1
return eventId
endfunction
function RegisterIndexNativeEvent takes integer whichIndex, integer whichEvent, code func returns nothing
call RegisterNativeEventTrigger(whichIndex, whichEvent)
call TriggerAddCondition(GetIndexNativeEventTrigger(whichIndex, whichEvent), Condition(func))
endfunction
function RegisterNativeEvent takes integer whichEvent, code func returns nothing
call RegisterIndexNativeEvent(bj_MAX_PLAYER_SLOTS, whichEvent, func)
endfunction
endlibrary
/*****************************************************************************
*
* RegisterPlayerUnitEvent v1.0.3.2
* by Bannar
*
* Register version of TriggerRegisterPlayerUnitEvent.
*
* Special thanks to Magtheridon96, Bribe, azlier and BBQ for the original library version.
*
******************************************************************************
*
* Requirements:
*
* RegisterNativeEvent by Bannar
* hiveworkshop.com/threads/snippet-registerevent-pack.250266/
*
******************************************************************************
*
* Functions:
*
* function GetAnyPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* Retrieves trigger handle for playerunitevent whichEvent.
*
* function GetPlayerUnitEventTrigger takes player whichPlayer, playerunitevent whichEvent returns trigger
* Retrieves trigger handle for playerunitevent whichEvent specific to player whichPlayer.
*
* function RegisterAnyPlayerUnitEvent takes playerunitevent whichEvent, code func returns nothing
* Registers generic playerunitevent whichEvent adding code func as callback.
*
* function RegisterPlayerUnitEvent takes player whichPlayer, playerunitevent whichEvent, code func returns nothing
* Registers playerunitevent whichEvent for player whichPlayer adding code func as callback.
*
*****************************************************************************/
library RegisterPlayerUnitEvent requires RegisterNativeEvent
function GetAnyPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
return GetNativeEventTrigger(GetHandleId(whichEvent))
endfunction
function GetPlayerUnitEventTrigger takes player whichPlayer, playerunitevent whichEvent returns trigger
return GetIndexNativeEventTrigger(GetPlayerId(whichPlayer), GetHandleId(whichEvent))
endfunction
function RegisterAnyPlayerUnitEvent takes playerunitevent whichEvent, code func returns nothing
local integer eventId = GetHandleId(whichEvent)
local integer index = 0
local trigger t = null
if RegisterNativeEventTrigger(bj_MAX_PLAYER_SLOTS, eventId) then
set t = GetNativeEventTrigger(eventId)
loop
call TriggerRegisterPlayerUnitEvent(t, Player(index), whichEvent, null)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
set t = null
endif
call RegisterNativeEvent(eventId, func)
endfunction
function RegisterPlayerUnitEvent takes player whichPlayer, playerunitevent whichEvent, code func returns nothing
local integer playerId = GetPlayerId(whichPlayer)
local integer eventId = GetHandleId(whichEvent)
if RegisterNativeEventTrigger(playerId, eventId) then
call TriggerRegisterPlayerUnitEvent(GetIndexNativeEventTrigger(playerId, eventId), whichPlayer, whichEvent, null)
endif
call RegisterIndexNativeEvent(playerId, eventId, func)
endfunction
endlibrary