Name | Type | is_array | initial_value |
EntropyCastPoint | location | No | |
LivingMetalPoint | location | No | |
SoulSpreadPoint | location | No | |
SurprisePoint | location | No |
library Alloc /* v1.0.0 https://www.hiveworkshop.com/threads/324937/
*/uses /*
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage
*///! novjass
/*
Coded by AGD, based on MyPad's allocation algorithm
A allocator module using a single global indexed stack. Allocated values are
within the JASS_MAX_ARRAY_SIZE. No more need to worry about code bloats behind
the module implementation as it generates least code as possible (six lines of
code in non-DEBUG_MODE), nor does it use an initialization function.
This system also only uses ONE variable (for the whole map) for the hashtable.
*/
|-----|
| API |
|-----|
/*
*/module Alloc/*
*/debug readonly boolean allocated/* Is node allocated?
*/static method allocate takes nothing returns thistype/*
*/method deallocate takes nothing returns nothing/*
*///! endnovjass
/*===========================================================================*/
globals
private hashtable table = InitHashtable()
endglobals
static if DEBUG_MODE then
private function AssertError takes boolean condition, string methodName, integer structTypeId, integer node, string message returns nothing
static if LIBRARY_ErrorMessage then
call ThrowError(condition, SCOPE_PREFIX, methodName, LoadStr(table, 0, structTypeId), node, message)
else
if condition then
call BJDebugMsg("[Library: " + SCOPE_PREFIX + "] [Struct: " + LoadStr(table, 0, structTypeId) + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffff0000" + message + "|r")
endif
endif
endfunction
private function IsAllocated takes integer typeId, integer node returns boolean
return node > 0 and LoadInteger(table, typeId, node) == 0
endfunction
endif
private function Allocate takes integer typeId returns integer
local integer node = LoadInteger(table, typeId, 0)
local integer stackNext = LoadInteger(table, typeId, node)
if stackNext == 0 then
debug call AssertError(node == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", typeId, node, "Overflow")
set node = node + 1
call SaveInteger(table, typeId, 0, node)
else
call SaveInteger(table, typeId, 0, stackNext)
call SaveInteger(table, typeId, node, 0)
endif
return node
endfunction
private function Deallocate takes integer typeId, integer node returns nothing
debug call AssertError(node == 0, "deallocate()", typeId, 0, "Null node")
debug call AssertError(LoadInteger(table, typeId, node) > 0, "deallocate()", typeId, node, "Double-free")
call SaveInteger(table, typeId, node, LoadInteger(table, typeId, 0))
call SaveInteger(table, typeId, 0, node)
endfunction
module Alloc
debug method operator allocated takes nothing returns boolean
debug return IsAllocated(thistype.typeid, this)
debug endmethod
static method allocate takes nothing returns thistype
return Allocate(thistype.typeid)
endmethod
method deallocate takes nothing returns nothing
call Deallocate(thistype.typeid, this)
endmethod
debug private static method onInit takes nothing returns nothing
debug call SaveStr(table, 0, thistype.typeid, "thistype")
debug endmethod
endmodule
endlibrary
library Buff /*
Buff v1.30
by Flux
Handles all interactions of self-defined buffs.
Features:
- Can dispel positive/negative/both/all buffs.
- Supports 3 types of buff stacking.
- Buffs with duration.
- Pick all Buffs of a unit easily.
*/ requires /*
(nothing)
*/ optional TimerUtils /*
*/ optional RegisterPlayerUnitEvent /*
******************
CREDITS
******************
muzzel - For BuffHandler which this resource is heavily based upon.
Vexorian - For the optional TimerUtils.
Magtheridon96 - For the optional RegisterPlayerUnitEvent.
*/
globals
//-----------------------------//
//--------- BUFF TYPES --------//
//-----------------------------//
constant integer BUFF_NONE = 0
constant integer BUFF_POSITIVE = 1
constant integer BUFF_NEGATIVE = 2
//-----------------------------//
//------ BUFF STACK TYPES -----//
//-----------------------------//
//Applying the same buff only refreshes the duration
//If the buff is reapplied but from a different source, the Buff unit source gets replaced.
constant integer BUFF_STACK_NONE = 0
//Each buff from different source stacks.
//Re-applying the same buff from the same source only refreshes the duration
constant integer BUFF_STACK_PARTIAL = 1
//Each buff applied fully stacks.
constant integer BUFF_STACK_FULL = 2
//Determines the automatic Buff rawcode based on the Ability rawcode
//If BUFF_OFFSET = 0x01000000, then Ability rawcode of 'AXXX' will have Buff rawcode of 'BXXX'
//If BUFF_OFFSET = 0x20000000, then Ability rawcode of 'AXXX' will have Buff rawcode of 'aXXX'
private constant integer BUFF_OFFSET = 0x01000000
//Automatically Preloads all Buff abilities defined in "method rawcode"
//but will generate a lot of scripts in the process
private constant boolean PRELOAD_BUFFS = true
//Automatically initialize a Buff type.
//If false, initialize it using <MyBuff>.initialize()
private constant boolean AUTO_INITIALIZE = true
endglobals
struct Buff
//Buff properties
readonly boolean exist
readonly unit target
readonly unit source
readonly integer rawcode
readonly integer buffId
readonly integer stackType
readonly integer dispelType
//For duration
private timer t
//Buff Enumeration
private thistype bnext
private thistype bprev
//Events
private static thistype callback
private static trigger array onApply
private static trigger array onRemove
method operator name takes nothing returns string
return GetObjectName(this.rawcode)
endmethod
private static hashtable hash = InitHashtable()
//===============================================================
//======================== BUFF CORE ============================
//===============================================================
static method get takes unit source, unit target, integer typeid returns thistype
local integer id = GetHandleId(target)
local thistype this
local thistype head
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this = head
loop
if this.getType() == typeid and (source == null or source == this.source) then
return this
endif
exitwhen this == head.bprev
set this = this.bnext
endloop
endif
return 0
endmethod
static method has takes unit source, unit target, integer typeid returns boolean
return thistype.get(source, target, typeid) > 0
endmethod
method remove takes nothing returns nothing
local boolean remove = false
local integer id
local thistype head
local integer count
if this.exist then
set id = GetHandleId(this.target)
set thistype.callback = this
call TriggerEvaluate(thistype.onRemove[this.getType()])
if this.t != null then
static if LIBRARY_TimerUtils then
call ReleaseTimer(this.t)
else
call RemoveSavedInteger(thistype.hash, GetHandleId(this.t), 0)
call DestroyTimer(this.t)
endif
set this.t = null
endif
if this.stackType == BUFF_STACK_FULL or this.stackType == BUFF_STACK_PARTIAL then
//Update Buff count
set count = LoadInteger(thistype.hash, this.getType(), id) - 1
call SaveInteger(thistype.hash, this.getType(), id, count)
if count == 0 then
set remove = true
endif
elseif this.stackType == BUFF_STACK_NONE then
set remove = true
endif
if remove then
call UnitRemoveAbility(this.target, this.rawcode)
call UnitRemoveAbility(this.target, this.buffId)
endif
//Remove from the BuffList
set head = LoadInteger(thistype.hash, id, 0)
if this == head and this.bnext == head then //If this is the only Buff of the unit
call RemoveSavedInteger(thistype.hash, id, 0)
else
//If this is the head of the BuffList
if this == head then
//Change this unit's BuffList head
call SaveInteger(thistype.hash, id, 0, this.bnext)
endif
set this.bnext.bprev = this.bprev
set this.bprev.bnext = this.bnext
endif
set this.exist = false
set this.target = null
set this.source = null
call this.destroy()
debug else
debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "[Buff]: Attempted to remove non-existing Buff instance.")
endif
endmethod
private static method expires takes nothing returns nothing
static if LIBRARY_TimerUtils then
local thistype this = GetTimerData(GetExpiredTimer())
call ReleaseTimer(GetExpiredTimer())
else
local integer id = GetHandleId(GetExpiredTimer())
local thistype this = LoadInteger(thistype.hash, id, 0)
call RemoveSavedInteger(thistype.hash, id, 0)
call DestroyTimer(GetExpiredTimer())
endif
if this.t != null then
set this.t = null
call this.remove()
endif
endmethod
method operator duration takes nothing returns real
if this.t != null then
return TimerGetRemaining(this.t)
endif
return 0.0
endmethod
method operator duration= takes real time returns nothing
if this.t == null then
static if LIBRARY_TimerUtils then
set this.t = NewTimerEx(this)
else
set this.t = CreateTimer()
call SaveInteger(thistype.hash, GetHandleId(this.t), 0, this)
endif
endif
call TimerStart(this.t, time, false, function thistype.expires)
endmethod
method check takes unit source, unit target returns thistype
local boolean apply = false
local integer id = GetHandleId(target)
local thistype head
local thistype temp
static if not LIBRARY_TimerUtils then
local timer t
endif
if this.stackType == BUFF_STACK_FULL then
//Count how many buffs are stored in a certain unit
call SaveInteger(thistype.hash, this.getType(), id, LoadInteger(thistype.hash, this.getType(), id) + 1)
set apply = true
elseif this.stackType == BUFF_STACK_PARTIAL then
//Check if a similar buff type with the same target and source exist
set temp = thistype.get(source, target, this.getType())
if temp == 0 then //None is found
set apply = true
//Count how many buffs of this type are stored in this certain unit
call SaveInteger(thistype.hash, this.getType(), id, LoadInteger(thistype.hash, this.getType(), id) + 1)
else //Buff is found, use the previous Buff as the newly applied Buff
call this.destroy()
set this = temp
endif
elseif this.stackType == BUFF_STACK_NONE then
//Check if a similar buff type with the same target exist
set temp = thistype.get(null, target, this.getType())
if temp == 0 then //None is found
set apply = true
else //Buff is found, use the previous Buff as the newly applied Buff
call this.destroy()
set this = temp
endif
endif
set this.source = source
set this.target = target
set this.exist = true
set this.buffId = this.rawcode + BUFF_OFFSET
if apply then
if GetUnitAbilityLevel(target, this.rawcode) == 0 then
call UnitAddAbility(target, this.rawcode)
call UnitMakeAbilityPermanent(target, true, this.rawcode)
endif
//Add the Buff to a BuffList of this unit
//If BuffList already exist
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this.bnext = head
set this.bprev = head.bprev
set this.bnext.bprev = this
set this.bprev.bnext = this
else
//Set this as the unit's BuffList head
call SaveInteger(thistype.hash, id, 0, this)
set this.bnext = this
set this.bprev = this
endif
set thistype.callback = this
call TriggerEvaluate(thistype.onApply[this.getType()])
endif
static if LIBRARY_BuffEvent then
static if LIBRARY_TimerUtils then
call TimerStart(NewTimerEx(this), 0.0, false, function BuffEvent.pickAll)
else
set t = CreateTimer()
call SaveInteger(thistype.hash, GetHandleId(t), 0, this)
call TimerStart(t, 0.0, false, function BuffEvent.pickAll)
endif
endif
return this
endmethod
//===============================================================
//======================== BUFF ENUM ============================
//===============================================================
readonly static thistype buffHead
readonly static thistype picked
static method pickBuffs takes unit u returns nothing
local integer id = GetHandleId(u)
if HaveSavedInteger(thistype.hash, id, 0) then
set thistype.buffHead = LoadInteger(thistype.hash, id, 0)
else
set thistype.buffHead = 0
endif
endmethod
//===============================================================
//======================= BUFF DISPEL ===========================
//===============================================================
static method dispel takes unit u, integer dispelType returns nothing
local integer id = GetHandleId(u)
local thistype head
local thistype this
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this = head.bnext
loop
if this.dispelType == dispelType then
call this.remove()
endif
exitwhen this == head
set this = this.bnext
endloop
endif
endmethod
static method dispelBoth takes unit u returns nothing
local integer id = GetHandleId(u)
local thistype head
local thistype this
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this = head.bnext
loop
if this.dispelType == BUFF_POSITIVE or this.dispelType == BUFF_NEGATIVE then
call this.remove()
endif
exitwhen this == head
set this = this.bnext
endloop
endif
endmethod
static method dispelAll takes unit u returns nothing
local integer id = GetHandleId(u)
local thistype head
local thistype this
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this = head.bnext
loop
call this.remove()
exitwhen this == head
set this = this.bnext
endloop
endif
endmethod
private static method onDeath takes nothing returns nothing
call thistype.dispelAll(GetTriggerUnit())
endmethod
implement optional BuffInit
private static method onInit takes nothing returns nothing
static if LIBRARY_RegisterPlayerUnitEvent then
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
else
local trigger core = CreateTrigger()
local code death = function thistype.onDeath
call TriggerRegisterAnyUnitEventBJ(core, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(core, Condition(death))
endif
endmethod
endstruct
static if PRELOAD_BUFFS then
module BuffInit
readonly static unit preloader
private static method onInit takes nothing returns nothing
set thistype.preloader = CreateUnit(Player(14), 'ushd', GetRectMaxX(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea), 0)
call UnitApplyTimedLife(thistype.preloader, 'BTLF', 1.0)
endmethod
endmodule
endif
module BuffApply
static method add takes unit source, unit target returns thistype
local thistype this = thistype.create()
//Write into readonly attributes
set s__Buff_rawcode[this] = thistype.RAWCODE
set s__Buff_stackType[this] = thistype.STACK_TYPE
set s__Buff_dispelType[this] = thistype.DISPEL_TYPE
set this = this.check(source, target)
return this
endmethod
private static method onApplyInit takes nothing returns boolean
call thistype(s__Buff_callback).onApply()
return false
endmethod
private static method onRemoveInit takes nothing returns boolean
call thistype(s__Buff_callback).onRemove()
return false
endmethod
static method initialize takes nothing returns nothing
static if thistype.onApply.exists then
set s__Buff_onApply[thistype.typeid] = CreateTrigger()
call TriggerAddCondition(s__Buff_onApply[thistype.typeid], function thistype.onApplyInit)
endif
static if thistype.onRemove.exists then
set s__Buff_onRemove[thistype.typeid] = CreateTrigger()
call TriggerAddCondition(s__Buff_onRemove[thistype.typeid], function thistype.onRemoveInit)
endif
endmethod
static if PRELOAD_BUFFS then
private static method onInit takes nothing returns nothing
local thistype this = thistype.create()
call UnitAddAbility(Buff.preloader, thistype.RAWCODE)
call UnitRemoveAbility(Buff.preloader, thistype.RAWCODE)
call this.destroy()
static if AUTO_INITIALIZE then
call thistype.initialize()
endif
endmethod
elseif AUTO_INITIALIZE then
private static method onInit takes nothing returns nothing
call thistype.initialize()
endmethod
endif
endmodule
module BuffListStart
if Buff.buffHead > 0 then
set s__Buff_picked = s__Buff_buffHead
loop
endmodule
module BuffListEnd
exitwhen Buff.picked == s__Buff_bprev[s__Buff_buffHead]
set s__Buff_picked = s__Buff_bnext[s__Buff_picked]
endloop
endif
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 SpellEvent /* v2.0.3.0 https://www.hiveworkshop.com/threads/301895/
*/uses /*
*/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
*///! novjass
/*
A library that eases and expands the possibilities of custom spells development.
Core Features:
1. Two-phase spell event handlers
2. Manual spell event invocation
3. Event parameters overriding and event cancelling
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, ...) 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.) As mentioned in no. 1, within the generic event handlers, you can override the event parameters. The change
will only affect the ability-specific handlers. You can also stop the ability-specific handlers from running.
(known as event cancelling)
Note: Event cancelling is currently incompatible with Reforged. (crashes the game)
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 abilityId /*
*/readonly static integer eventType /*
*/readonly static integer orderType /*
*/readonly static integer level /*
*/readonly static player triggerPlayer /*
*/readonly static unit triggerUnit /*
*/readonly static unit targetUnit /* Fixed a mistake in the native event responses where target is set to caster for 'No-target' abilities based on channel (Is set to null instead)
*/readonly static item targetItem /*
*/readonly static destructable targetDest /*
*/readonly static real targetX /* Returns the x-coordinate of the caster if the spell is a 'No-target' ability
*/readonly static real targetY /* Returns the y-coordinate of the caster if the spell is a 'No-target' ability
Fields:
*/readonly integer abilId/*
- Rawcode of the activation ability for the spell
*/boolean handlersDisabled/*
- (incompatible with Reforged versions - crashes the game)
- Value is always reset to false before running the generic spell handlers
- Set to <false> to increment the internal disabler counter or <true> to decrement counter
- If counter > 0, the ability-specific handlers won't run
Methods:
*/static method operator [] takes integer abilId 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 invokeNoTargetEvent takes integer eventType, integer level, unit caster returns nothing/*
*/method invokePointTargetEvent takes integer eventType, integer level, unit caster, real targetX, real targetY returns nothing/*
*/method invokeSingleTargetEvent takes integer eventType, integer level, unit caster, widget target returns nothing/*
- Manually invokes 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_SINGLE_TARGET/*
*/constant integer SPELL_ORDER_TYPE_POINT_TARGET/*
*/constant integer SPELL_ORDER_TYPE_NO_TARGET/*
*/
|===========|
| 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 GetEventSpellPlayer 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 SpellCancelEventHandlers takes boolean cancel returns nothing/*
- This function is incompatible with Reforged versions
*/function SpellInvokeNoTargetEvent takes integer abilId, integer eventType, integer level, unit caster returns nothing/*
*/function SpellInvokePointTargetEvent takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY returns nothing/*
*/function SpellInvokeSingleTargetEvent 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 abilId, integer eventType, code handler returns nothing/*
*/function SpellUnregisterEventHandler takes integer abilId, integer eventType, code handler returns nothing/*
*/function SpellClearEventHandlers takes integer abilId, integer eventType returns nothing/*
*/function SpellClearHandlers takes integer abilId 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 (per struct) 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. But if you insist in
using this, only do so for manually inserting nodes. Only do it if really needed
Public methods:
*/static method registerSpellEvent takes integer abilId, integer eventType returns nothing/*
- Manually registers an ability rawcode to trigger spell events
- Can be used for spells that involve more than one activation ability IDs
Member interfaces:
- Should be declared above the module implementation
*/interface static integer SPELL_ABILITY_ID /* Ability rawcode
*/interface static integer SPELL_EVENT_TYPE /* Spell event type
*/interface static real SPELL_PERIOD /* Spell periodic actions execution period
*/interface 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 different value or transmute 'this', provided that all your nodes/values
comes from the same node/value stack. Also remember to always deallocate what you manually
allocated.
- The value returned will be added to the list of instances that will run onSpellPeriodic()
*/optional interface method onSpellPeriodic takes nothing returns boolean/*
- Runs periodically after the spell event fires until it returns false
*/optional interface method onSpellEnd takes nothing returns nothing/*
- Runs after method onSpellPeriodic() returns false
- If onSpellPeriodic() is not present, this will be called after onSpellStart() returns a valid instance
*/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 abilId, 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
*/interface static integer SPELL_ABILITY_ID /* Ability rawcode
*/interface static integer SPELL_EVENT_TYPE /* Spell event type
*/interface static real SPELL_PERIOD /* Spell periodic actions execution period
*/interface 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
*/optional interface method onSpellPeriodic takes nothing returns boolean/*
- Runs periodically after the spell event fires until it returns false
*/optional interface method onSpellEnd takes nothing returns nothing/*
- Runs after method onSpellPeriodic() returns false
- If onSpellPeriodic() is not present, this will be called after onSpellStart() returns a valid instance
- User must manually deallocate the spell instance inside this method
*/module SpellEventGeneric/*
Member interfaces:
- Should be declared above the module implementation
*/optional interface static method onSpellEvent takes nothing returns nothing/*
- Runs on any generic spell event
*/optional interface static method onSpellCast takes nothing returns nothing/*
*/optional interface static method onSpellChannel takes nothing returns nothing/*
*/optional interface static method onSpellEffect takes nothing returns nothing/*
*/optional interface static method onSpellEndcast takes nothing returns nothing/*
*/optional interface static method onSpellFinish takes nothing returns nothing/*
- Runs on certain generic 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_SINGLE_TARGET = 0x12
constant integer SPELL_ORDER_TYPE_POINT_TARGET = 0x123
constant integer SPELL_ORDER_TYPE_NO_TARGET = 0x1234
endglobals
globals
private integer eventAbilityId = 0
private integer eventEventType = 0
private integer eventOrderType = 0
private integer eventLevel = 0
private player eventTriggerPlayer = null
private unit eventTriggerUnit = null
private unit eventTargetUnit = null
private item eventTargetItem = null
private destructable eventTargetDest = null
private real eventTargetX = 0.00
private real eventTargetY = 0.00
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 TableArray table
private integer array eventTypeId
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 function OnOverrideParams takes nothing returns nothing
set eventOrderType = tempOrderType
set eventLevel = tempLevel
set eventTriggerPlayer = GetOwningPlayer(tempTriggerUnit)
set eventTriggerUnit = tempTriggerUnit
set eventTargetX = tempTargetX
set eventTargetY = tempTargetY
if tempTarget == null then
set eventTargetUnit = null
set eventTargetItem = null
set eventTargetDest = null
else
set table[0].widget[0] = tempTarget
set eventTargetUnit = table[0].unit[0]
set eventTargetItem = table[0].item[0]
set eventTargetDest = table[0].destructable[0]
call table[0].handle.remove(0)
endif
set tempOrderType = 0
set tempLevel = 0
set tempTriggerUnit = null
set tempTargetX = 0.00
set tempTargetY = 0.00
set tempTarget = null
endfunction
/*===================================================================================*/
/*
* One Allocator for the whole library. 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 module List
readonly thistype prev
readonly thistype next
method makeHead takes nothing returns nothing
set this.prev = this
set 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 delete takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
set this.handle = null
call Node(this).deallocate()
endmethod
endmodule
private struct ConditionList extends array
triggercondition handle
implement List
endstruct
private struct ExprList extends array
boolexpr handle
implement List
method operator conditionList takes nothing returns ConditionList
return this
endmethod
method operator empty takes nothing returns boolean
return this.next == this
endmethod
method insertExpr takes boolexpr expr returns thistype
local thistype node = Node.allocate()
set node.handle = expr
call this.insert(node)
return node
endmethod
endstruct
private struct Handler extends array
readonly trigger trigger
boolean overrideParams
integer disablerCounter
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
/*
* This method is registered in position after all the generic handlers and before the
* ability-specific handlers. Its position allows it to unlink all the ability-specific
* handlers positioned after it or to change the event parameters, should the user goto
*/
private static method bridge takes nothing returns nothing
local integer triggerId = GetHandleId(GetTriggeringTrigger())
local thistype node = table[0][triggerId]
local trigger tempTrig
if node.disablerCounter > 0 then
if node.exprList.next != node.exprList then
set tempTrig = node.trigger
set node.trigger = CreateTrigger()
set table[0][GetHandleId(node.trigger)] = node
call node.updateHandlers()
call table[0].remove(triggerId)
call TriggerClearConditions(tempTrig)
call DestroyTrigger(tempTrig)
set tempTrig = null
endif
return
endif
if node.overrideParams and node.exprList.next != node.exprList then
call OnOverrideParams()
endif
endmethod
static method registerGeneric takes integer eventIndex, boolexpr expr returns nothing
local integer exprId = GetHandleId(expr)
local ExprList exprNode = table[genericList[eventIndex]][exprId]
local ConditionList conditionNode = Node.allocate()
if exprNode == 0 then
set exprNode = genericList[eventIndex].prev.prev.insertExpr(expr)
call exprNode.conditionList.makeHead()
set table[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 = table[genericList[eventIndex]][exprId]
local ConditionList conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
call conditionNode.delete()
set conditionNode = conditionNode.next
endloop
call table[genericList[eventIndex]].remove(exprId)
call exprNode.delete()
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 = table[this][exprId]
local ConditionList conditionNode = Node.allocate()
if this.exprList.empty then
set this.trigger = CreateTrigger()
set table[0][GetHandleId(this.trigger)] = this
endif
if exprNode == 0 then
set exprNode = this.exprList.prev.insertExpr(expr)
call exprNode.conditionList.makeHead()
set table[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 = table[this][exprId]
local ConditionList conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
call TriggerRemoveCondition(this.trigger, conditionNode.handle)
call conditionNode.delete()
set conditionNode = conditionNode.next
endloop
call exprNode.delete()
call table[this].remove(exprId)
if this.exprList.empty then
call table[0].remove(GetHandleId(this.trigger))
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
static method create takes integer eventIndex returns thistype
local thistype node = Node.allocate()
call ExprList(node).makeHead()
set node.index = eventIndex
set node.disablerCounter = 0
return node
endmethod
method destroy takes nothing returns nothing
call this.clear()
call Node(this).deallocate()
endmethod
debug static method hasGenericExpr takes integer eventIndex, boolexpr expr returns boolean
debug return table[genericList[eventIndex]][GetHandleId(expr)] != 0
debug endmethod
debug method hasExpr takes boolexpr expr returns boolean
debug return table[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 list = create(eventIndex)
local ExprList exprNode = list.insertExpr(bridgeExpr)
call exprNode.conditionList.makeHead()
call exprNode.conditionList.insert(Node.allocate())
set genericList[eventIndex] = list
endmethod
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.bridge
set bridgeExpr = Filter(bridgeFunc)
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 integer abilId
private static integer spellCount = 0
private static Node spellKey
private static Handler array eventHandler
static if not LIBRARY_ResourcePreloader then
private static unit preloadDummy
endif
static constant method operator abilityId takes nothing returns integer
return eventAbilityId
endmethod
static constant method operator eventType takes nothing returns integer
return eventEventType
endmethod
static constant method operator orderType takes nothing returns integer
return eventOrderType
endmethod
static constant method operator level takes nothing returns integer
return eventLevel
endmethod
static constant method operator triggerPlayer takes nothing returns player
return eventTriggerPlayer
endmethod
static constant method operator triggerUnit takes nothing returns unit
return eventTriggerUnit
endmethod
static constant method operator targetUnit takes nothing returns unit
return eventTargetUnit
endmethod
static constant method operator targetItem takes nothing returns item
return eventTargetItem
endmethod
static constant method operator targetDest takes nothing returns destructable
return eventTargetDest
endmethod
static constant method operator target takes nothing returns widget
if eventTargetUnit != null then
return eventTargetUnit
elseif eventTargetItem != null then
return eventTargetItem
elseif eventTargetDest != null then
return eventTargetDest
endif
return null
endmethod
static constant method operator targetX takes nothing returns real
return eventTargetX
endmethod
static constant method operator targetY takes nothing returns real
return eventTargetY
endmethod
static method operator [] takes integer abilId returns thistype
local thistype this = table[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)
else
if UnitAddAbility(preloadDummy, abilId) then
call UnitRemoveAbility(preloadDummy, abilId)
endif
endif
set spellCount = spellCount + 1
set thistype(spellCount).abilId = abilId
set table[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 operator GENERIC takes nothing returns thistype
return thistype[0]
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
local Handler handler
if eventAbilityId == this.abilId then
set handler = eventHandler[(this - 1)*5 + eventIndex[eventEventType]]
if disabled then
set handler.disablerCounter = handler.disablerCounter + 1
else
set handler.disablerCounter = handler.disablerCounter - 1
endif
endif
endmethod
method operator handlersDisabled takes nothing returns boolean
if eventAbilityId != this.abilId then
return false
endif
return eventHandler[(this - 1)*5 + eventIndex[eventEventType]].disablerCounter > 0
endmethod
private static method overrideParams takes integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
if eventAbilityId != 0 then
set Handler(table[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_NO_TARGET, 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_TARGET, level, triggerUnit, null, targetX, targetY)
endmethod
static method overrideSingleTargetParams takes integer level, unit triggerUnit, widget target returns nothing
call overrideParams(SPELL_ORDER_TYPE_SINGLE_TARGET, level, triggerUnit, target, GetWidgetX(target), GetWidgetY(target))
endmethod
private static method executeEventHandler takes Handler eventHandler, integer currentId, boolean manualInvoke, integer eventFlag, integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
local integer disablerCounter = eventHandler.disablerCounter
local boolean overrideParams = eventHandler.overrideParams
local integer prevAbilityId = eventAbilityId
local integer prevEventType = eventEventType
local integer prevOrderType = eventOrderType
local integer prevLevel = eventLevel
local player prevTriggerPlayer = eventTriggerPlayer
local unit prevTriggerUnit = eventTriggerUnit
local real prevTargetX = eventTargetX
local real prevTargetY = eventTargetY
local unit prevTargetUnit = eventTargetUnit
local item prevTargetItem = eventTargetItem
local destructable prevTargetDest = eventTargetDest
local location tempLoc
set eventAbilityId = currentId
if manualInvoke then
set eventEventType = eventFlag
set eventOrderType = orderType
set eventLevel = level
set eventTriggerPlayer = GetOwningPlayer(triggerUnit)
set eventTriggerUnit = triggerUnit
set eventTargetX = targetX
set eventTargetY = targetY
set table[0].widget[0] = target
set eventTargetUnit = table[0].unit[0]
set eventTargetItem = table[0].item[0]
set eventTargetDest = table[0].destructable[0]
else
set eventEventType = eventTypeId[GetHandleId(GetTriggerEventId())]
set eventTriggerPlayer = GetTriggerPlayer()
set eventTriggerUnit = GetTriggerUnit()
set eventLevel = GetUnitAbilityLevel(eventTriggerUnit, eventAbilityId)
set eventTargetUnit = GetSpellTargetUnit()
set eventTargetItem = GetSpellTargetItem()
set eventTargetDest = GetSpellTargetDestructable()
if eventTargetUnit != null then
if eventTargetUnit == eventTriggerUnit and /*
*/ not (GetSpellTargetX() != 0) and /*
*/ not (GetSpellTargetY() != 0) then
set eventTargetX = GetUnitX(eventTriggerUnit)
set eventTargetY = GetUnitY(eventTriggerUnit)
set eventTargetUnit = null
set eventOrderType = SPELL_ORDER_TYPE_NO_TARGET
else
set eventTargetX = GetUnitX(eventTargetUnit)
set eventTargetY = GetUnitY(eventTargetUnit)
set eventOrderType = SPELL_ORDER_TYPE_SINGLE_TARGET
endif
elseif eventTargetItem != null then
set eventTargetX = GetItemX(eventTargetItem)
set eventTargetY = GetItemY(eventTargetItem)
set eventOrderType = SPELL_ORDER_TYPE_SINGLE_TARGET
elseif eventTargetDest != null then
set eventTargetX = GetWidgetX(eventTargetDest)
set eventTargetY = GetWidgetY(eventTargetDest)
set eventOrderType = SPELL_ORDER_TYPE_SINGLE_TARGET
else
set tempLoc = GetSpellTargetLoc()
if tempLoc == null then
set eventTargetX = GetUnitX(eventTriggerUnit)
set eventTargetY = GetUnitY(eventTriggerUnit)
set eventOrderType = SPELL_ORDER_TYPE_NO_TARGET
else
call RemoveLocation(tempLoc)
set tempLoc = null
set eventTargetX = GetSpellTargetX()
set eventTargetY = GetSpellTargetY()
set eventOrderType = SPELL_ORDER_TYPE_POINT_TARGET
endif
endif
endif
set eventHandler.disablerCounter = 0
set eventHandler.overrideParams = false
call TriggerEvaluate(eventHandler.trigger)
set eventHandler.disablerCounter = disablerCounter
set eventHandler.overrideParams = overrideParams
set eventAbilityId = prevAbilityId
set eventEventType = prevEventType
set eventOrderType = prevOrderType
set eventLevel = prevLevel
set eventTriggerPlayer = prevTriggerPlayer
set eventTriggerUnit = prevTriggerUnit
set eventTargetX = prevTargetX
set eventTargetY = prevTargetY
set eventTargetUnit = prevTargetUnit
set eventTargetItem = prevTargetItem
set eventTargetDest = prevTargetDest
set prevTriggerPlayer = null
set prevTriggerUnit = null
set prevTargetUnit = null
set prevTargetItem = null
set prevTargetDest = null
endmethod
private method invokeEvent 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.abilId, true, eventType, orderType, level, triggerUnit, target, targetX, targetY)
endif
endmethod
method invokeNoTargetEvent 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.invokeEvent(eventType, SPELL_ORDER_TYPE_NO_TARGET, level, triggerUnit, null, GetUnitX(triggerUnit), GetUnitY(triggerUnit))
endmethod
method invokePointTargetEvent 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.invokeEvent(eventType, SPELL_ORDER_TYPE_POINT_TARGET, level, triggerUnit, null, targetX, targetY)
endmethod
method invokeSingleTargetEvent 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.invokeEvent(eventType, SPELL_ORDER_TYPE_SINGLE_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[(table[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 core = CreateTrigger()
local integer index = 0
loop
call TriggerRegisterPlayerUnitEvent(core, Player(index), whichEvent, null)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(core, Filter(handler) )
endif
endmethod
static if not LIBRARY_ResourcePreloader then
private static method initPreloadDummy takes nothing returns nothing
local rect world = GetWorldBounds()
set preloadDummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'hpea', 0.00, 0.00, 0.00)
call UnitAddAbility(preloadDummy, 'AInv')
call UnitAddAbility(preloadDummy, 'Avul')
call UnitRemoveAbility(preloadDummy, 'Amov')
call SetUnitY(preloadDummy, GetRectMaxY(world) + 1000.00)
call RemoveRect(world)
set world = null
endmethod
endif
private static method init takes nothing returns nothing
set spellKey = Node.allocate()
set spellCount = spellCount + 1
set table[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 eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_CAST)] = EVENT_SPELL_CAST
set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_CHANNEL)] = EVENT_SPELL_CHANNEL
set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_EFFECT)] = EVENT_SPELL_EFFECT
set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_ENDCAST)] = EVENT_SPELL_ENDCAST
set eventTypeId[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)
static if not LIBRARY_ResourcePreloader then
call initPreloadDummy()
endif
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
set table = TableArray[JASS_MAX_ARRAY_SIZE]
call init()
call Handler.init()
endmethod
endmodule
/*===================================================================================*/
constant function GetEventSpellAbilityId takes nothing returns integer
return Spell.abilityId
endfunction
constant function GetEventSpellEventType takes nothing returns integer
return Spell.eventType
endfunction
constant function GetEventSpellOrderType takes nothing returns integer
return Spell.orderType
endfunction
constant function GetEventSpellLevel takes nothing returns integer
return Spell.level
endfunction
constant function GetEventSpellPlayer takes nothing returns player
return Spell.triggerPlayer
endfunction
constant function GetEventSpellCaster takes nothing returns unit
return Spell.triggerUnit
endfunction
constant function GetEventSpellTargetUnit takes nothing returns unit
return Spell.targetUnit
endfunction
constant function GetEventSpellTargetItem takes nothing returns item
return Spell.targetItem
endfunction
constant function GetEventSpellTargetDest takes nothing returns destructable
return Spell.targetDest
endfunction
constant function GetEventSpellTarget takes nothing returns widget
return Spell.target
endfunction
constant function GetEventSpellTargetX takes nothing returns real
return Spell.targetX
endfunction
constant function GetEventSpellTargetY takes nothing returns real
return Spell.targetY
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 SpellCancelEventHandlers takes boolean cancel returns nothing
set Spell[GetEventSpellAbilityId()].handlersDisabled = cancel
endfunction
function SpellInvokeNoTargetEvent takes integer abilId, integer eventType, integer level, unit caster returns nothing
call Spell[abilId].invokeNoTargetEvent(eventType, level, caster)
endfunction
function SpellInvokePointTargetEvent takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY returns nothing
call Spell[abilId].invokePointTargetEvent(eventType, level, caster, targetX, targetY)
endfunction
function SpellInvokeSingleTargetEvent takes integer abilId, integer eventType, integer level, unit caster, widget target returns nothing
call Spell[abilId].invokeSingleTargetEvent(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()
set table[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 SpellRegisterEventHandler(abilId, eventType, onSpellEvent)
endif
endfunction
module SpellEvent
static if thistype.onSpellPeriodic.exists then
private static method enqueue takes thistype node returns nothing
local thistype last = thistype(0).prev
set thistype(0).prev = node
set last.next = node
set node.prev = last
set node.next = 0
endmethod
private static method onPeriodic takes nothing returns nothing
local thistype node = thistype(0).next
local thistype next
if node == 0 then
/*
* For some reason, someone tried to manually remove his node from the supposed
* readonly linked-list, without realizing that he's almost breaking the system
*/
call DestroyTimerEx(GetExpiredTimer())
return
endif
loop
exitwhen node == 0
set next = node.next
if not node.onSpellPeriodic() then
set node.next.prev = node.prev
set node.prev.next = node.next
static if thistype.onSpellEnd.exists then
call node.onSpellEnd()
endif
if node.internal then
set node.internal = false
call Node(node).deallocate()
endif
if thistype(0).next == 0 then
call DestroyTimerEx(GetExpiredTimer())
endif
endif
set node = next
endloop
endmethod
endif
static if not thistype.onSpellPeriodic.exists and not thistype.onSpellEnd.exists then
private static method onSpellEvent takes nothing returns nothing
call thistype(0).onSpellStart()
endmethod
else
readonly thistype prev
readonly thistype next
private boolean internal
private static method onSpellEvent takes nothing returns nothing
local thistype node = Node.allocate()
local boolean prevEmpty = thistype(0).next == 0
local thistype used = node.onSpellStart()
/*
* Add the new node into the list
*/
if used == node then
debug call AssertError(used.next.prev == used, "[internal] onSpellStart()", "thistype", used, "[Node Already Exists] : Make sure your nodes are all from the same stack..")
static if thistype.onSpellPeriodic.exists then
set used.internal = true
call enqueue(used)
elseif thistype.onSpellEnd.exists then
call used.onSpellEnd()
call used.deallocate()
endif
else
/*
* If the user returned a different node than the one he was given,
* deallocate the earlier node and replace it with the new node from the user
*/
call Node(node).deallocate()
if used > 0 then
debug call AssertError(used.next.prev == used, "[internal] onSpellStart()", "thistype", used, "[Node Already Exists] : Make sure your nodes are all from the same stack..")
static if thistype.onSpellPeriodic.exists then
call enqueue(used)
elseif thistype.onSpellEnd.exists then
call used.onSpellEnd()
endif
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()
*/
static if thistype.onSpellPeriodic.exists then
if prevEmpty and thistype(0).next != 0 then
call TimerStart(CreateTimer(), SPELL_PERIOD, true, function thistype.onPeriodic)
endif
endif
endmethod
endif
private static method onInit takes nothing returns nothing
call RegisterSpell(SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
static if thistype.onSpellEventModuleInit.exists then
call onSpellEventModuleInit()
endif
endmethod
static method registerSpellEvent takes integer abilId, integer eventType returns nothing
call SpellRegisterEventHandler(abilId, eventType, function thistype.onSpellEvent)
endmethod
endmodule
module SpellEventEx
static if thistype.onSpellPeriodic.exists then
private static method onPeriodic takes nothing returns nothing
local timer expired = GetExpiredTimer()
local integer handleId = GetHandleId(expired)
local thistype node = table[0][handleId]
if not node.onSpellPeriodic() then
static if thistype.onSpellEnd.exists then
call node.onSpellEnd()
endif
call table[0].remove(handleId)
call DestroyTimerEx(expired)
endif
set expired = null
endmethod
endif
private static method onSpellEvent takes nothing returns nothing
static if thistype.onSpellPeriodic.exists then
call OnSpellEventEx(onSpellStart(), SPELL_PERIOD, function thistype.onPeriodic)
elseif thistype.onSpellEnd.exists then
local thistype node = onSpellStart()
if node > 0 then
call node.onSpellEnd()
endif
else
call onSpellStart()
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpell(SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
static if thistype.onSpellEventModuleInit.exists then
call onSpellEventModuleInit()
endif
endmethod
static method registerSpellEvent takes integer abilId, integer eventType returns nothing
call SpellRegisterEventHandler(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 GetEventSpellEventType() == EVENT_SPELL_CAST then
call onSpellCast()
endif
endif
static if thistype.onSpellChannel.exists then
if GetEventSpellEventType() == EVENT_SPELL_CHANNEL then
call onSpellChannel()
endif
endif
static if thistype.onSpellEffect.exists then
if GetEventSpellEventType() == EVENT_SPELL_EFFECT then
call onSpellEffect()
endif
endif
static if thistype.onSpellEndcast.exists then
if GetEventSpellEventType() == EVENT_SPELL_ENDCAST then
call onSpellEndcast()
endif
endif
static if thistype.onSpellFinish.exists then
if GetEventSpellEventType() == 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)
static if thistype.onSpellEventGenericModuleInit.exists then
call onSpellEventGenericModuleInit()
endif
endmethod
endmodule
endlibrary
library SpellCloner /* v2.0.0 https://www.hiveworkshop.com/threads/324157/
*/uses /*
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/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 |
|=====|
readonly static SpellCloner.configuredInstance/*
- Use this variable inside the configuration function to refer to the spell
instance being configured
*/module SpellClonerHeader/*
- Implement this module at the top of your spell struct
*/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)
*/module SpellCloner/*
- A supplement to using both SpellClonerHeader and SpellClonerFooter. Implement this
module at the bottom of your spell struct, no need to implement the SpellEvent module
*/interface method onClonedSpellStart takes nothing returns thistype/*
*/interface method onClonedSpellPeriodic takes nothing returns boolean/*
*/interface method onClonedSpellEnd takes nothing returns nothing/*
- Supplement to the onSpellStart(), onSpellPeriodic(), and onSpellEnd() methods from
SpellEvent module
- All these interface methods follow the same rules as their SpellEvent interface methods
counterpart
- You no longer need to call this.initSpellConfiguration(ABILITY_ID) on onClonedSpellStart()
to run your configuration function, this is already done internally by the system
*///! endnovjass
private keyword SpellConfigList
globals
private trigger evaluator = CreateTrigger()
private integer array eventIndex
private SpellConfigList array configStructNode
private integer configuredSpellInstance = 0
private TableArray table
endglobals
private module Init
private static method onInit takes nothing returns nothing
set table = TableArray[JASS_MAX_ARRAY_SIZE]
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
struct SpellCloner extends array
static method operator configuredInstance takes nothing returns thistype
return configuredSpellInstance
endmethod
implement Init
endstruct
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 = table[spellStructId*5 + eventIndex[GetEventSpellEventType()]][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 = table[spellStructId*5 + eventIndex[eventId]][abilId]
if configList == 0 then
set configList = SpellConfigList.create()
set table[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_ABILITY_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
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
module SpellCloner
static constant integer SPELL_ABILITY_ID = 0
static constant integer SPELL_EVENT_TYPE = 0
method onSpellStart takes nothing returns thistype
local integer configStructId = InitSpellConfiguration(thistype.typeid, this, GetEventSpellAbilityId())
local thistype node = this.onClonedSpellStart()
if node > 0 then
if node != this then
call SpellConfigList(configStructNode[configStructId]).evaluateExpr(node)
endif
return node
endif
return 0
endmethod
method onSpellPeriodic takes nothing returns boolean
return this.onClonedSpellPeriodic()
endmethod
method onSpellEnd takes nothing returns nothing
call this.onClonedSpellEnd()
endmethod
implement SpellEvent
implement SpellClonerFooter
endmodule
endlibrary
library LinkedList /* v1.0.0
*/uses /*
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage
*///! novjass
/*
Author:
- AGD
Credits:
- Nestharus , Dirac , Bribe (for their scripts and discussions which I used as reference)
Pros:
- Modular
- Feature-Rich (Can Be)
- Safety-Oriented (On DEBUG_MODE)
- Extensible (Provides Interfaces)
- Flexible (Does not enforce a built-in allocator - enables user to use a custom Alloc or the default allocator)
*/
|-----|
| API |
|-----|
/*
Note: All the fields except from 'prev' and 'next' are actually operators, so you might want to
avoid using them from the interface methods that would be declared above them.
=====================================================================================================
List Fields Modules (for those who want to write or inline the core linked-list operations themselves)
*/module LinkedListFields/*
*/readonly thistype prev/*
*/readonly thistype next/*
*/module StaticListFields extends LinkedListFields/*
*/readonly static constant thistype head/*
*/readonly static thistype front/*
*/readonly static thistype back/*
*/readonly static boolean empty/*
*/module ListFields extends LinkedListFields/*
*/readonly thistype front/*
*/readonly thistype back/*
*/readonly boolean empty/*
=====================================================================================================
Lite List Modules (should be enough for most cases)
*/module LinkedListLite extends LinkedListFields/*
*/optional interface static method onInsert takes thistype node returns nothing/*
*/optional interface static method onRemove takes thistype node returns nothing/*
*/optional interface method onTraverse takes thistype node returns nothing/*
*/static method insert takes thistype prev, thistype node returns nothing/*
*/static method remove takes thistype node returns nothing/*
*/method traverseForwards takes nothing returns nothing/*
*/method traverseBackwards takes nothing returns nothing/*
- Only present if onTraverse() is also present
*/module StaticListLite extends StaticListFields, LinkedListLite/*
*/static method pushFront takes thistype node returns nothing/*
*/static method popFront takes nothing returns thistype/*
*/static method pushBack takes thistype node returns nothing/*
*/static method popBack takes nothing returns thistype/*
*/module ListLite extends ListFields, LinkedListLite/*
*/method pushFront takes thistype node returns nothing/*
*/method popFront takes nothing returns thistype/*
*/method pushBack takes thistype node returns nothing/*
*/method popBack takes nothing returns thistype/*
*/module InstantiatedListLite extends ListLite/*
*/interface static method allocate takes nothing returns thistype/*
*/interface method deallocate takes nothing returns nothing/*
*/optional interface method onConstruct takes nothing returns nothing/*
*/optional interface method onDestruct takes nothing returns nothing/*
*/static method create takes nothing returns thistype/*
*/method destroy takes nothing returns nothing/*
=====================================================================================================
Standard List Modules
*/module LinkedList extends LinkedListLite/*
*/static method isLinked takes thistype node returns boolean/*
*/module StaticList extends StaticListLite, LinkedList/*
*/static method clear takes nothing returns nothing/*
*/static method flush takes nothing returns nothing/*
*/module List extends ListLite, LinkedList/*
*/method clear takes nothing returns nothing/*
*/method flush takes nothing returns nothing/*
*/module InstantiatedList extends InstantiatedListLite, List/*
=====================================================================================================
Feature-Rich List Modules (for those who somehow need exotic linked-list operations)
*/module LinkedListEx extends LinkedList/*
*/static method swap takes thistype nodeA, thistype nodeB returns nothing/*
*/module StaticListEx extends StaticList, LinkedListEx/*
*/static method contains takes thistype node returns boolean/*
*/static method getSize takes nothing returns integer/*
*/static method rotateLeft takes nothing returns nothing/*
*/static method rotateRight takes nothing returns nothing/*
*/module ListEx extends List, LinkedListEx/*
*/method contains takes thistype node returns boolean/*
*/method getSize takes nothing returns integer/*
*/method rotateLeft takes nothing returns nothing/*
*/method rotateRight takes nothing returns nothing/*
*/module InstantiatedListEx extends InstantiatedList, ListEx/*
*///! endnovjass
/*========================================= CONFIGURATION ===========================================
* Only affects DEBUG_MODE
* If false, throws warnings instead (Errors pauses the game while warnings do not)
*/
globals
private constant boolean THROW_ERRORS = true
endglobals
/*====================================== END OF CONFIGURATION =====================================*/
static if DEBUG_MODE then
private function AssertError takes boolean condition, string methodName, string structName, integer node, string message returns nothing
static if LIBRARY_ErrorMessage then
static if THROW_ERRORS then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, node, message)
else
call ThrowWarning(condition, SCOPE_PREFIX, methodName, structName, node, message)
endif
endif
endfunction
endif
private module LinkedListUtils
method p_clear takes nothing returns nothing
set this.next.prev = 0
set this.prev.next = 0
set this.prev = this
set this.next = this
endmethod
method p_flush takes nothing returns nothing
local thistype node = this.prev
loop
exitwhen node == this
call remove(node)
set node = node.prev
endloop
endmethod
endmodule
private module LinkedListUtilsEx
implement LinkedListUtils
method p_contains takes thistype toFind returns boolean
local thistype node = this.next
loop
exitwhen node == this
if node == toFind then
return true
endif
set node = node.next
endloop
return false
endmethod
method p_getSize takes nothing returns integer
local integer count = 0
local thistype node = this.next
loop
exitwhen node == this
set count = count + 1
set node = node.next
endloop
return count
endmethod
endmodule
module LinkedListFields
readonly thistype prev
readonly thistype next
endmodule
module LinkedListLite
implement LinkedListFields
static method p_insert takes thistype this, 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
static method p_remove takes thistype node returns nothing
set node.next.prev = node.prev
set node.prev.next = node.next
endmethod
static method insert takes thistype this, thistype node returns nothing
debug call AssertError(node == 0, "insert()", "thistype", 0, "Cannot insert null node")
debug call AssertError(node.next.prev == node or node.prev.next == node, "insert()", "thistype", 0, "Already linked node [" + I2S(node) + "]")
call p_insert(this, node)
static if thistype.onInsert.exists then
call onInsert(node)
endif
endmethod
static method remove takes thistype node returns nothing
debug call AssertError(node == 0, "remove()", "thistype", 0, "Cannot remove null node")
debug call AssertError(node.next.prev != node and node.prev.next != node, "remove()", "thistype", 0, "Invalid node [" + I2S(node) + "]")
static if thistype.onRemove.exists then
call onRemove(node)
endif
call p_remove(node)
endmethod
static if thistype.onTraverse.exists then
method p_traverse takes boolean forward returns nothing
local thistype node
if forward then
set node = this.next
loop
exitwhen node == this or node.prev.next != node
call this.onTraverse(node)
set node = node.next
endloop
else
set node = this.prev
loop
exitwhen node == this or node.next.prev != node
call this.onTraverse(node)
set node = node.prev
endloop
endif
endmethod
method traverseForwards takes nothing returns nothing
call this.p_traverse(true)
endmethod
method traverseBackwards takes nothing returns nothing
call this.p_traverse(false)
endmethod
endif
endmodule
module LinkedList
implement LinkedListLite
static method isLinked takes thistype node returns boolean
return node.next.prev == node or node.prev.next == node
endmethod
endmodule
module LinkedListEx
implement LinkedList
static method swap takes thistype this, thistype node returns nothing
local thistype thisPrev = this.prev
local thistype thisNext = this.next
debug call AssertError(this == 0, "swap()", "thistype", 0, "Cannot swap null node")
debug call AssertError(node == 0, "swap()", "thistype", 0, "Cannot swap null node")
debug call AssertError(not isLinked(this), "swap()", "thistype", 0, "Cannot use unlinked node [" + I2S(this) + "]")
debug call AssertError(not isLinked(node), "swap()", "thistype", 0, "Cannot use unlinked node [" + I2S(node) + "]")
call p_remove(this)
call p_insert(node, this)
if thisNext != node then
call p_remove(node)
call p_insert(thisPrev, node)
endif
endmethod
endmodule
module StaticListFields
implement LinkedListFields
static constant method operator head takes nothing returns thistype
return 0
endmethod
static method operator back takes nothing returns thistype
return head.prev
endmethod
static method operator front takes nothing returns thistype
return head.next
endmethod
static method operator empty takes nothing returns boolean
return front == head
endmethod
endmodule
module StaticListLite
implement StaticListFields
implement LinkedListLite
static method pushFront takes thistype node returns nothing
debug call AssertError(node == 0, "pushFront()", "thistype", 0, "Cannot use null node")
debug call AssertError(node.next.prev == node, "pushFront()", "thistype", 0, "Already linked node [" + I2S(node) + "]")
call insert(head, node)
endmethod
static method popFront takes nothing returns thistype
local thistype node = front
debug call AssertError(node.prev != head, "popFront()", "thistype", 0, "Invalid list")
call remove(node)
return node
endmethod
static method pushBack takes thistype node returns nothing
debug call AssertError(node == 0, "pushBack()", "thistype", 0, "Cannot use null node")
debug call AssertError(node.next.prev == node, "pushBack()", "thistype", 0, "Already linked node [" + I2S(node) + "]")
call insert(back, node)
endmethod
static method popBack takes nothing returns thistype
local thistype node = back
debug call AssertError(node.next != head, "popBack()", "thistype", 0, "Invalid list")
call remove(node)
return node
endmethod
endmodule
module StaticList
implement StaticListLite
implement LinkedList
implement LinkedListUtils
static method clear takes nothing returns nothing
call head.p_clear()
endmethod
static method flush takes nothing returns nothing
call head.p_flush()
endmethod
endmodule
module StaticListEx
implement StaticList
implement LinkedListEx
implement LinkedListUtilsEx
static method contains takes thistype node returns boolean
return head.p_contains(node)
endmethod
static method getSize takes nothing returns integer
return head.p_getSize()
endmethod
static method rotateLeft takes nothing returns nothing
call pushBack(popFront())
endmethod
static method rotateRight takes nothing returns nothing
call pushFront(popBack())
endmethod
endmodule
module ListFields
implement LinkedListFields
method operator back takes nothing returns thistype
return this.prev
endmethod
method operator front takes nothing returns thistype
return this.next
endmethod
method operator empty takes nothing returns boolean
return this.next == this
endmethod
endmodule
module ListLite
implement ListFields
implement LinkedListLite
method pushFront takes thistype node returns nothing
debug call AssertError(this == 0, "pushFront()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "pushFront()", "thistype", this, "Invalid list")
debug call AssertError(node == 0, "pushFront()", "thistype", this, "Cannot insert null node")
debug call AssertError(node.next.prev == node, "pushFront()", "thistype", this, "Already linked node [" + I2S(node) + "]")
call insert(this, node)
endmethod
method popFront takes nothing returns thistype
local thistype node = this.next
debug call AssertError(this == 0, "popFront()", "thistype", 0, "Null list")
debug call AssertError(node.prev != this, "popFront()", "thistype", this, "Invalid list")
call remove(node)
return node
endmethod
method pushBack takes thistype node returns nothing
debug call AssertError(this == 0, "pushBack()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "pushBack()", "thistype", this, "Invalid list")
debug call AssertError(node == 0, "pushBack()", "thistype", this, "Cannot insert null node")
debug call AssertError(node.next.prev == node, "pushBack()", "thistype", this, "Already linked node [" + I2S(node) + "]")
call insert(this.prev, node)
endmethod
method popBack takes nothing returns thistype
local thistype node = this.prev
debug call AssertError(this == 0, "popBack()", "thistype", 0, "Null list")
debug call AssertError(node.next != this, "pushFront()", "thistype", this, "Invalid list")
call remove(node)
return node
endmethod
endmodule
module List
implement ListLite
implement LinkedList
implement LinkedListUtils
method clear takes nothing returns nothing
debug call AssertError(this == 0, "clear()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "clear()", "thistype", this, "Invalid list")
call this.p_clear()
endmethod
method flush takes nothing returns nothing
debug call AssertError(this == 0, "flush()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "flush()", "thistype", this, "Invalid list")
call this.p_flush()
endmethod
endmodule
module ListEx
implement List
implement LinkedListEx
implement LinkedListUtilsEx
method contains takes thistype node returns boolean
debug call AssertError(this == 0, "contains()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "contains()", "thistype", this, "Invalid list")
return this.p_contains(node)
endmethod
method getSize takes nothing returns integer
debug call AssertError(this == 0, "getSize()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "getSize()", "thistype", this, "Invalid list")
return this.p_getSize()
endmethod
method rotateLeft takes nothing returns nothing
debug call AssertError(this == 0, "rotateLeft()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "rotateLeft()", "thistype", this, "Invalid list")
call this.pushBack(this.popFront())
endmethod
method rotateRight takes nothing returns nothing
debug call AssertError(this == 0, "rotateRight()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev == this, "rotateRight()", "thistype", this, "Invalid list")
call this.pushFront(this.popBack())
endmethod
endmodule
module InstantiatedListLite
implement ListLite
debug private boolean valid
static method create takes nothing returns thistype
local thistype node = allocate()
set node.prev = node
set node.next = node
debug set node.valid = true
static if thistype.onConstruct.exists then
call node.onConstruct()
endif
return node
endmethod
method destroy takes nothing returns nothing
debug call AssertError(this == 0, "destroy()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "destroy()", "thistype", this, "Invalid list")
debug call AssertError(not this.valid, "destroy()", "thistype", this, "Double-free")
debug set this.valid = false
static if thistype.flush.exists then
call this.flush()
endif
static if thistype.onDestruct.exists then
call this.onDestruct()
endif
debug set this.prev = 0
debug set this.next = 0
call this.deallocate()
endmethod
endmodule
module InstantiatedList
implement List
implement InstantiatedListLite
endmodule
module InstantiatedListEx
implement ListEx
implement InstantiatedList
endmodule
endlibrary
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
* v1.2.2.0, 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 global filter for units being indexed
/*static method onFilter takes unit u returns boolean
return true
endmethod*/
endmodule
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines.
*
* 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.
*
* -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
/****************************************************************/
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
function UnitDexRemove takes unit u, boolean runEvents returns boolean
return UnitDex.Remove(u, runEvents)
endfunction
/****************************************************************/
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 = 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
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
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
// Index preplaced units
set i = 0
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
set LastIndex = Counter
// run init triggers
call TimerStart(CreateTimer(), 0.00, false, function thistype.onGameStart)
endmethod
endmodule
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 = 'dumi'
//The owner of the Dummy Unit
private constant player OWNER = Player(bj_PLAYER_NEUTRAL_EXTRA)
//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 TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
library ZLibrary /* this is just the lightweight version
********************************************************************
*
* function GetTerrainZ takes real x, real y returns real
*
********************************************************************/
globals
private constant location Loc = Location(0, 0)
endglobals
function GetTerrainZ takes real x, real y returns real
call MoveLocation(Loc, x, y)
return GetLocationZ(Loc)
endfunction
endlibrary
library DispelConfiguration uses DispelLibrary
/* If you want to include more dispel abilities in the list
* Just copy & paste the default format of the structs here
*/
private struct DispelMagic extends array
// Dispel Magic (Priest) Ability ID
private static constant integer ABILITY_ID = 'Adis'
private static constant method radius takes integer level returns real
return 200.00 + (0.00*level) // radius of the ability (set to 0 to target unit dispel)
endmethod
private static constant method dispel takes integer level returns integer
/* DISPEL TYPES:
* BUFF_POSITIVE
* BUFF_NEGATIVE
*/
return 0 // dispel type of the ability (set to 0 to dispel both)
endmethod
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement DispelConfigurationCloner
endstruct
private struct Disenchant extends array
// Disenchant (Spirit Walker) Ability ID
private static constant integer ABILITY_ID = 'Adcn'
private static constant method radius takes integer level returns real
return 250.00 + (0.00*level) // radius of the ability (set to 0 to target unit dispel)
endmethod
private static constant method dispel takes integer level returns integer
/* DISPEL TYPES:
* BUFF_POSITIVE
* BUFF_NEGATIVE
*/
return 0 // dispel type of the ability (set to 0 to dispel both)
endmethod
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement DispelConfigurationCloner
endstruct
private struct Purge extends array
// Purge (Shaman) Ability ID
private static constant integer ABILITY_ID = 'Aprg'
private static constant method radius takes integer level returns real
return 0.00 + (0.00*level) // radius of the ability (set to 0 to target unit dispel)
endmethod
private static constant method dispel takes integer level returns integer
/* DISPEL TYPES:
* BUFF_POSITIVE
* BUFF_NEGATIVE
*/
return 0 // dispel type of the ability (set to 0 to dispel both)
endmethod
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement DispelConfigurationCloner
endstruct
private struct DevourMagic extends array
// Devour Magic (Destroyer) Ability ID
private static constant integer ABILITY_ID = 'Advm'
private static constant method radius takes integer level returns real
return 200.00 + (0.00*level) // radius of the ability (set to 0 to target unit dispel)
endmethod
private static constant method dispel takes integer level returns integer
/* DISPEL TYPES:
* BUFF_POSITIVE
* BUFF_NEGATIVE
*/
return 0 // dispel type of the ability (set to 0 to dispel both)
endmethod
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement DispelConfigurationCloner
endstruct
private struct AbolishMagic extends array
// Abolish Magic (Dryad) Ability ID
private static constant integer ABILITY_ID = 'Aadm'
private static constant method radius takes integer level returns real
return 0.00 + (0.00*level) // radius of the ability (set to 0 to target unit dispel)
endmethod
private static constant method dispel takes integer level returns integer
/* DISPEL TYPES:
* BUFF_POSITIVE
* BUFF_NEGATIVE
*/
return 0 // dispel type of the ability (set to 0 to dispel both)
endmethod
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement DispelConfigurationCloner
endstruct
private struct Detonate extends array
// Detonate (Wisp) Ability ID
private static constant integer ABILITY_ID = 'Adtn'
private static constant method radius takes integer level returns real
return 300.00 + (0.00*level) // radius of the ability (set to 0 to target unit dispel)
endmethod
private static constant method dispel takes integer level returns integer
/* DISPEL TYPES:
* BUFF_POSITIVE
* BUFF_NEGATIVE
*/
return 0 // dispel type of the ability (set to 0 to dispel both)
endmethod
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement DispelConfigurationCloner
endstruct
endlibrary
library DispelLibrary /* by JAKEZINC, extra library to dispel self-defined buffs created by Buff library
*/uses /*
*/Buff /* https://www.hiveworkshop.com/threads/288640/
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/SpellCloner /* https://www.hiveworkshop.com/threads/292751/
*/
globals
private group Group = CreateGroup()
endglobals
native UnitAlive takes unit id returns boolean
// Filtration of the units that will be dispelled
private function Filtration takes unit picked returns boolean
return UnitAlive(picked)
endfunction
private struct Clone extends array
static real radius
static integer dispel
endstruct
private struct Dispel extends array
private method onClonedSpellStart takes nothing returns thistype
local real x
local real y
local unit picked
if Clone.radius > 0. then
set x = GetEventSpellTargetX()
set y = GetEventSpellTargetY()
call GroupEnumUnitsInRange(Group,x,y,Clone.radius,null)
loop
set picked = FirstOfGroup(Group)
exitwhen picked == null
if Filtration(picked) then
if Clone.dispel > 0 then
call Buff.dispel(picked,Clone.dispel)
else
call Buff.dispelBoth(picked)
endif
endif
call GroupRemoveUnit(Group,picked)
endloop
else
if Clone.dispel > 0 then
call Buff.dispel(GetEventSpellTargetUnit(),Clone.dispel)
else
call Buff.dispelBoth(GetEventSpellTargetUnit())
endif
endif
return 0
endmethod
/*
* locust methods because of the SpellEvent interface
*/
private static constant real SPELL_PERIOD = 0.0
private method onClonedSpellPeriodic takes nothing returns boolean
return false
endmethod
private method onClonedSpellEnd takes nothing returns nothing
endmethod
implement SpellCloner
endstruct
module DispelConfigurationCloner
private static method configHandler takes nothing returns nothing
set Clone.radius = radius(GetEventSpellLevel())
set Clone.dispel = dispel(GetEventSpellLevel())
endmethod
private static method onInit takes nothing returns nothing
call Dispel.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
endmethod
endmodule
endlibrary
library PauseLibrary // by JAKEZINC, extra library to adapt pause/unpause methods for a unit safely
globals
// This serves as the main setup for pausing/unpausing a unit.
// Pause Ability ID
public constant integer PAUSE_ID = 'A00A' // Based on 'Infinite Channel Ability'
public constant integer PAUSE_ORDER_ID = 852055
/*
OBJECT DATA IMPORTS ARE OPTIONAL BELOW
I've discovered that infinite channel ability alone is not enough for some rare cases.
So if you don't care about these rare cases you can simply set the booleans to FALSE. (<-NO IMPORT REQUIRED)
If the infinite channel ability doesn't get implemented to a unit, the pause method adapts to a new method.
These are some of the rare cases where infinite channel ability method breaks: */
/*
Case #1: Militia - for unknown reason, we can only put aura abilities to them,
so we can't implement this method to militia units as it's considered using an active ability...
In order to pause them, we can DISARM them. */
public constant boolean DISARM = true
// Disarm Ability ID
public constant integer DISARM_ID = 'A001' // Based on 'Cargo Hold Ability'
/*
Case #2: Same as #1 but the unit has abilities, therefore DISARM is not enough.
In order to pause them, we can DISABLE (DISARM + SILENCE) them.
So you should set DISARM to false and set DISABLE to true instead.
Note that this DISABLE method requires and creates a global dummy unit to
cast the Disable Ability on a unit. It also shows on the unit's status bar.
Make sure you set the 'Stats - Buffs' field of the Disable Ability to Disable <- (Buff) in OE. */
public constant boolean DISABLE = false
// Dummy Unit ID
public constant integer DISABLE_CASTER = 'dumi' // Based on 'DummyRecycler Library'
// Disable Ability ID
public constant integer DISABLE_ID = 'A004' // Based on 'Drunken Haze Ability'
public constant integer DISABLE_ORDER_ID = 852585
// Disable Buff ID
public constant integer DISABLE_BUFF = 'B000' // Based on 'Drunken Haze Buff'
/* If you have Breath Of Fire ability that ignites the units having the Drunken Haze Buff in your map,
You must specify the Drunken Haze buff and the Breath of Fire buff that is related to each other.
In this way, Breath Of Fire ability will not ignite the units that are paused using the DISABLE method. */
// Drunken Haze Buff ID
public constant integer DRUNKEN_HAZE_BUFF = 'BNdh'
// Breath Of Fire Buff ID
public constant integer BREATH_OF_FIRE_BUFF = 'BNbf'
/*
Case #3: Silenced - for obvious reason, we can't implement the method to a silenced unit.
In order to pause them, the silence must be removed from the unit first before pausing it. */
public constant boolean SILENCE = true
// Silence Buff ID
public constant integer SILENCE_BUFF = 'BNsi'
/*
Case #4: Polymorphed - for obvious reason, we can't implement the method to a polymorphed unit.
In order to pause them, periodic polymorph checking, disabling its movement, and re-enable pause. */
public constant boolean POLYMORPH = true
/*
Case #5: Sleeping - for obvious reason, we can't implement the method to a sleeping unit.
In order to pause them, the unit must wake up first so we can order it to execute the pause. */
public constant boolean SLEEPING = true
public constant real PERIODIC_INTERVAL = 0.05
endglobals
struct PauseSystem extends array
private static hashtable table = InitHashtable()
private static method periodic takes nothing returns nothing
local unit target = LoadUnitHandle(table,0,GetHandleId(GetExpiredTimer()))
local thistype this = GetHandleId(target)
static if DISABLE then
if GetUnitAbilityLevel(target,BREATH_OF_FIRE_BUFF) > 0 and GetUnitAbilityLevel(target,DRUNKEN_HAZE_BUFF) == 0 then
call UnitRemoveAbility(target,BREATH_OF_FIRE_BUFF)
endif
endif
static if SILENCE then
if GetUnitAbilityLevel(target,SILENCE_BUFF) > 0 then
static if DISARM then
if GetUnitAbilityLevel (target,DISARM_ID) == 0 then
call UnitAddAbility(target,DISARM_ID)
endif
elseif DISABLE then
if GetUnitAbilityLevel(target,DISABLE_BUFF) == 0 then
call SetUnitX( disabler,GetUnitX(target) )
call SetUnitY( disabler,GetUnitY(target) )
call UnitAddAbility(disabler,DISABLE_ID)
call IssueTargetOrderById(disabler,DISABLE_ORDER_ID,target)
call UnitRemoveAbility(disabler,DISABLE_ID)
endif
else
call UnitRemoveAbility(target,SILENCE_BUFF)
endif
endif
endif
if GetUnitTypeId(target) != LoadInteger(table,this,2) then
call SaveInteger( table,this,2,GetUnitTypeId(target) )
call UnitRemoveAbility(target,PAUSE_ID)
call SetUnitTurnSpeed(target,0)
call SetUnitPropWindow(target,0)
if UnitAddAbility(target,PAUSE_ID) and IssueImmediateOrderById(target,PAUSE_ORDER_ID) then
endif
if GetUnitAbilityLevel(target,PAUSE_ID) == 0 or GetUnitCurrentOrder(target) != PAUSE_ORDER_ID then
static if DISARM then
if GetUnitAbilityLevel (target,DISARM_ID) == 0 then
call UnitAddAbility(target,DISARM_ID)
endif
elseif DISABLE then
if GetUnitAbilityLevel(target,DISABLE_BUFF) == 0 then
call SetUnitX( disabler,GetUnitX(target) )
call SetUnitY( disabler,GetUnitY(target) )
call UnitAddAbility(disabler,DISABLE_ID)
call IssueTargetOrderById(disabler,DISABLE_ORDER_ID,target)
call UnitRemoveAbility(disabler,DISABLE_ID)
endif
endif
endif
endif
static if POLYMORPH then
if not IsUnitType(target,UNIT_TYPE_POLYMORPHED) and LoadBoolean(table,this,3) then
call SetUnitTurnSpeed(target,0)
call SetUnitPropWindow(target,0)
static if SILENCE then
if GetUnitAbilityLevel(target,SILENCE_BUFF) > 0 then
call UnitRemoveAbility(target,SILENCE_BUFF)
endif
endif
call IssueImmediateOrderById(target,PAUSE_ORDER_ID)
call RemoveSavedBoolean(table,this,3)
elseif IsUnitType(target,UNIT_TYPE_POLYMORPHED) then
call SetUnitTurnSpeed(target,0)
call SetUnitPropWindow(target,0)
call SaveBoolean( table,this,3,true )
endif
endif
set target = null
endmethod
static method pause takes unit target returns nothing
local thistype this = GetHandleId(target)
local integer count = LoadInteger(table,this,0)
local timer time
if count == 0 then
set time = CreateTimer()
call SaveUnitHandle(table,0,GetHandleId(time),target)
call TimerStart(time,PERIODIC_INTERVAL,true,function thistype.periodic)
call SaveTimerHandle(table,this,1,time)
call SaveInteger( table,this,2,GetUnitTypeId(target) )
static if SLEEPING then
if IsUnitType(target,UNIT_TYPE_SLEEPING) then
call UnitWakeUp(target)
endif
endif
static if SILENCE then
if GetUnitAbilityLevel(target,SILENCE_BUFF) > 0 then
call UnitRemoveAbility(target,SILENCE_BUFF)
endif
endif
call SetUnitTurnSpeed(target,0)
call SetUnitPropWindow(target,0)
if UnitAddAbility(target,PAUSE_ID) and IssueImmediateOrderById(target,PAUSE_ORDER_ID) then
endif
if GetUnitAbilityLevel(target,PAUSE_ID) == 0 or GetUnitCurrentOrder(target) != PAUSE_ORDER_ID then
static if DISARM then
call UnitAddAbility(target,DISARM_ID)
elseif DISABLE then
call SetUnitX( disabler,GetUnitX(target) )
call SetUnitY( disabler,GetUnitY(target) )
call UnitAddAbility(disabler,DISABLE_ID)
call IssueTargetOrderById(disabler,DISABLE_ORDER_ID,target)
call UnitRemoveAbility(disabler,DISABLE_ID)
endif
endif
static if POLYMORPH then
call SaveBoolean( table,this,3,IsUnitType(target,UNIT_TYPE_POLYMORPHED) )
endif
set time = null
endif
call SaveInteger(table,this,0,count + 1)
endmethod
static method unpause takes unit target returns nothing
local timer time
local thistype this = GetHandleId(target)
local integer count = LoadInteger(table,this,0)
set count = count - 1
call SaveInteger(table,this,0,count)
if count == 0 then
static if DISARM then
call UnitRemoveAbility(target,DISARM_ID)
endif
static if DISABLE then
call UnitRemoveAbility(target,DISABLE_BUFF)
endif
call UnitRemoveAbility(target,PAUSE_ID)
call SetUnitTurnSpeed(target,GetUnitDefaultTurnSpeed(target) )
call SetUnitPropWindow(target,GetUnitDefaultPropWindow(target)*bj_RADTODEG)
set time = LoadTimerHandle(table,this,1)
call DestroyTimer(time)
call RemoveSavedHandle( table,0,GetHandleId(time) )
call FlushChildHashtable(table,this)
set time = null
endif
endmethod
static if DISABLE then
private static unit disabler = null
private static method onInit takes nothing returns nothing
set disabler = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA),DISABLE_CASTER,0,0,0)
endmethod
endif
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 ChilledBuffLibrary /* is using optional custom models
war3mapImported\ChilledColdBuff.mdx
*/uses /*
*/Buff /* https://www.hiveworkshop.com/threads/288640/
*/
public struct ChilledBuff extends Buff
/**************************************************
* GLOBAL CONFIGURATION
***************************************************/
/*
* Note that when importing, make sure the two objects (Buff Creator and the Buff)
* In object editor have the same rawcode except for their first characters
* You can easily customize rawcodes using an object merger script or Grimoire extension
* This feature makes the ability to work like a debuff (with Dispel library)
* The slow-stats are configurable inside the Object Editor, on the buff creator itself
*/
// Chilled Ability Buff Creator ID
readonly static constant integer RAWCODE = 'ACHL'
// The model of the Chilled buff
private static string BUFF_MODEL = "war3mapImported\\ChilledColdBuff.mdx"
private static string BUFF_MODEL_ORIGIN = "chest"
// BUFF_POSITIVE , BUFF_NEGATIVE (set to 0 to not dispel)
private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
/* BUFF_STACK_NONE = does not stack with any source
* BUFF_STACK_PARTIAL = does not stack with same source
* BUFF_STACK_FULL = does stack with any source or same source
* Note that it refreshes duration if the buff doesn't stack
*/
private static constant integer STACK_TYPE = BUFF_STACK_NONE
/**************************************************
* END OF GLOBAL CONFIGURATION
***************************************************/
/*============================= CORE CODE =============================*/
private effect model
method onRemove takes nothing returns nothing
call DestroyEffect(.model)
set .model = null
endmethod
method onApply takes nothing returns nothing
set .model = AddSpecialEffectTarget(BUFF_MODEL,.target,BUFF_MODEL_ORIGIN)
endmethod
implement BuffApply
endstruct
endlibrary
library FrozenBuffLibrary /*
*/uses /*
*/Buff /* https://www.hiveworkshop.com/threads/288640/
*/TimerUtils /* http://www.wc3c.net/showthread.php?t=101322
*/PauseLibrary /* library is used for pausing/unpausing a unit safely
*/
public struct FrozenBuff extends Buff
/**************************************************
* GLOBAL CONFIGURATION
***************************************************/
private static constant real PERIODIC_INTERVAL = 0.05
/*
* Note that when importing, make sure the two objects (Buff Creator and the Buff)
* In object editor have the same rawcode except for their first characters
* You can easily customize rawcodes using an object merger script or Grimoire extension
* This feature makes the ability to work like a debuff (with Dispel library)
*/
// Frozen Ability Buff Creator ID
private static constant integer RAWCODE = 'AFRZ'
// The model of the Frozen buff
private static string BUFF_MODEL = "Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl"
private static string BUFF_MODEL_ORIGIN = "origin"
// The time it takes before the unit becomes completely frozen (set to 0 to instant)
readonly static constant real DELAY = 0.75
// BUFF_POSITIVE , BUFF_NEGATIVE (set to 0 to not dispel)
private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
/* BUFF_STACK_NONE = does not stack with any source
* BUFF_STACK_PARTIAL = does not stack with same source
* BUFF_STACK_FULL = does stack with any source or same source
* Note that it refreshes duration if the buff doesn't stack
*/
private static constant integer STACK_TYPE = BUFF_STACK_NONE
/**************************************************
* END OF GLOBAL CONFIGURATION
***************************************************/
/*============================= CORE CODE =============================*/
readonly integer id
readonly boolean polymorphed
private real timescale
private real rate
private effect model
private timer time
method onRemove takes nothing returns nothing
call ReleaseTimer(.time)
call DestroyEffect(.model)
if .timescale <= 0. then
call PauseSystem.unpause(.target)
endif
call SetUnitTimeScale(.target,1.0)
set .time = null
set .model = null
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if .timescale > 0. then
set .timescale = .timescale - .rate
call SetUnitTimeScale(.target,.timescale)
if .timescale <= 0. then
call PauseSystem.pause(.target)
set .polymorphed = IsUnitType(.target,UNIT_TYPE_POLYMORPHED)
endif
endif
if .timescale <= 0. then
if not IsUnitType(.target,UNIT_TYPE_POLYMORPHED) and .polymorphed then
call DestroyEffect(.model)
set .model = null
set .model = AddSpecialEffectTarget(BUFF_MODEL,.target,BUFF_MODEL_ORIGIN)
set .polymorphed = false
elseif IsUnitType(.target,UNIT_TYPE_POLYMORPHED) and not .polymorphed then
set .polymorphed = true
endif
if GetUnitTypeId(.target) != .id then
call DestroyEffect(.model)
set .model = null
set .model = AddSpecialEffectTarget(BUFF_MODEL,.target,BUFF_MODEL_ORIGIN)
set .id = GetUnitTypeId(.target)
endif
endif
endmethod
method onApply takes nothing returns nothing
set .id = GetUnitTypeId(.target)
if DELAY > 0. then
set .timescale = 1.0
set .rate = .timescale/(DELAY/PERIODIC_INTERVAL)
else
set .timescale = 0.
call SetUnitTimeScale(.target,0)
call PauseSystem.pause(.target)
set .polymorphed = IsUnitType(.target,UNIT_TYPE_POLYMORPHED)
endif
set .model = AddSpecialEffectTarget(BUFF_MODEL,.target,BUFF_MODEL_ORIGIN)
set .time = NewTimerEx(this)
call TimerStart(.time,PERIODIC_INTERVAL,true,function thistype.onPeriod)
endmethod
implement BuffApply
endstruct
endlibrary
library FrostboltConfiguration uses Frostbolt optional ChilledBuffLibrary optional FrozenBuffLibrary
private module Configuration
/* Frostbolt Ability IDs are separated and configured below
* As it have the mastered version of the ability,
* the mastered one needs to be registered in the system to make it work
*/
// The size of the projectile model
static constant real PROJECTILE_MODEL_SIZE = 1.25
// The height of the projectile model
static constant real PROJECTILE_MODEL_HEIGHT = 75.00
// The distance of the projectile model
static constant real PROJECTILE_MODEL_DISTANCE = 25.00
// The detection range of the projectile
static constant real PROJECTILE_DETECT_RANGE = 50.00
// The model of the projectile
static constant string PROJECTILE_MODEL = "Abilities\\Spells\\Other\\FrostBolt\\FrostBoltMissile.mdl"
static constant string PROJECTILE_MODEL_ORIGIN = "origin"
// The model spawned on the target when projectile hits
static constant string PROJECTILE_HIT_MODEL = "Abilities\\Weapons\\ZigguratFrostMissile\\ZigguratFrostMissile.mdl"
static constant string PROJECTILE_HIT_MODEL_ORIGIN = "chest"
// The attack type of inflicting damage
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
// The damage type of inflicting damage
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// The weapon type of inflicting damage
static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
static constant method radius takes integer level returns real
return 500.00 + (0.00*level) // bounce range of the projectile
endmethod
static constant method velocity takes integer level returns real
return 30.00 + (0.00*level) // speed of the projectile
endmethod
static constant method damage takes integer level returns real
return 75.00 + (25.00*level) // damage of the projectile
endmethod
static if LIBRARY_ChilledBuffLibrary then
static constant method chilledDuration takes integer level returns real
return 0.00 + (5.00*level) // duration of the chilled buff applied when projectile hits
endmethod
endif
static if LIBRARY_FrozenBuffLibrary then
static constant method frozenDuration takes integer level returns real
return 0.00 + (0.00*level) // duration of the frozen buff applied on the units when projectile hits
endmethod
static constant method frozenHeroDuration takes integer level returns real
return 0.00 + (0.00*level) // duration of the frozen buff applied on the heroes when projectile hits
endmethod
endif
static constant method reduction takes integer level returns boolean
return true // reduce the projectile size and damage per bounce
endmethod
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
endmodule
public struct Original extends array
// Frostbolt Ability ID
static constant integer ABILITY_ID = 'A003'
static constant method count takes integer level returns integer
return -1 + (2 *level) // bounce count of the projectile
endmethod
static if LIBRARY_ChilledBuffLibrary then
static constant method chilledLevel takes integer level returns integer
return level // level of the chilled buff applied on the units when projectile hits
endmethod
endif
implement Configuration
implement FrostboltConfigurationCloner
endstruct
public struct Mastered extends array
// Multi Frostbolt Ability ID
static constant integer ABILITY_ID = 'A00F'
static constant method count takes integer level returns integer
return 5 + (0 *level) // bounce count of the projectile
endmethod
static if LIBRARY_ChilledBuffLibrary then
static constant method chilledLevel takes integer level returns integer
return 3 // level of the chilled buff applied on the units when projectile hits
endmethod
endif
implement Configuration
implement FrostboltConfigurationCloner
endstruct
endlibrary
library Frostbolt /*
*/uses /*
*/UnitDex /* https://www.hiveworkshop.com/threads/248209/
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/SpellCloner /* https://www.hiveworkshop.com/threads/292751/
*/DummyRecycler /* https://www.hiveworkshop.com/threads/277659/
*/ZLibrary /* https://www.hiveworkshop.com/threads/237821/
*/optional ChilledBuffLibrary /* library is used to make this work on Chilled buff
*/optional FrozenBuffLibrary /* library is used to make this work on Frozen buff
*/
/**************************************************
* GLOBAL CONFIGURATION
***************************************************/
globals
private constant real PERIODIC_INTERVAL = 0.03125
private constant real MODEL_DEATH_TIME = 2.5
endglobals
// Filtration of the units that will be targeted on bounce
private function Filtration takes unit picked, player owner returns boolean
return IsUnitEnemy(picked,owner) and /*
*/ not IsUnitType(picked,UNIT_TYPE_STRUCTURE) and /*
*/ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
*/ UnitAlive(picked)
endfunction
/**************************************************
* END OF GLOBAL CONFIGURATION
***************************************************/
/*============================= CORE CODE =============================*/
native UnitAlive takes unit id returns boolean
globals
private constant integer DEFAULT_PITCH = 90
private integer array index
private group Group = CreateGroup()
endglobals
struct Frostbolt extends array
real size
real height
real distance
real range
real radius
real velocity
real damage
static if LIBRARY_ChilledBuffLibrary then
real chilledDuration
integer chilledLevel
endif
static if LIBRARY_FrozenBuffLibrary then
real frozenDuration
real frozenHeroDuration
endif
integer count
string projectileModel
string projectileModelOrigin
string hitModel
string hitModelOrigin
boolean reduction
attacktype attackType
damagetype damageType
weapontype weaponType
readonly unit caster
readonly unit target
readonly unit projectile
readonly integer level
readonly real sizeReduction
readonly real damageReduction
private effect model
readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL
private method onClonedSpellStart takes nothing returns thistype
local real x
local real y
local real angle
if .count > 0 then
set .caster = GetEventSpellCaster()
set .target = GetEventSpellTargetUnit()
set .level = GetEventSpellLevel()
set x = GetUnitX(.caster)
set y = GetUnitY(.caster)
set angle = Atan2(GetEventSpellTargetY()-y,GetEventSpellTargetX()-x)
set x = x + .distance*Cos(angle)
set y = y + .distance*Sin(angle)
set .projectile = GetRecycledDummy(x,y,.height,angle*bj_RADTODEG)
call SetUnitScale(.projectile,.size,0,0)
set .model = AddSpecialEffectTarget(.projectileModel,.projectile,.projectileModelOrigin)
if .reduction then
set .sizeReduction = .size/count
set .damageReduction = .damage/count
endif
return this
else
return 0
endif
endmethod
private method onClonedSpellPeriodic takes nothing returns boolean
static if LIBRARY_ChilledBuffLibrary then
local ChilledBuffLibrary_ChilledBuff chilled
endif
static if LIBRARY_FrozenBuffLibrary then
local FrozenBuffLibrary_FrozenBuff frozen
endif
local unit picked
local real x1 = GetUnitX(.projectile)
local real x2 = GetUnitX(.target)
local real y1 = GetUnitY(.projectile)
local real y2 = GetUnitY(.target)
local real z1 = GetUnitFlyHeight(.projectile) + GetTerrainZ(x1,y1)
local real z2 = GetUnitFlyHeight(.target) + .height + GetTerrainZ(x2,y2)
local real ang = Atan2(y2-y1,x2-x1)
local real dis = SquareRoot( (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1) )
local real phi = Atan2(z2-z1,dis)
local integer count = 0
call SetUnitX(.projectile,x1 + .velocity*Cos(ang)*Cos(phi) )
call SetUnitY(.projectile,y1 + .velocity*Sin(ang)*Cos(phi) )
call SetUnitFlyHeight(.projectile,z1 + .velocity*Sin(phi)-GetTerrainZ(x1,y1),0)
call SetUnitAnimationByIndex(.projectile,R2I(phi*bj_RADTODEG+0.5)+DEFAULT_PITCH)
call SetUnitFacing(.projectile,ang*bj_RADTODEG)
set dis = (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1)
if (dis <= .range*.range) then
call DestroyEffect( AddSpecialEffectTarget(.hitModel,.target,.hitModelOrigin) )
call UnitDamageTarget(.caster,.target,.damage,false,false,.attackType,.damageType,.weaponType)
static if LIBRARY_ChilledBuffLibrary then
if .chilledDuration > 0. then
set chilled = ChilledBuffLibrary_ChilledBuff.add(.caster,.target)
set chilled.duration = .chilledDuration
call SetUnitAbilityLevel(.target,ChilledBuffLibrary_ChilledBuff.RAWCODE,.chilledLevel)
endif
endif
static if LIBRARY_FrozenBuffLibrary then
if .frozenDuration > 0. or .frozenHeroDuration > 0. then
set frozen = FrozenBuffLibrary_FrozenBuff.add(.caster,.target)
if not IsUnitType(.target,UNIT_TYPE_HERO) then
set frozen.duration = .frozenDuration + FrozenBuffLibrary_FrozenBuff.DELAY
else
set frozen.duration = .frozenHeroDuration + FrozenBuffLibrary_FrozenBuff.DELAY
endif
endif
endif
set .count = .count - 1
if .count > 0 then
if .reduction then
set .size = .size - .sizeReduction
set .damage = .damage - .damageReduction
endif
call GroupEnumUnitsInRange(Group,x1,y1,.radius,null)
loop
set picked = FirstOfGroup(Group)
exitwhen picked == null
if Filtration( picked,GetOwningPlayer(.caster) ) and picked != .target then
set count = count + 1
set index[count] = GetUnitUserData(picked)
endif
call GroupRemoveUnit(Group,picked)
endloop
if count > 0 then
set z1 = GetUnitFlyHeight(.projectile)
call DestroyEffect(.model)
call DummyAddRecycleTimer(.projectile,MODEL_DEATH_TIME)
set .target = GetUnitById(index[GetRandomInt(1,count)])
set .model = null
set .projectile = null
set .projectile = GetRecycledDummy(x1,y1,z1,Atan2(GetUnitY(.target)-y1,GetUnitX(.target)-x1)*bj_RADTODEG)
call SetUnitScale(.projectile,.size,0,0)
set .model = AddSpecialEffectTarget(.projectileModel,.projectile,.projectileModelOrigin)
endif
else
return false
endif
endif
return UnitAlive(.target)
endmethod
private method onClonedSpellEnd takes nothing returns nothing
if .model != null and .projectile != null then
call DestroyEffect(.model)
call DummyAddRecycleTimer(.projectile,MODEL_DEATH_TIME)
set .model = null
set .projectile = null
endif
set .caster = null
set .target = null
endmethod
implement SpellCloner
endstruct
module FrostboltConfigurationCloner
private static method configHandler takes nothing returns nothing
local Frostbolt clone = SpellCloner.configuredInstance
// Configuration by constants
set clone.size = PROJECTILE_MODEL_SIZE
set clone.height = PROJECTILE_MODEL_HEIGHT
set clone.distance = PROJECTILE_MODEL_DISTANCE
set clone.range = PROJECTILE_DETECT_RANGE
set clone.projectileModel = PROJECTILE_MODEL
set clone.projectileModelOrigin = PROJECTILE_MODEL_ORIGIN
set clone.hitModel = PROJECTILE_HIT_MODEL
set clone.hitModelOrigin = PROJECTILE_HIT_MODEL_ORIGIN
set clone.attackType = ATTACK_TYPE
set clone.damageType = DAMAGE_TYPE
set clone.weaponType = WEAPON_TYPE
// Configuration with levels
set clone.count = count(GetEventSpellLevel())
set clone.radius = radius(GetEventSpellLevel())
set clone.velocity = velocity(GetEventSpellLevel())
set clone.damage = damage(GetEventSpellLevel())
static if LIBRARY_ChilledBuffLibrary then
set clone.chilledDuration = chilledDuration(GetEventSpellLevel())
set clone.chilledLevel = chilledLevel(GetEventSpellLevel())
endif
static if LIBRARY_FrozenBuffLibrary then
set clone.frozenDuration = frozenDuration(GetEventSpellLevel())
set clone.frozenHeroDuration = frozenHeroDuration(GetEventSpellLevel())
endif
set clone.reduction = reduction(GetEventSpellLevel())
endmethod
private static method onInit takes nothing returns nothing
call Frostbolt.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
endmethod
endmodule
endlibrary
library FrostbiteConfiguration uses Frostbite
private struct Configuration extends array
// Frostbite Ability ID
private static constant integer ABILITY_ID = 'A00C'
// The model spawned on the picked unit
private static constant string PICKED_MODEL = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
private static constant string PICKED_MODEL_ORIGIN = "origin"
// The attack type of inflicting damage
private static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
// The damage type of inflicting damage
private static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// The weapon type of inflicting damage
private static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
private static constant method radius takes integer level returns real
return 1000.00 + (0.00*level) // radius of the ability
endmethod
private static constant method damage takes integer level returns real
return 0.00 + (0.00*level) // damage of the ability
endmethod
private static constant method duration takes integer level returns real
return 0.00 + (2.50*level) // duration for the frozen units
endmethod
private static constant method heroDuration takes integer level returns real
return 0.00 + (1.25*level) // duration for the frozen heroes
endmethod
private static constant method manaCost takes integer level returns real
return 0.00 + (0.00*level) // reverted manacost when the ability fails (set to 0 to disable)
endmethod
private static constant method manaCostPerUnit takes integer level returns real
return 5.00 + (5.00*level) // costed mana per unit that will be frozen (set to 0 to disable)
endmethod
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement FrostbiteConfigurationCloner
endstruct
endlibrary
library Frostbite /*
*/uses /*
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/SpellCloner /* https://www.hiveworkshop.com/threads/292751/
*/FrozenBuffLibrary /* library is used to make this work on Frozen buff
*/optional ChilledBuffLibrary /* library is used to make this work on Chilled buff
*/
/**************************************************
* GLOBAL CONFIGURATION
***************************************************/
private module Configuration
// Order ID to cancel the ability cooldown (set to 0 to disable)
static constant integer STOP_ID = 851972
static if LIBRARY_ChilledBuffLibrary then
// Displays timed text when ability fails (there's no chilled unit)
static constant string COUNTFAIL_DISPLAY = "There are no chilled units nearby the caster."
// Displays timed text when ability fails (there's not enough mana)
static constant string MANAFAIL_DISPLAY = "Not enough mana to cast Frostbite towards the chilled unit."
else
// Displays timed text when ability fails (there's no picked unit)
static constant string COUNTFAIL_DISPLAY = "There are no picked units nearby the caster."
// Displays timed text when ability fails (there's not enough mana)
static constant string MANAFAIL_DISPLAY = "Not enough mana to cast Frostbite towards the picked unit."
endif
static constant string COUNTFAIL_COLOR = "|cffffcc00"
static constant real COUNTFAIL_DURATION = 1.0 // (set to 0 to disable)
static constant string MANAFAIL_COLOR = "|cffffcc00"
static constant real MANAFAIL_DURATION = 1.0 // (set to 0 to disable)
endmodule
// Filtration of the units that will be picked
private function Filtration takes unit picked, player owner returns boolean
return UnitAlive(picked) and IsUnitEnemy(picked,owner)
endfunction
/**************************************************
* END OF GLOBAL CONFIGURATION
***************************************************/
/*============================= CORE CODE =============================*/
native UnitAlive takes unit id returns boolean
globals
private group Group = CreateGroup()
private group Container = CreateGroup()
private sound Sound
endglobals
struct Frostbite extends array
implement Configuration
static real radius
static real damage
static real duration
static real heroDuration
static real manaCost
static real manaCostPerUnit
static string pickedModel
static string pickedModelOrigin
static attacktype attackType
static damagetype damageType
static weapontype weaponType
private method onClonedSpellStart takes nothing returns thistype
local unit picked
local real mana
local integer count = 0
static if LIBRARY_ChilledBuffLibrary then
local ChilledBuffLibrary_ChilledBuff chilled
endif
local FrozenBuffLibrary_FrozenBuff frozen
call GroupEnumUnitsInRange(Group,GetEventSpellTargetX(),GetEventSpellTargetY(),radius,null)
loop
set picked = FirstOfGroup(Group)
exitwhen picked == null
if Filtration(picked,GetEventSpellPlayer()) then
static if LIBRARY_ChilledBuffLibrary then
if Buff.get(GetEventSpellCaster(),picked,ChilledBuffLibrary_ChilledBuff.typeid) > 0 then
call GroupAddUnit(Container,picked)
set count = count + 1
endif
else
call GroupAddUnit(Container,picked)
set count = count + 1
endif
endif
call GroupRemoveUnit(Group,picked)
endloop
if count > 0 then
loop
set picked = FirstOfGroup(Container)
exitwhen picked == null
set mana = GetUnitState(GetEventSpellCaster(),UNIT_STATE_MANA)
if mana >= manaCostPerUnit then
call DestroyEffect( AddSpecialEffectTarget(pickedModel,picked,pickedModelOrigin) )
call UnitDamageTarget(GetEventSpellCaster(),picked,damage,false,false,attackType,damageType,weaponType)
if UnitAlive(picked) then
static if LIBRARY_ChilledBuffLibrary then
set chilled = Buff.get(GetEventSpellCaster(),picked,ChilledBuffLibrary_ChilledBuff.typeid)
call chilled.remove()
endif
set frozen = FrozenBuffLibrary_FrozenBuff.add(GetEventSpellCaster(),picked)
if not IsUnitType(picked,UNIT_TYPE_HERO) then
set frozen.duration = duration + FrozenBuffLibrary_FrozenBuff.DELAY
else
set frozen.duration = heroDuration + FrozenBuffLibrary_FrozenBuff.DELAY
endif
endif
call SetUnitState(GetEventSpellCaster(),UNIT_STATE_MANA,mana-manaCostPerUnit)
else
if MANAFAIL_DURATION > 0. and count != 0 then
if (GetLocalPlayer() == GetEventSpellPlayer()) then
call StartSound(Sound)
endif
call DisplayTimedTextToPlayer(GetEventSpellPlayer(),0,0,MANAFAIL_DURATION,MANAFAIL_COLOR+MANAFAIL_DISPLAY+"|r")
set count = 0
endif
endif
call GroupRemoveUnit(Container,picked)
endloop
else
if STOP_ID != 0 then
call IssueImmediateOrderById(GetEventSpellCaster(),STOP_ID)
endif
if manaCost > 0. then
set mana = GetUnitState(GetEventSpellCaster(),UNIT_STATE_MANA)
call SetUnitState(GetEventSpellCaster(),UNIT_STATE_MANA,mana+manaCost)
endif
if COUNTFAIL_DURATION > 0. then
if (GetLocalPlayer() == GetEventSpellPlayer()) then
call StartSound(Sound)
endif
call DisplayTimedTextToPlayer(GetEventSpellPlayer(),0,0,COUNTFAIL_DURATION,COUNTFAIL_COLOR+COUNTFAIL_DISPLAY+"|r")
endif
endif
return 0
endmethod
private static method onInit takes nothing returns nothing
set Sound = CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
endmethod
/*
* locust methods because of the SpellEvent interface
*/
private static constant real SPELL_PERIOD = 0.0
private method onClonedSpellPeriodic takes nothing returns boolean
return false
endmethod
private method onClonedSpellEnd takes nothing returns nothing
endmethod
implement SpellCloner
endstruct
module FrostbiteConfigurationCloner
private static method configHandler takes nothing returns nothing
// Configuration by constants
set Frostbite.pickedModel = PICKED_MODEL
set Frostbite.pickedModelOrigin = PICKED_MODEL_ORIGIN
set Frostbite.attackType = ATTACK_TYPE
set Frostbite.damageType = DAMAGE_TYPE
set Frostbite.weaponType = WEAPON_TYPE
// Configuration with levels
set Frostbite.radius = radius(GetEventSpellLevel())
set Frostbite.damage = damage(GetEventSpellLevel())
set Frostbite.duration = duration(GetEventSpellLevel())
set Frostbite.heroDuration = heroDuration(GetEventSpellLevel())
set Frostbite.manaCost = manaCost(GetEventSpellLevel())
set Frostbite.manaCostPerUnit = manaCostPerUnit(GetEventSpellLevel())
endmethod
private static method onInit takes nothing returns nothing
call Frostbite.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
endmethod
endmodule
endlibrary
library BlizzardConfiguration uses Blizzard optional ChilledBuffLibrary optional FrozenBuffLibrary
/* is using optional custom models
war3mapImported\Blizzard II Missile.mdx
*/
private struct Configuration extends array
// Blizzard Ability ID
private static constant integer ABILITY_ID = 'A002'
// Pitch of the shard
private static constant integer SHARD_PITCH = 45
// Initial height of the shard (Note: high value can cause crashes)
private static constant real SHARD_INITIAL_HEIGHT = 3000.00 // If you have WorldBounds, high value is safe.
// Let height of the shard on the ground (prevents model glitch)
private static constant real SHARD_LET_HEIGHT = 25.00
// The model of the shard
private static constant string SHARD_MODEL = "war3mapImported\\Blizzard II Missile.mdx"
private static constant string SHARD_MODEL_ORIGIN = "origin"
// The model spawned on the shard impact
private static constant string SHARD_IMPACT_MODEL = ""
// The model spawned on the picked unit hit by the shard impact
private static constant string SHARD_HIT_MODEL = "Abilities\\Weapons\\ZigguratFrostMissile\\ZigguratFrostMissile.mdl"
private static constant string SHARD_HIT_MODEL_ORIGIN = "chest"
// The model spawned on the picked unit hit by the shard impact while frozen
private static constant string FROZEN_SHATTER_MODEL = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
private static constant string FROZEN_SHATTER_MODEL_ORIGIN = "origin"
// The attack type of inflicting damage
private static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
// The damage type of inflicting damage
private static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// The weapon type of inflicting damage
private static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
private static constant method radius takes integer level returns real
return 150.00 + (50.00*level) // radius of the ability
endmethod
private static constant method duration takes integer level returns real
return 10.00 + (0.00*level) // duration of the ability
endmethod
private static constant method shardMinSpawnTime takes integer level returns real
return 0.2 - (0.025*level) // minimum interval of spawning the shard
endmethod
private static constant method shardMaxSpawnTime takes integer level returns real
return 0.1 - (0.05*level) // maximum interval of spawning the shard
endmethod
private static constant method shardSpawnCount takes integer level returns integer
return 1 + (0 *level) // the number of shard spawned per interval
endmethod
private static constant method shardMinSize takes integer level returns real
return 0.70 + (0.10*level) // minimum size of the shard
endmethod
private static constant method shardMaxSize takes integer level returns real
return 1.10 + (0.10*level) // maximum size of the shard
endmethod
private static constant method shardMinLandTime takes integer level returns real
return 1.00 - (0.00*level) // minimum landing time of the shard
endmethod
private static constant method shardMaxLandTime takes integer level returns real
return 1.50 - (0.00*level) // maximum landing time of the shard
endmethod
/* Each shard's size randomization varies from shardMinSize and shardMaxsize
* To make it more realistic, its radius and damage must reflect to its size
* So this is a multiplier configuration where:
* radius = shardSize * radiusMultiplier , damage = shardSize * damageMultiplier
* (Example: 100.00 = 1.00 * 100.00)
*/
private static constant method radiusMultiplier takes integer level returns real
return 75.00 + (0.00*level) // radius multiplier that reflects to the shard size
endmethod
private static constant method damageMultiplier takes integer level returns real
return 25.00 + (0.00*level) // damage multiplier that reflects to the shard size
endmethod
/* If picked units are in frozen state, the shard impact shatters them,
* dealing a radius damage for each unit (breaks them from the frozen state if set to true)
*/
private static constant method frozenShatterBreak takes integer level returns boolean
return true // breaks the unit from the frozen state (set to false to disable)
endmethod
private static constant method frozenShatterRadius takes integer level returns real
return 100.00 + (25.00*level) // radius of the shatter
endmethod
private static constant method frozenShatterDamage takes integer level returns real
return 0.00 + (50.00*level) // damage of the shatter
endmethod
// Of course, let me provide you these awesome benefits (set to 0 to disable)
static if LIBRARY_ChilledBuffLibrary then
static constant method chilledDuration takes integer level returns real
return 0.00 + (0.00*level) // duration of the chilled buff applied on the radius of shard per impact
endmethod
static constant method chilledLevel takes integer level returns integer
return level // level of the chilled buff applied on the radius of shard per impact
endmethod
endif
static if LIBRARY_FrozenBuffLibrary then
static constant method frozenDuration takes integer level returns real
return 0.00 + (0.00*level) // duration of the frozen buff applied on the units in radius of shard per impact
endmethod
static constant method frozenHeroDuration takes integer level returns real
return 0.00 + (0.00*level) // duration of the frozen buff applied on the heroes in radius of shard per impact
endmethod
endif
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement BlizzardConfigurationCloner
endstruct
endlibrary
library Blizzard /*
*/uses /*
*/Alloc /* library is used to make this work on instances efficiently
*/LinkedList /* library is used to make this work on a list data structure
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/SpellCloner /* https://www.hiveworkshop.com/threads/292751/
*/DummyRecycler /* https://www.hiveworkshop.com/threads/277659/
*/ZLibrary /* https://www.hiveworkshop.com/threads/237821/
*/optional WorldBounds /* https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds
*/optional ChilledBuffLibrary /* library is used to make this work on Chilled buff
*/optional FrozenBuffLibrary /* library is used to make this work on Frozen buff
*/
/**************************************************
* GLOBAL CONFIGURATION
***************************************************/
globals
// For those who could notice performance drops: 0.03125 is the default value
// Otherwise, this prevents rough animation, making a fluid landing of shards
private constant real PERIODIC_INTERVAL = 0.015625
private constant real MODEL_DEATH_TIME = 5.0
endglobals
private module DestroyTreesConfiguration
static constant boolean DESTROY_TREES = true
// Rawcode of the harvest ability used for detecting if a destructable is a tree
static constant integer HARVEST_ID = 'Ahar'
// Rawcode of the unit that will use the harvest ability
static constant integer HARVESTER_ID = 'hpea'
endmodule
// Filtration of the units that will be damaged, chilled, or frozen
private function Filtration takes unit picked, player owner returns boolean
return IsUnitEnemy(picked,owner) and /*
*/ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
*/ UnitAlive(picked)
endfunction
/**************************************************
* END OF GLOBAL CONFIGURATION
***************************************************/
/*============================= CORE CODE =============================*/
native UnitAlive takes unit id returns boolean
globals
private constant integer DEFAULT_PITCH = 90
private group Group = CreateGroup()
private group shatterGroup = CreateGroup()
endglobals
private struct Shard extends array
implement Alloc
implement StaticList
implement DestroyTreesConfiguration
readonly real x
readonly real y
readonly real z
readonly real ang
readonly real velocity
readonly real size
readonly unit shard
readonly unit source
readonly player owner
readonly real radius
readonly real damage
private effect model
readonly Blizzard main
private static timer time = CreateTimer()
static if thistype.DESTROY_TREES then
private static real X
private static real Y
private static rect Region
private static real Radius
private static unit harvester
private static method isTree takes destructable o returns boolean
return IssueTargetOrderById(harvester,852018,o) and IssueImmediateOrderById(harvester,851972)
endmethod
private static method destroyTree takes nothing returns nothing
local destructable o = GetEnumDestructable()
local real x = GetWidgetX(o) - X
local real y = GetWidgetY(o) - Y
if x*x + y*y <= Radius and isTree(o) then
call KillDestructable(o)
endif
set o = null
endmethod
endif
method destroy takes nothing returns nothing
call this.deallocate()
call SetUnitAnimationByIndex(.shard,DEFAULT_PITCH)
call DestroyEffect(.model)
call DummyAddRecycleTimer(.shard,MODEL_DEATH_TIME)
set .model = null
set .shard = null
set .source = null
call remove(this)
if empty then
call PauseTimer(time)
endif
endmethod
static method periodic takes nothing returns nothing
static if LIBRARY_ChilledBuffLibrary then
local ChilledBuffLibrary_ChilledBuff chilled
endif
static if LIBRARY_FrozenBuffLibrary then
local FrozenBuffLibrary_FrozenBuff frozen
endif
local unit picked
local unit shatter
local thistype this = front
loop
exitwhen this == 0
set .x = .x + .velocity*Cos(.ang)
set .y = .y + .velocity*Sin(.ang)
set .z = .z - .velocity
static if LIBRARY_WorldBounds then
if .x < WorldBounds.maxX and .x > WorldBounds.minX and .y < WorldBounds.maxY and .y > WorldBounds.minY then
call SetUnitX(.shard,.x)
call SetUnitY(.shard,.y)
call SetUnitFlyHeight(.shard,.z - GetTerrainZ(.x,.y),0)
endif
else
call SetUnitX(.shard,.x)
call SetUnitY(.shard,.y)
call SetUnitFlyHeight(.shard,.z - GetTerrainZ(.x,.y),0)
endif
if .z - GetTerrainZ(.x,.y) <= .main.letHeight then
call DestroyEffect( AddSpecialEffect(.main.impactModel, .x, .y) )
call GroupEnumUnitsInRange(Group,.x,.y,.radius,null)
loop
set picked = FirstOfGroup(Group)
exitwhen picked == null
if Filtration(picked,.owner) then
call DestroyEffect( AddSpecialEffectTarget(.main.hitModel,picked,.main.hitModelOrigin) )
set frozen = Buff.get(null,picked,FrozenBuffLibrary_FrozenBuff.typeid)
if frozen > 0 then
if .main.shatterBreak then
call frozen.remove()
endif
call DestroyEffect( AddSpecialEffectTarget(.main.shatterModel,picked,.main.shatterModelOrigin) )
call GroupEnumUnitsInRange(shatterGroup,GetUnitX(picked),GetUnitY(picked),.main.shatterRadius,null)
loop
set shatter = FirstOfGroup(shatterGroup)
exitwhen shatter == null
if Filtration(shatter,.owner) then
call UnitDamageTarget(.source,shatter,.main.shatterDamage,false,false,.main.attackType,.main.damageType,.main.weaponType)
endif
call GroupRemoveUnit(shatterGroup,shatter)
endloop
endif
call UnitDamageTarget(.source,picked,.damage,false,false,.main.attackType,.main.damageType,.main.weaponType)
if UnitAlive(picked) then
static if LIBRARY_ChilledBuffLibrary then
if .main.chilledDuration > 0. then
set chilled = ChilledBuffLibrary_ChilledBuff.add(.source,picked)
set chilled.duration = .main.chilledDuration
call SetUnitAbilityLevel(picked,ChilledBuffLibrary_ChilledBuff.RAWCODE,.main.chilledLevel)
endif
endif
static if LIBRARY_FrozenBuffLibrary then
if .main.frozenDuration > 0. or .main.frozenHeroDuration > 0. then
set frozen = FrozenBuffLibrary_FrozenBuff.add(.source,picked)
if not IsUnitType(picked,UNIT_TYPE_HERO) then
set frozen.duration = .main.frozenDuration + FrozenBuffLibrary_FrozenBuff.DELAY
else
set frozen.duration = .main.frozenHeroDuration + FrozenBuffLibrary_FrozenBuff.DELAY
endif
endif
endif
endif
endif
call GroupRemoveUnit(Group,picked)
endloop
static if thistype.DESTROY_TREES then
set X = .x
set Y = .y
set Radius = .radius*.radius
call SetRect(Region,.x - .radius,.y - .radius,.x + .radius,.y + .radius)
call EnumDestructablesInRect(Region,null,function thistype.destroyTree)
endif
call this.destroy()
endif
set this = next
endloop
endmethod
static method create takes Blizzard source, real x1, real y1, real x2, real y2 returns thistype
local real x = x2 - x1
local real y = y2 - y1
local real sourceZ = GetTerrainZ(x1,y1)
local real targetZ = GetTerrainZ(x2,y2)
local real z = targetZ - sourceZ
local real dis
local thistype this = allocate()
if empty then
call TimerStart(time,PERIODIC_INTERVAL,true,function thistype.periodic)
endif
call pushBack(this)
set .main = source
set .source = source.caster
set .owner = GetOwningPlayer(.source)
set .x = x1
set .y = y1
set .z = source.initialHeight + targetZ
set .ang = Atan2(y,x)
set dis = SquareRoot(x*x + y*y + z*z)
static if LIBRARY_WorldBounds then
if x1 > WorldBounds.maxX then
set x1 = WorldBounds.maxX
elseif x1 < WorldBounds.minX then
set x1 = WorldBounds.minX
endif
if y1 > WorldBounds.maxY then
set y1 = WorldBounds.maxY
elseif y1 < WorldBounds.minY then
set y1 = WorldBounds.minY
endif
endif
set .size = GetRandomReal(source.minSize,source.maxSize)
set .shard = GetRecycledDummy(x1,y1,.z,.ang*bj_RADTODEG)
call SetUnitScale(.shard,.size,0,0)
call SetUnitAnimationByIndex(.shard,source.pitch)
set .model = AddSpecialEffectTarget(source.shardModel,.shard,source.shardModelOrigin)
set .velocity = dis/(GetRandomReal(source.minLandTime,source.maxLandTime)/PERIODIC_INTERVAL)
set .radius = .size * source.radiusMultiplier
set .damage = .size * source.damageMultiplier
return this
endmethod
private static method onInit takes nothing returns nothing
static if thistype.DESTROY_TREES then
set Region = Rect(0,0,0,0)
set harvester = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA),HARVESTER_ID,0,0,0)
call UnitAddAbility(harvester,'Aloc')
call UnitAddAbility(harvester,HARVEST_ID)
call ShowUnit(harvester, false)
endif
endmethod
endstruct
struct Blizzard extends array
real radius
real duration
integer count
integer pitch
real minSize
real maxSize
real initialHeight
real letHeight
real minSpawnTime
real maxSpawnTime
real minLandTime
real maxLandTime
string shardModel
string shardModelOrigin
string impactModel
string hitModel
string hitModelOrigin
string shatterModel
string shatterModelOrigin
real shatterRadius
real shatterDamage
real radiusMultiplier
real damageMultiplier
static if LIBRARY_ChilledBuffLibrary then
real chilledDuration
integer chilledLevel
endif
static if LIBRARY_FrozenBuffLibrary then
real frozenDuration
real frozenHeroDuration
endif
attacktype attackType
damagetype damageType
weapontype weaponType
boolean shatterBreak
readonly real x
readonly real y
readonly unit caster
readonly integer level
readonly real remaining
readonly real remainingInterval
readonly real facing
readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL
private method onClonedSpellStart takes nothing returns thistype
set .caster = GetEventSpellCaster()
set .level = GetEventSpellLevel()
set .x = GetEventSpellTargetX()
set .y = GetEventSpellTargetY()
set .remaining = .duration
set .remainingInterval = 0.
set .facing = GetUnitFacing(.caster)*bj_DEGTORAD
return this
endmethod
private method onClonedSpellPeriodic takes nothing returns boolean
local Shard shard
local real x1
local real x2
local real y1
local real y2
local real ang
local real dis
local integer count
set .remaining = .remaining - PERIODIC_INTERVAL
if .remaining > 0. and UnitAlive(.caster) then
set .remainingInterval = .remainingInterval + PERIODIC_INTERVAL
if .remainingInterval > GetRandomReal(.minSpawnTime,.maxSpawnTime) then
set count = .count
loop
set ang = GetRandomReal(-bj_PI,bj_PI)
set dis = GetRandomReal(0.,.radius)
set x1 = .x + dis*Cos(ang)
set y1 = .y + dis*Sin(ang)
set x2 = x1 - .initialHeight*Cos(.facing)
set y2 = y1 - .initialHeight*Sin(.facing)
set shard = Shard.create(this,x2,y2,x1,y1)
set count = count - 1
exitwhen count == 0
endloop
set .remainingInterval = 0.
endif
else
return false
endif
return true
endmethod
private method onClonedSpellEnd takes nothing returns nothing
set .caster = null
endmethod
implement SpellCloner
endstruct
module BlizzardConfigurationCloner
private static method configHandler takes nothing returns nothing
local Blizzard clone = SpellCloner.configuredInstance
// Configuration by constants
set clone.pitch = SHARD_PITCH
set clone.initialHeight = SHARD_INITIAL_HEIGHT
set clone.letHeight = SHARD_LET_HEIGHT
set clone.shardModel = SHARD_MODEL
set clone.shardModelOrigin = SHARD_MODEL_ORIGIN
set clone.impactModel = SHARD_IMPACT_MODEL
set clone.hitModel = SHARD_HIT_MODEL
set clone.hitModelOrigin = SHARD_HIT_MODEL_ORIGIN
set clone.shatterModel = FROZEN_SHATTER_MODEL
set clone.shatterModelOrigin = FROZEN_SHATTER_MODEL_ORIGIN
set clone.attackType = ATTACK_TYPE
set clone.damageType = DAMAGE_TYPE
set clone.weaponType = WEAPON_TYPE
// Configuration with levels
set clone.radius = radius(GetEventSpellLevel())
set clone.duration = duration(GetEventSpellLevel())
set clone.minSpawnTime = shardMinSpawnTime(GetEventSpellLevel())
set clone.maxSpawnTime = shardMaxSpawnTime(GetEventSpellLevel())
set clone.count = shardSpawnCount(GetEventSpellLevel())
set clone.minSize = shardMinSize(GetEventSpellLevel())
set clone.maxSize = shardMaxSize(GetEventSpellLevel())
set clone.minLandTime = shardMinLandTime(GetEventSpellLevel())
set clone.maxLandTime = shardMaxLandTime(GetEventSpellLevel())
set clone.radiusMultiplier = radiusMultiplier(GetEventSpellLevel())
set clone.damageMultiplier = damageMultiplier(GetEventSpellLevel())
set clone.shatterBreak = frozenShatterBreak(GetEventSpellLevel())
set clone.shatterRadius = frozenShatterRadius(GetEventSpellLevel())
set clone.shatterDamage = frozenShatterDamage(GetEventSpellLevel())
static if LIBRARY_ChilledBuffLibrary then
set clone.chilledDuration = chilledDuration(GetEventSpellLevel())
set clone.chilledLevel = chilledLevel(GetEventSpellLevel())
endif
static if LIBRARY_FrozenBuffLibrary then
set clone.frozenDuration = frozenDuration(GetEventSpellLevel())
set clone.frozenHeroDuration = frozenHeroDuration(GetEventSpellLevel())
endif
endmethod
private static method onInit takes nothing returns nothing
call Blizzard.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
endmethod
endmodule
endlibrary
library IceStormConfiguration uses IceStorm optional ChilledBuffLibrary optional FrozenBuffLibrary
/* is using optional custom models
war3mapImported\FrozenShell.MDX
*/
private struct Configuration extends array
// Ice Storm Ability ID
private static constant integer ABILITY_ID = 'A005'
// The size of the primary projectile
private static constant real PRIMARY_SIZE = 0.50
// The size of the secondary projectile
private static constant real SECONDARY_SIZE = 1.25
// The height of the primary projectile
private static constant real PRIMARY_HEIGHT = 50.00
// The height of the secondary projectile
private static constant real SECONDARY_HEIGHT = 50.00
// The model of the primary projectile
private static constant string PRIMARY_MODEL = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
private static constant string PRIMARY_MODEL_ORIGIN = "origin"
// The model of the secondary projectile
private static constant string SECONDARY_MODEL = "Abilities\\Weapons\\ZigguratFrostMissile\\ZigguratFrostMissile.mdl"
private static constant string SECONDARY_MODEL_ORIGIN = "origin"
// The model spawned on the caster
private static constant string CASTER_MODEL = "war3mapImported\\FrozenShell.MDX"
private static constant string CASTER_MODEL_ORIGIN = "chest"
// The model spawned on the picked unit when inflicting damage
private static constant string PICKED_MODEL = "Abilities\\Weapons\\ZigguratFrostMissile\\ZigguratFrostMissile.mdl"
private static constant string PICKED_MODEL_ORIGIN = "chest"
// The primary model spawning randomly on the ground within the radius per period
private static constant string PRIMARY_PERIODIC_MODEL = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
// The secondary model spawning randomly on the ground within the radius per period
private static constant string SECONDARY_PERIODIC_MODEL = "Abilities\\Weapons\\ZigguratFrostMissile\\ZigguratFrostMissile.mdl"
// The attack type of inflicting damage
private static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
// The damage type of inflicting damage
private static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// The weapon type of inflicting damage
private static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
private static constant method radius takes integer level returns real
return 750.00 + (0.00*level) // radius of the ability
endmethod
private static constant method delay takes integer level returns real
return 5.00 - (1.50*level) // time it takes to cover the radius
endmethod
private static constant method duration takes integer level returns real
return 10.00 + (10.00*level) // duration of the ability (starts ticking after delay time)
endmethod
private static constant method primaryCount takes integer level returns integer
return 25 + (0 *level) // maximum number of the primary projectile
endmethod
private static constant method rotation takes integer level returns real
return 2.50 + (0.00*level) // rotation of the primary projectile
endmethod
private static constant method distance takes integer level returns real
return 100.00 + (0.00*level) // minimum distance of the secondary projectile
endmethod
private static constant method minInterval takes integer level returns real
return 0.30 - (0.05*level) // minimum spawning interval of the secondary projectile
endmethod
private static constant method maxInterval takes integer level returns real
return 0.80 - (0.10*level) // maximum spawning interval of the secondary projectile
endmethod
private static constant method secondaryCount takes integer level returns integer
return 5 + (5 *level) // maximum number of the secondary projectile
endmethod
private static constant method minRotation takes integer level returns real
return 2.50 + (0.00*level) // minimum rotation of the secondary projectile
endmethod
private static constant method maxRotation takes integer level returns real
return 5.00 + (0.00*level) // maximum rotation of the secondary projectile
endmethod
private static constant method collision takes integer level returns real
return 50.00 + (0.00*level) // collision of the secondary projectile
endmethod
private static constant method damage takes integer level returns real
return 150.00 + (0.00*level) // inflicting damage of the secondary projectile in collision per second
endmethod
static if LIBRARY_ChilledBuffLibrary then
private static constant method chilledRadius takes integer level returns boolean
return true // applying chilled buff on the radius
endmethod
endif
static if LIBRARY_FrozenBuffLibrary then
private static constant method frozenRadius takes integer level returns boolean
return false // applying frozen buff on the radius
endmethod
endif
private static constant method rate takes integer level returns real
return 20.00 + (10.00*level) // spawning rate of the periodical models
endmethod
private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
implement IceStormConfigurationCloner
endstruct
endlibrary
library IceStorm /*
*/uses /*
*/Alloc /* library is used to make this work on instances efficiently
*/LinkedList /* library is used to make this work on a list data structure
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/SpellCloner /* https://www.hiveworkshop.com/threads/292751/
*/DummyRecycler /* https://www.hiveworkshop.com/threads/277659/
*/optional WorldBounds /* https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds
*/optional ChilledBuffLibrary /* library is used to make this work on Chilled buff
*/optional FrozenBuffLibrary /* library is used to make this work on Frozen buff
*/
/**************************************************
* GLOBAL CONFIGURATION
***************************************************/
globals
private constant real PERIODIC_INTERVAL = 0.03125
private constant real MODEL_DEATH_TIME = 2.5
endglobals
// Filtration of the units that will be damaged, chilled, or frozen
private function Filtration takes unit picked, player owner returns boolean
return IsUnitEnemy(picked,owner) and /*
*/ not IsUnitType(picked,UNIT_TYPE_MAGIC_IMMUNE) and /*
*/ UnitAlive(picked)
endfunction
/**************************************************
* END OF GLOBAL CONFIGURATION
***************************************************/
/*============================= CORE CODE =============================*/
native UnitAlive takes unit id returns boolean
globals
private constant real TAU = 2.*bj_PI
private group Group = CreateGroup()
private key PRIMARY
private key SECONDARY
endglobals
private struct Node extends array
implement Alloc
endstruct
private struct Projectile extends array
integer stage
unit projectile
real angle
real rotation
real curDis
real maxDis
effect model
method operator x takes nothing returns real
return GetUnitX(.projectile)
endmethod
method operator y takes nothing returns real
return GetUnitY(.projectile)
endmethod
method destroy takes nothing returns nothing
call DestroyEffect(.model)
call DummyAddRecycleTimer(.projectile,MODEL_DEATH_TIME)
set .model = null
set .projectile = null
call Node(this).deallocate()
endmethod
static method create takes real x, real y, real z, real size, real facing, string model, string modelOrigin returns thistype
local thistype node = Node.allocate()
set node.projectile = GetRecycledDummy(x,y,z,facing*bj_RADTODEG)
set node.model = AddSpecialEffectTarget(model,node.projectile,modelOrigin)
call SetUnitScale(node.projectile,size,0,0)
return node
endmethod
endstruct
private struct ProjectileList extends array
private static method onRemove takes thistype node returns nothing
call Projectile(node).destroy()
endmethod
private static method allocate takes nothing returns thistype
return Node.allocate()
endmethod
private method deallocate takes nothing returns nothing
call Node(this).deallocate()
endmethod
implement InstantiatedList
endstruct
struct IceStorm extends array
real radius
real delay
real duration
real rotation
real distance
real minInterval
real maxInterval
real minRotation
real maxRotation
real collision
real damage
real rate
real primarySize
real secondarySize
real primaryHeight
real secondaryHeight
string primaryModel
string primaryModelOrigin
string secondaryModel
string secondaryModelOrigin
string casterModel
string casterModelOrigin
string pickedModel
string pickedModelOrigin
string periodicPrimaryModel
string periodicSecondaryModel
integer primaryCount
integer secondaryCount
static if LIBRARY_ChilledBuffLibrary then
boolean chilledRadius
endif
static if LIBRARY_FrozenBuffLibrary then
boolean frozenRadius
endif
attacktype attackType
damagetype damageType
weapontype weaponType
readonly unit caster
readonly player owner
readonly integer level
readonly integer counter
readonly real remainingDelay
readonly real remainingDuration
readonly real randomizeInterval
readonly real remainingInterval
readonly real primaryRadius
readonly real primaryVelocity
private effect model
private ProjectileList list
readonly static constant real SPELL_PERIOD = PERIODIC_INTERVAL
private method onClonedSpellStart takes nothing returns thistype
local real x1
local real x2
local real y1
local real y2
local real angle
local real delta
local real facing
local integer count
local Projectile node
set .caster = GetEventSpellCaster()
set .owner = GetEventSpellPlayer()
set .level = GetEventSpellLevel()
set .list = ProjectileList.create()
set .model = AddSpecialEffectTarget(.casterModel,.caster,.casterModelOrigin)
set x1 = GetUnitX(.caster)
set y1 = GetUnitY(.caster)
set facing = GetUnitFacing(.caster)*bj_DEGTORAD
set angle = facing
set count = .primaryCount
set delta = (TAU/count)
if GetRandomReal(0,1.0) <= .5 then
set .rotation = -.rotation
endif
loop
set x2 = x1 + 0.*Cos(angle)
set y2 = y1 + 0.*Sin(angle)
set node = Projectile.create(x2,y2,.primaryHeight,.primarySize,angle,.primaryModel,.primaryModelOrigin)
set node.angle = angle
set node.rotation = .rotation
set node.stage = PRIMARY
call .list.pushFront(node)
set angle = angle + delta
set count = count - 1
exitwhen count == 0
endloop
set .counter = .secondaryCount
set .remainingDelay = .delay
set .remainingDuration = .duration
set .randomizeInterval = GetRandomReal(.minInterval,.maxInterval)
set .remainingInterval = .randomizeInterval
set .primaryRadius = 0.
set .primaryVelocity = .radius/(.delay/PERIODIC_INTERVAL)
return this
endmethod
private method onClonedSpellPeriodic takes nothing returns boolean
local Projectile node
local ProjectileList listNode
static if LIBRARY_ChilledBuffLibrary then
local ChilledBuffLibrary_ChilledBuff chilled
endif
static if LIBRARY_FrozenBuffLibrary then
local FrozenBuffLibrary_FrozenBuff frozen
endif
local real x = GetUnitX(.caster)
local real y = GetUnitY(.caster)
local real x1
local real y1
local real x2
local real y2
local real ang
local real dis
local unit picked
if .primaryRadius < .radius then
set .primaryRadius = .primaryRadius + .primaryVelocity
endif
set listNode = this.list.next
loop
exitwhen listNode == this.list
if Projectile(listNode).stage == PRIMARY then
set Projectile(listNode).angle = Projectile(listNode).angle + Projectile(listNode).rotation
set x1 = x + .primaryRadius*Cos(Projectile(listNode).angle)
set y1 = y + .primaryRadius*Sin(Projectile(listNode).angle)
static if LIBRARY_WorldBounds then
if x1 < WorldBounds.maxX and x1 > WorldBounds.minX and y1 < WorldBounds.maxY and y1 > WorldBounds.minY then
call SetUnitX(Projectile(listNode).projectile,x1)
call SetUnitY(Projectile(listNode).projectile,y1)
call SetUnitFacing(Projectile(listNode).projectile,Projectile(listNode).angle*bj_RADTODEG)
endif
else
call SetUnitX(Projectile(listNode).projectile,x1)
call SetUnitY(Projectile(listNode).projectile,y1)
call SetUnitFacing(Projectile(listNode).projectile,Projectile(listNode).angle*bj_RADTODEG)
endif
elseif Projectile(listNode).stage == SECONDARY then
if Projectile(listNode).curDis < Projectile(listNode).maxDis then
set Projectile(listNode).curDis = Projectile(listNode).curDis + .primaryVelocity
endif
set Projectile(listNode).angle = Projectile(listNode).angle + Projectile(listNode).rotation
set x2 = x + Projectile(listNode).curDis*Cos(Projectile(listNode).angle)
set y2 = y + Projectile(listNode).curDis*Sin(Projectile(listNode).angle)
static if LIBRARY_WorldBounds then
if x2 < WorldBounds.maxX and x2 > WorldBounds.minX and y2 < WorldBounds.maxY and y2 > WorldBounds.minY then
call SetUnitX(Projectile(listNode).projectile,x2)
call SetUnitY(Projectile(listNode).projectile,y2)
call SetUnitFacing(Projectile(listNode).projectile,Projectile(listNode).angle*bj_RADTODEG)
endif
else
call SetUnitX(Projectile(listNode).projectile,x2)
call SetUnitY(Projectile(listNode).projectile,y2)
call SetUnitFacing(Projectile(listNode).projectile,Projectile(listNode).angle*bj_RADTODEG)
endif
call GroupEnumUnitsInRange(Group,Projectile(listNode).x,Projectile(listNode).y,.collision,null)
loop
set picked = FirstOfGroup(Group)
exitwhen picked == null
if Filtration(picked,.owner) then
call DestroyEffect( AddSpecialEffectTarget(.pickedModel,picked,.pickedModelOrigin) )
call UnitDamageTarget(.caster,picked,.damage,false,false,.attackType,.damageType,.weaponType)
endif
call GroupRemoveUnit(Group,picked)
endloop
endif
set listNode = listNode.next
endloop
if .counter > 0 then
if .remainingInterval <= 0. then
set ang = GetRandomReal(-bj_PI,bj_PI)
set node = Projectile.create(x + 0.*Cos(ang),y + 0.*Sin(ang),.secondaryHeight,.secondarySize,ang,.secondaryModel,.secondaryModelOrigin)
set node.angle = ang
set node.curDis = 0.
set node.maxDis = GetRandomReal(.distance,.primaryRadius)
set node.stage = SECONDARY
if .rotation > 0. then
set .rotation = GetRandomReal(.minRotation,.maxRotation)
else
set .rotation = -GetRandomReal(.minRotation,.maxRotation)
endif
set node.rotation = .rotation
call .list.pushFront(node)
set .randomizeInterval = GetRandomReal(.minInterval,.maxInterval)
set .remainingInterval = .randomizeInterval
set .counter = .counter - 1
else
set .remainingInterval = .remainingInterval - PERIODIC_INTERVAL
endif
endif
call GroupEnumUnitsInRange(Group,x,y,.primaryRadius,null)
loop
set picked = FirstOfGroup(Group)
exitwhen picked == null
if Filtration(picked,.owner) then
static if LIBRARY_ChilledBuffLibrary then
if .chilledRadius then
set chilled = ChilledBuffLibrary_ChilledBuff.add(.caster,picked)
set chilled.duration = 1.
if GetUnitAbilityLevel(picked,ChilledBuffLibrary_ChilledBuff.RAWCODE) != .level then
call SetUnitAbilityLevel(picked,ChilledBuffLibrary_ChilledBuff.RAWCODE,.level)
endif
endif
endif
static if LIBRARY_FrozenBuffLibrary then
if .frozenRadius then
set frozen = FrozenBuffLibrary_FrozenBuff.add(.caster,picked)
set frozen.duration = 1. + FrozenBuffLibrary_FrozenBuff.DELAY
endif
endif
endif
call GroupRemoveUnit(Group,picked)
endloop
if GetRandomReal(0,100) <= .rate then
set ang = GetRandomReal(-bj_PI,bj_PI)
set dis = GetRandomReal(0,.primaryRadius)
call DestroyEffect(AddSpecialEffect(.periodicPrimaryModel,x + dis*Cos(ang),y + dis*Sin(ang)))
endif
if GetRandomReal(0,100) <= .rate then
set ang = GetRandomReal(-bj_PI,bj_PI)
set dis = GetRandomReal(0,.primaryRadius)
call DestroyEffect(AddSpecialEffect(.periodicSecondaryModel,x + dis*Cos(ang),y + dis*Sin(ang)))
endif
if .remainingDelay > 0. then
set .remainingDelay = .remainingDelay - PERIODIC_INTERVAL
else
set .remainingDuration = .remainingDuration - PERIODIC_INTERVAL
endif
return UnitAlive(.caster) and .remainingDuration > 0.
endmethod
private method onClonedSpellEnd takes nothing returns nothing
call this.list.destroy()
call DestroyEffect(.model)
set .model = null
set .caster = null
endmethod
implement SpellCloner
endstruct
module IceStormConfigurationCloner
private static method configHandler takes nothing returns nothing
local IceStorm clone = SpellCloner.configuredInstance
// Configuration by constants
set clone.primarySize = PRIMARY_SIZE
set clone.primaryHeight = PRIMARY_HEIGHT
set clone.secondarySize = SECONDARY_SIZE
set clone.secondaryHeight = SECONDARY_HEIGHT
set clone.primaryModel = PRIMARY_MODEL
set clone.primaryModelOrigin = PRIMARY_MODEL_ORIGIN
set clone.secondaryModel = SECONDARY_MODEL
set clone.secondaryModelOrigin = SECONDARY_MODEL_ORIGIN
set clone.casterModel = CASTER_MODEL
set clone.casterModelOrigin = CASTER_MODEL_ORIGIN
set clone.pickedModel = PICKED_MODEL
set clone.pickedModelOrigin = PICKED_MODEL_ORIGIN
set clone.periodicPrimaryModel = PRIMARY_PERIODIC_MODEL
set clone.periodicSecondaryModel = SECONDARY_PERIODIC_MODEL
set clone.attackType = ATTACK_TYPE
set clone.damageType = DAMAGE_TYPE
set clone.weaponType = WEAPON_TYPE
// Configuration with levels
set clone.radius = radius(GetEventSpellLevel())
set clone.delay = delay(GetEventSpellLevel())
set clone.duration = duration(GetEventSpellLevel())
set clone.primaryCount = primaryCount(GetEventSpellLevel())
set clone.rotation = rotation(GetEventSpellLevel())*bj_DEGTORAD
set clone.distance = distance(GetEventSpellLevel())
set clone.minInterval = minInterval(GetEventSpellLevel())
set clone.maxInterval = maxInterval(GetEventSpellLevel())
set clone.secondaryCount = secondaryCount(GetEventSpellLevel())
set clone.minRotation = minRotation(GetEventSpellLevel())*bj_DEGTORAD
set clone.maxRotation = maxRotation(GetEventSpellLevel())*bj_DEGTORAD
set clone.collision = collision(GetEventSpellLevel())
set clone.damage = damage(GetEventSpellLevel())/(1./PERIODIC_INTERVAL)
static if LIBRARY_ChilledBuffLibrary then
set clone.chilledRadius = chilledRadius(GetEventSpellLevel())
endif
static if LIBRARY_FrozenBuffLibrary then
set clone.frozenRadius = frozenRadius(GetEventSpellLevel())
endif
set clone.rate = rate(GetEventSpellLevel())
endmethod
private static method onInit takes nothing returns nothing
call IceStorm.create(thistype.typeid,ABILITY_ID,SPELL_EVENT_TYPE,function thistype.configHandler)
endmethod
endmodule
endlibrary