scope Test initializer StartInit
private function Display takes string s returns nothing
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, s)
endfunction
private function Create takes nothing returns boolean
call CreateIllusion( /*
*/ GetTriggerPlayer(), /* whichPlayer
*/ 'hpea', /* whichUnitType
*/ 0, /* duration
*/ 0, /* damageDealtFactor
*/ 0, /* damageReceiveFactor
*/ GetCameraTargetPositionX(), /* x
*/ GetCameraTargetPositionY(), /* y
*/ 270 /* facing
*/ )
return false
endfunction
private function Init takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'E000', 0, 300, 270)
call CreateIllusion(Player(12), 'Hblm', 0, 1, 0.05, 0, -100, 90)
call SetHeroLevel(u, 20, true)
call SelectUnit(u, true)
set u = null
endfunction
private function InitFunction takes nothing returns nothing
call DestroyTimer(GetExpiredTimer())
call Init()
call Display("Test Map:
- Press ESC to summon a permanent illusion at your current camera field
")
endfunction
private function StartInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
call TriggerAddCondition(t, Filter(function Create))
call PanCameraToTimed(0, 0, 0)
call FogEnable(false)
call FogMaskEnable(false)
call TimerStart(CreateTimer(), 0, false, function InitFunction)
endfunction
endscope
Name | Type | is_array | initial_value |
IE_AddDuration | integer | No | |
IE_BaseUnit | unit | No | |
IE_CreateIllusion | integer | No | |
IE_CreateIllusionCopy | integer | No | |
IE_DamageDealtFactor | real | No | |
IE_DamageReceiveFactor | real | No | |
IE_DeathEffect | string | No | |
IE_Duration | real | No | |
IE_DyingIllusion | unit | No | |
IE_Facing | real | No | |
IE_Flag | boolean | No | |
IE_GetBaseUnit | integer | No | |
IE_GetDamageFactor | integer | No | |
IE_GetDuration | integer | No | |
IE_GetElapsedTime | integer | No | |
IE_GetFlag | integer | No | |
IE_GetReceiveFactor | integer | No | |
IE_GetRemainingTime | integer | No | |
IE_Illusion | unit | No | |
IE_IllusionDeathEvent | real | No | |
IE_Operation | integer | No | |
IE_Owner | player | No | |
IE_Position | location | No | |
IE_SetDamageFactor | integer | No | |
IE_SetDeathEffect | integer | No | |
IE_SetDuration | integer | No | |
IE_SetReceiveFactor | integer | No | |
IE_UnitType | unitcode | No |
library DamageEvent/* v 1.3.0.0
**********************************************************************************
*
* Physical Damage Detection Engine
* --------------------------------
* By looking_for_help aka eey
*
* This system is able to detect, modify and deal damage. It can differentiate
* between physical and spell damage, as well as block, reduce or increase the
* applied damage to a desired value. You can also allocate damage events from
* running damage events.
*
**********************************************************************************
*
* Implementation
* --------------
* 1. Copy this trigger to your map. With the AddDamageHandler function you can
* add as many handlers as you like (compare the OnDamage scope).
* 2. Copy the two custom abilities to your map and make sure they have the
* correct ID in the globals variable block.
* 3. Go to the locust swarm ability and invert its damage return portion
* from (default) 0.75 to -0.75.
* 4. Remove the spell damage reduction ability from the spell damage reduction
* items you use (runed bracers). You can configure the resistance of this
* item in the globals block, modifying BRACERS_SPELL_DAMAGE_REDUCTION.
*
**********************************************************************************
*
* Important Notes
* ---------------
* 1. Life Drain does not work with this system, so you should use a triggered
* version of this spell if you want to use it.
* 2. Same for Finger of Death, if you want to use this spell bug free with this
* system, you should use a triggered version of it.
* 3. If you use damage modifiers by setting the damage amount variable, you have
* to use GetUnitLife, GetUnitMaxLife and SetUnitLife instead of GetWidgetLife,
* GetUnitState for UNIT_STATE_MAX_LIFE and SetWidgetLife in your whole map to
* ensure there occure no bugs.
* 4. The boolean USE_SPELL_RESISTANCE_AUTO_DETECT is only neccessary set to true
* if you want to use a customized damage table with spell damage resistance
* above 100%. If this is not the case, it should be set to false, as the
* system is faster then and still works correct.
* 5. As already mentioned you can't use the spell reduction ability when using this
* system (runed bracers and elunes grace). If you want to use them, you can
* trigger them by using the damage modifiers. Runed bracers is already considered
* in this system, so you don't have to code it.
*
**********************************************************************************
*
* System API
* ----------
* unit PDDS.target
* - In this global unit variable, the damaged unit is saved. Don't write any-
* thing into this variable, use it as readonly.
*
* unit PDDS.source
* - In this global unit variable, the damage source is saved. Don't write any-
* thing into this variable, use it as readonly.
*
* real PDDS.amount
* - In this global real variable, the amount of damage is saved. This amount
* can be modified by simply setting it to the desired amount that should be
* applied to the target. Set the amount to 0.0 to block the damage completly.
* Negative values will result in heal.
*
* integer PDDS.damageType
* - In this global integer variable, the damage type of the current damage is
* saved. Use it to differentiate between physical, spell and code damage. The
* variable can takes the values PHYSICAL == 0, SPELL == 1 and CODE == 2. Don't
* write anything into this variable, use it as readonly.
*
* function UnitDamageTargetEx takes unit localSource, unit localTarget, real localAmount,
* boolean attack, boolean ranged, attacktype localAttackType,
* damagetype localDamageType, weapontype localWeaponType
* returns boolean
* - Use this function to allocate attacks from a running damage event. You can use
* it in exactly the same way as the standard native UnitDamageTarget. The function
* is designed recursive, so you can allocate as many attacks as you want. Do not
* use this function outside of a damage event.
*
* function GetUnitLife takes unit u returns real
* - Use this function instead of the GetWidgetLife native. It ensures that you
* get the correct health value, even when damage modifiers are applied. If
* you don't use damage modifiers at all, you don't need this function.
*
* function GetUnitMaxLife takes unit u returns real
* - Use this function instead of the GetUnitState(u, UNIT_STATE_MAX_LIFE) native.
* It will return the correct value, even when damage modifiers are applied. If
* you don't use damage modifiers at all, you don't need this function.
*
* function SetUnitLife takes unit u, real newLife returns nothing
* - Use this function instead of the SetWidgetLife(u, newLife) native if you use
* damage modifiers in your map. Same rules as for the GetUnitMaxLife and the
* GetUnitMaxLife functions.
*
* function AddDamageHandler takes code damageHandler returns nothing
* - Allows you to add a damage handler function to the system. Compare the
* OnDamage scope for an example usage.
*
* function RemoveDamageHandler takes code damageHandler returns nothing
* - Allows you to remove a damage handler function from the system dynamic-
* ally. If you added the same handler function multiple times to the sys-
* tem, this function will remove all of these equal functions.
*
*********************************************************************************/
globals
/*************************************************************************
* Customizable globals
*************************************************************************/
private constant integer DAMAGE_TYPE_DETECTOR = 'A000'
private constant integer SET_MAX_LIFE = 'A001'
private constant integer SPELL_DAMAGE_REDUCTION_ITEM = 'brac'
private constant boolean USE_SPELL_RESISTANCE_AUTO_DETECT = false
private constant real ETHEREAL_DAMAGE_FACTOR = 1.66
private constant real BRACERS_SPELL_DAMAGE_REDUCTION = 0.33
private constant real TRIGGER_CLEANUP_PERIOD = 10.0
/*************************************************************************
* End of Customizable globals
*************************************************************************/
constant integer PHYSICAL = 0
constant integer SPELL = 1
constant integer CODE = 2
constant real UNIT_MIN_LIFE = 0.406
private constant attacktype ATTACK_TYPE_UNIVERSAL = ConvertAttackType(7)
private hashtable h
private real pureAmount
private trigger damageEvent
private trigger damageHandler
private trigger runAllocatedAttacks
private integer allocatedAttacks
private integer totalAllocs
private integer allocCounter
private real damageEventTrigger
endglobals
struct PDDS extends array
static unit source
static unit target
static real amount
static integer damageType
endstruct
/******************************************************************************
* User functions
******************************************************************************/
function AddDamageHandler takes code func returns nothing
local integer id = GetHandleId(Condition(func))
local integer index = 0
// Loop to manage equal damage handlers
loop
exitwhen ( LoadTriggerConditionHandle(h, id, index) == null )
set index = index + 1
endloop
// Store the desired damage handler function
call SaveTriggerConditionHandle(h, id, index, TriggerAddCondition(damageHandler, Filter(func)))
endfunction
function RemoveDamageHandler takes code func returns nothing
local integer id = GetHandleId(Condition(func))
local integer index = 0
// Loop through all equal damage handlers
loop
exitwhen ( LoadTriggerConditionHandle(h, id, index) == null )
call TriggerRemoveCondition(damageHandler, LoadTriggerConditionHandle(h, id, index))
set index = index + 1
endloop
// Clean things up
call FlushChildHashtable(h, id)
endfunction
function GetUnitLife takes unit u returns real
local boolean duringModification
local integer uId = GetHandleId(u)
local real health
local real storedHealth = LoadReal(h, uId, 2)
local real storedDamage = LoadReal(h, uId, 1)
// Check if the unit is being rescued from damage
set duringModification = GetUnitAbilityLevel(u, SET_MAX_LIFE) > 0
if duringModification then
call UnitRemoveAbility(u, SET_MAX_LIFE)
endif
// Get the correct health value of the unit
if storedHealth != 0.0 then
set health = storedHealth - storedDamage
else
set health = GetWidgetLife(u) - storedDamage
endif
// Restore the rescue ability and return
if duringModification then
call UnitAddAbility(u, SET_MAX_LIFE)
endif
return health
endfunction
function GetUnitMaxLife takes unit u returns real
local real maxHealth
// Check if the unit is being rescued from damage
if GetUnitAbilityLevel(u, SET_MAX_LIFE) > 0 then
call UnitRemoveAbility(u, SET_MAX_LIFE)
set maxHealth = GetUnitState(u, UNIT_STATE_MAX_LIFE)
call UnitAddAbility(u, SET_MAX_LIFE)
return maxHealth
endif
// If the unit isn't being rescued, use the standard native
return GetUnitState(u, UNIT_STATE_MAX_LIFE)
endfunction
function SetUnitLife takes unit u, real newLife returns nothing
local integer targetId
local integer oldTimerId
local real oldHealth
// Check if the unit is being rescued from damage
if GetUnitAbilityLevel(u, SET_MAX_LIFE) > 0 then
call UnitRemoveAbility(u, SET_MAX_LIFE)
call SetWidgetLife(u, newLife)
call UnitAddAbility(u, SET_MAX_LIFE)
// Get the unit specific timer information
set targetId = GetHandleId(u)
set oldHealth = LoadReal(h, targetId, 0)
// Update the unit specific timer information
if oldHealth != 0.0 then
set oldTimerId = LoadInteger(h, targetId, 3)
call SaveReal(h, targetId, 2, newLife)
call SaveReal(h, targetId, 0, newLife)
call SaveReal(h, oldTimerId, 4, newLife)
endif
return
endif
// If the unit isn't being rescued, use the standard native
call SetWidgetLife(u, newLife)
endfunction
function UnitDamageTargetEx takes unit localSource, unit localTarget, real localAmount, boolean attack, /*
*/boolean ranged, attacktype localAttackType, damagetype localDamageType, /*
*/weapontype localWeaponType returns boolean
// Avoid infinite loop due to recursion
if PDDS.damageType == CODE then
return false
endif
// Avoid allocating attacks on units that are about to be killed
if ( localTarget == PDDS.target and GetUnitLife(localTarget) - PDDS.amount < UNIT_MIN_LIFE ) then
return false
endif
// Store all damage parameters determined by the user
set allocatedAttacks = allocatedAttacks + 1
call SaveUnitHandle(h, allocatedAttacks, 0, localSource)
call SaveUnitHandle(h, allocatedAttacks, 1, localTarget)
call SaveReal(h, allocatedAttacks, 2, localAmount)
call SaveBoolean(h, allocatedAttacks, 3, attack)
call SaveBoolean(h, allocatedAttacks, 4, ranged)
call SaveInteger(h, allocatedAttacks, 5, GetHandleId(localAttackType))
call SaveInteger(h, allocatedAttacks, 6, GetHandleId(localDamageType))
call SaveInteger(h, allocatedAttacks, 7, GetHandleId(localWeaponType))
// Return true if the damage was allocated
return true
endfunction
/******************************************************************************
* Sub functions
******************************************************************************/
private function DealFixDamage takes unit source, unit target, real pureAmount returns nothing
local real MAX_DAMAGE = 1000000.0
local real beforeHitpoints
local real newHitpoints
// Ensure the amount is positive
if pureAmount < 0 then
set pureAmount = -pureAmount
endif
// Save the targets hitpoints
set beforeHitpoints = GetWidgetLife(target)
set newHitpoints = beforeHitpoints - pureAmount
// Apply the desired, fixed amount
if newHitpoints >= UNIT_MIN_LIFE then
call SetUnitState(target, UNIT_STATE_LIFE, newHitpoints)
else
if ( IsUnitType(target, UNIT_TYPE_ETHEREAL) == false ) then
call SetWidgetLife(target, 1.0)
call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, ATTACK_TYPE_UNIVERSAL, DAMAGE_TYPE_UNIVERSAL, null)
else
call UnitRemoveAbility(target, DAMAGE_TYPE_DETECTOR)
call SetWidgetLife(target, 1.0)
call UnitDamageTarget(source, target, MAX_DAMAGE, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
endif
endif
endfunction
private function GetUnitSpellResistance takes unit u returns real
local real originalHP
local real beforeHP
local real afterHP
local real resistance
local real DUMMY_DAMAGE = 100
local real DUMMY_FACTOR = 0.01
// Deal spell damage in order to get the units resistance
call UnitRemoveAbility(u, DAMAGE_TYPE_DETECTOR)
set originalHP = GetWidgetLife(u)
call UnitAddAbility(u, SET_MAX_LIFE)
call SetWidgetLife(u, 20000.0)
set beforeHP = GetWidgetLife(u)
call DisableTrigger(damageEvent)
call UnitDamageTarget(PDDS.source, u, DUMMY_DAMAGE, true, false, null, DAMAGE_TYPE_UNIVERSAL, null)
call EnableTrigger(damageEvent)
set afterHP = GetWidgetLife(u)
call UnitRemoveAbility(u, SET_MAX_LIFE)
call SetWidgetLife(u, originalHP)
call UnitAddAbility(u, DAMAGE_TYPE_DETECTOR)
call UnitMakeAbilityPermanent(u, true, DAMAGE_TYPE_DETECTOR)
// Calculate this resistance
set resistance = DUMMY_FACTOR*(beforeHP - afterHP)
// If the resistance was greater than 100%, return it
if resistance > 1.0 then
return resistance
else
return 1.0
endif
endfunction
private function UnitHasItemOfType takes unit u, integer itemId returns integer
local integer index = 0
local item indexItem
// Check if the target has a spell damage reducing item
loop
set indexItem = UnitItemInSlot(u, index)
if ( indexItem != null ) and ( GetItemTypeId(indexItem) == itemId ) then
set indexItem = null
return index + 1
endif
set index = index + 1
exitwhen index >= bj_MAX_INVENTORY
endloop
set indexItem = null
return 0
endfunction
/******************************************************************************
* Damage Engine
******************************************************************************/
private function AfterDamage takes nothing returns nothing
local timer time = GetExpiredTimer()
local integer id = GetHandleId(time)
local unit localSource = LoadUnitHandle(h, id, 0)
local unit localTarget = LoadUnitHandle(h, id, 1)
local real amount = LoadReal(h, id, 3)
local real originalHealth = LoadReal(h, id, 4)
// If the damage was modified, restore units health
if originalHealth != 0.0 then
call UnitRemoveAbility(localTarget, SET_MAX_LIFE)
call SetWidgetLife(localTarget, originalHealth)
endif
// Apply the desired amount of damage
if amount > 0.0 then
call DisableTrigger(damageEvent)
call DealFixDamage(localSource, localTarget, amount)
call EnableTrigger(damageEvent)
else
call SetWidgetLife(localTarget, originalHealth - amount)
endif
// Clean things up
call FlushChildHashtable(h, id)
call FlushChildHashtable(h, GetHandleId(localTarget))
call DestroyTimer(time)
set time = null
set localSource = null
set localTarget = null
endfunction
private function DamageEngine takes nothing returns nothing
local timer time
local integer id
local real rawAmount
local real originalHealth
local integer targetId
local integer oldTimerId
local real oldHealth
local real oldOriginalHealth
local real oldAmount
// Set damage variables
set rawAmount = GetEventDamage()
if rawAmount == 0.0 then
return
endif
set PDDS.source = GetEventDamageSource()
set PDDS.target = GetTriggerUnit()
// Determine the damage type
if rawAmount > 0.0 then
if PDDS.damageType != CODE then
set PDDS.damageType = PHYSICAL
endif
set PDDS.amount = rawAmount
else
if PDDS.damageType != CODE then
set PDDS.damageType = SPELL
endif
set PDDS.amount = -rawAmount
endif
// Register spell reduction above 100%
static if USE_SPELL_RESISTANCE_AUTO_DETECT then
set PDDS.amount = GetUnitSpellResistance(PDDS.target)*PDDS.amount
else
if ( IsUnitType(PDDS.target, UNIT_TYPE_ETHEREAL) and IsUnitEnemy(PDDS.target, GetOwningPlayer(PDDS.source)) and rawAmount < 0.0 ) then
set PDDS.amount = ETHEREAL_DAMAGE_FACTOR*PDDS.amount
endif
endif
// Register spell damage reducing items like runed bracers
if ( IsUnitType(PDDS.target, UNIT_TYPE_HERO) and UnitHasItemOfType(PDDS.target, SPELL_DAMAGE_REDUCTION_ITEM) > 0 ) and rawAmount < 0.0 then
set PDDS.amount = (1 - BRACERS_SPELL_DAMAGE_REDUCTION)*PDDS.amount
endif
// Save health and damage variables
if PDDS.damageType != CODE then
call UnitRemoveAbility(PDDS.target, SET_MAX_LIFE)
endif
set pureAmount = PDDS.amount
set originalHealth = GetWidgetLife(PDDS.target)
set oldTimerId = 0
// Call damage handlers
set damageEventTrigger = 0.0
set damageEventTrigger = 1.0
set damageEventTrigger = 0.0
// If the damage was modified, apply the rescue ability
if PDDS.amount != pureAmount then
call UnitAddAbility(PDDS.target, SET_MAX_LIFE)
call SetWidgetLife(PDDS.target, GetWidgetLife(PDDS.target) + pureAmount)
endif
// Check if a previous timer didn't yet expire
set targetId = GetHandleId(PDDS.target)
set oldHealth = LoadReal(h, targetId, 0)
// If this is the case, update the timer information
if oldHealth != 0.0 then
set oldTimerId = LoadInteger(h, targetId, 3)
set oldOriginalHealth = LoadReal(h, targetId, 2)
set oldAmount = LoadReal(h, targetId, 1)
set originalHealth = oldOriginalHealth - oldAmount
call SaveReal(h, oldTimerId, 4, oldOriginalHealth)
endif
// Call after damage event if damage was spell, modified, code or parallel
if ( rawAmount < 0.0 or pureAmount != PDDS.amount or oldTimerId != 0 or allocatedAttacks > 0 ) then
set time = CreateTimer()
set id = GetHandleId(time)
// Save handles for after damage event
call SaveUnitHandle(h, id, 0, PDDS.source)
call SaveUnitHandle(h, id, 1, PDDS.target)
call SaveReal(h, id, 2, pureAmount)
call SaveReal(h, id, 3, PDDS.amount)
call SaveReal(h, id, 4, originalHealth)
// Save this extra to manage parallel damage instances
call SaveReal(h, targetId, 0, GetWidgetLife(PDDS.target))
call SaveReal(h, targetId, 1, PDDS.amount)
call SaveReal(h, targetId, 2, originalHealth)
call SaveInteger(h, targetId, 3, id)
// Avoid healing of negative spell damage
if rawAmount < 0.0 then
call DisableTrigger(damageEvent)
call DealFixDamage(PDDS.source, PDDS.target, -rawAmount)
if ( originalHealth - PDDS.amount < UNIT_MIN_LIFE ) then
call UnitRemoveAbility(PDDS.target, SET_MAX_LIFE)
call DealFixDamage(PDDS.source, PDDS.target, PDDS.amount)
endif
call EnableTrigger(damageEvent)
endif
// Guarantee unit exploding
if originalHealth - PDDS.amount < UNIT_MIN_LIFE then
if rawAmount > 0.0 then
call UnitRemoveAbility(PDDS.target, SET_MAX_LIFE)
call SetWidgetLife(PDDS.target, UNIT_MIN_LIFE)
endif
endif
// Start the after damage event
call TimerStart(time, 0.0, false, function AfterDamage)
endif
// Handle allocated attacks from UnitDamageTargetEx
if totalAllocs == 0 then
set totalAllocs = allocatedAttacks
endif
if allocatedAttacks > 0 then
set allocatedAttacks = allocatedAttacks - 1
set allocCounter = allocCounter + 1
call TriggerEvaluate(runAllocatedAttacks)
endif
// Reset all required variables
set totalAllocs = 0
set allocCounter = -1
set PDDS.damageType = -1
endfunction
/******************************************************************************
* Initialization
******************************************************************************/
private function RestoreTriggers takes nothing returns nothing
local unit enumUnit = GetEnumUnit()
// Re-register units that are alive
if GetUnitTypeId(enumUnit) != 0 then
call TriggerRegisterUnitEvent(damageEvent, enumUnit, EVENT_UNIT_DAMAGED)
endif
set enumUnit = null
endfunction
private function ClearMemory_Actions takes nothing returns nothing
local group g = CreateGroup()
local code c = function DamageEngine
// Reset the damage event
call GroupEnumUnitsInRect(g, GetWorldBounds(), null)
call ResetTrigger(damageEvent)
call DestroyTrigger(damageEvent)
set damageEvent = null
// Rebuild it then
set damageEvent = CreateTrigger()
call TriggerAddCondition(damageEvent, Filter(c))
call ForGroup(g, function RestoreTriggers)
// Clean things up
call DestroyGroup(g)
set g = null
set c = null
endfunction
private function MapInit takes nothing returns nothing
local unit enumUnit = GetEnumUnit()
// Register units on map initialization
call UnitAddAbility(enumUnit, DAMAGE_TYPE_DETECTOR)
call UnitMakeAbilityPermanent(enumUnit, true, DAMAGE_TYPE_DETECTOR)
call TriggerRegisterUnitEvent(damageEvent, enumUnit, EVENT_UNIT_DAMAGED)
set enumUnit = null
endfunction
private function UnitEntersMap takes nothing returns nothing
local unit triggerUnit = GetTriggerUnit()
// Register units that enter the map
if ( GetUnitAbilityLevel(triggerUnit, DAMAGE_TYPE_DETECTOR) < 1 ) then
call UnitAddAbility(triggerUnit, DAMAGE_TYPE_DETECTOR)
call UnitMakeAbilityPermanent(triggerUnit, true, DAMAGE_TYPE_DETECTOR)
call TriggerRegisterUnitEvent(damageEvent, triggerUnit, EVENT_UNIT_DAMAGED)
endif
set triggerUnit = null
endfunction
private function RunAllocatedAttacks takes nothing returns nothing
local integer localAllocAttacks = allocatedAttacks + 1
// Calculate the correct sequence of allocated attacks
set localAllocAttacks = localAllocAttacks - totalAllocs + 1 + 2*allocCounter
// Deal code damage if the unit isn't exploding
set PDDS.damageType = CODE
if GetUnitLife(LoadUnitHandle(h, localAllocAttacks, 1)) >= UNIT_MIN_LIFE then
call UnitDamageTarget(LoadUnitHandle(h, localAllocAttacks, 0), LoadUnitHandle(h, localAllocAttacks, 1), /*
*/LoadReal(h, localAllocAttacks, 2), LoadBoolean(h, localAllocAttacks, 3), /*
*/LoadBoolean(h, localAllocAttacks, 4), ConvertAttackType(LoadInteger(h, /*
*/localAllocAttacks, 5)), ConvertDamageType(LoadInteger(h, localAllocAttacks, 6)), /*
*/ConvertWeaponType(LoadInteger(h, localAllocAttacks, 7)))
else
call FlushChildHashtable(h, localAllocAttacks - 1)
endif
// Clean things up
call FlushChildHashtable(h, localAllocAttacks)
endfunction
private module Inits
private static method onInit takes nothing returns nothing
local group g = CreateGroup()
local region r = CreateRegion()
local trigger UnitEnters = CreateTrigger()
local trigger ClearMemory = CreateTrigger()
local code cDamageEngine = function DamageEngine
local code cUnitEnters = function UnitEntersMap
local code cClearMemory = function ClearMemory_Actions
local code cRunAllocatedAttacks = function RunAllocatedAttacks
// Initialize variables
set h = InitHashtable()
set damageEvent = CreateTrigger()
set damageHandler = CreateTrigger()
set PDDS.damageType = -1
set allocatedAttacks = 0
set runAllocatedAttacks = CreateTrigger()
set totalAllocs = 0
set allocCounter = -1
// Register units on map initialization
call TriggerRegisterVariableEvent(damageHandler, SCOPE_PRIVATE + "damageEventTrigger", EQUAL, 1.0)
call TriggerAddCondition(damageEvent, Filter(cDamageEngine))
call GroupEnumUnitsInRect(g, GetWorldBounds(), null)
call ForGroup(g, function MapInit)
// Register units that enter the map
call RegionAddRect(r, GetWorldBounds())
call TriggerRegisterEnterRegion(UnitEnters, r, null)
call TriggerAddCondition(UnitEnters, Filter(cUnitEnters))
// Register trigger for allocated attacks
call TriggerAddCondition(runAllocatedAttacks, Filter(cRunAllocatedAttacks))
// Clear memory leaks
call TriggerRegisterTimerEvent(ClearMemory, TRIGGER_CLEANUP_PERIOD, true)
call TriggerAddCondition(ClearMemory, Filter(cClearMemory))
// Clean things up
call DestroyGroup(g)
set UnitEnters = null
set ClearMemory = null
set cDamageEngine = null
set cUnitEnters = null
set cClearMemory = null
set cRunAllocatedAttacks = null
set g = null
set r = null
endmethod
endmodule
private struct Init extends array
implement Inits
endstruct
endlibrary
// PDDS ObjectMerger
//! external ObjectMerger w3a Alsr A000 anam "DAMAGE_TYPE_DETECT" aart "" isr2 1 2
//! external ObjectMerger w3a All1 A001 anam "SET_MAX_LIFE" ansf "" llif 1000000
library Alloc /* v1.3.1.1
*************************************************************************************
*
* */ uses /*
*
* */ optional ErrorMessage /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
* */ optional MemoryAnalysis /*
*
*************************************************************************************
*
* Minimizes code generation and global variables while maintaining
* excellent performance.
*
* local thistype this = recycler[0]
*
* if (recycler[this] == 0) then
* set recycler[0] = this + 1
* else
* set recycler[0] = recycler[this]
* endif
*
************************************************************************************
*
* module Alloc
*
* static method allocate takes nothing returns thistype
* method deallocate takes nothing returns nothing
*
* The Following Require Error Message To Be In The Map
* --------------------------------------------------------
*
* debug readonly boolean allocated
*
* The Following Require Memory Analysis To Be In The Map
* --------------------------------------------------------
*
* debug readonly integer monitorCount
* - the amount of global memory being monitored by this
* debug readonly integer monitorString
* - gets a string representation of all global memory being monitored by this
* debug readonly integer address
* - global memory address for debugging
* - used with monitor and stopMonitor
*
* debug static method calculateMemoryUsage takes nothing returns integer
* debug static method getAllocatedMemoryAsString takes nothing returns string
*
* debug method monitor takes string label, integer address returns nothing
* - monitor a global memory address with a label
* - used to identify memory leaks
* - should be memory that ought to be destroyed by the time this is destroyed
* debug method stopMonitor takes integer address returns nothing
* - stops monitoring global memory
* debug method stopMonitorValue takes handle monitoredHandle returns nothing
* - stops monitoring handle values
*
* The Following Are Used To Monitor Handle Values
*
* debug method monitor_widget takes string label, widget handleToTrack returns nothing
* debug method monitor_destructable takes string label, destructable handleToTrack returns nothing
* debug method monitor_item takes string label, item handleToTrack returns nothing
* debug method monitor_unit takes string label, unit handleToTrack returns nothing
* debug method monitor_timer takes string label, timer handleToTrack returns nothing
* debug method monitor_trigger takes string label, trigger handleToTrack returns nothing
* debug method monitor_triggercondition takes string label, triggercondition handleToTrack returns nothing
* debug method monitor_triggeraction takes string label, triggeraction handleToTrack returns nothing
* debug method monitor_force takes string label, force handleToTrack returns nothing
* debug method monitor_group takes string label, group handleToTrack returns nothing
* debug method monitor_location takes string label, location handleToTrack returns nothing
* debug method monitor_rect takes string label, rect handleToTrack returns nothing
* debug method monitor_boolexpr takes string label, boolexpr handleToTrack returns nothing
* debug method monitor_effect takes string label, effect handleToTrack returns nothing
* debug method monitor_unitpool takes string label, unitpool handleToTrack returns nothing
* debug method monitor_itempool takes string label, itempool handleToTrack returns nothing
* debug method monitor_quest takes string label, quest handleToTrack returns nothing
* debug method monitor_defeatcondition takes string label, defeatcondition handleToTrack returns nothing
* debug method monitor_timerdialog takes string label, timerdialog handleToTrack returns nothing
* debug method monitor_leaderboard takes string label, leaderboard handleToTrack returns nothing
* debug method monitor_multiboard takes string label, multiboard handleToTrack returns nothing
* debug method monitor_multiboarditem takes string label, multiboarditem handleToTrack returns nothing
* debug method monitor_dialog takes string label, dialog handleToTrack returns nothing
* debug method monitor_button takes string label, button handleToTrack returns nothing
* debug method monitor_texttag takes string label, texttag handleToTrack returns nothing
* debug method monitor_lightning takes string label, lightning handleToTrack returns nothing
* debug method monitor_image takes string label, image handleToTrack returns nothing
* debug method monitor_ubersplat takes string label, ubersplat handleToTrack returns nothing
* debug method monitor_region takes string label, region handleToTrack returns nothing
* debug method monitor_fogmodifier takes string label, fogmodifier handleToTrack returns nothing
* debug method monitor_hashtable takes string label, hashtable handleToTrack returns nothing
*
************************************************************************************/
module Alloc
/*
* stack
*/
private static integer array recycler
static if LIBRARY_MemoryAnalysis then
debug private MemoryMonitor globalAddress
debug method operator address takes nothing returns integer
debug call ThrowError(recycler[this] != -1, "Alloc", "address", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress
debug endmethod
endif
/*
* allocation
*/
static method allocate takes nothing returns thistype
local thistype this = recycler[0]
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 8192, "Alloc", "allocate", "thistype", 0, "Overflow.")
endif
if (recycler[this] == 0) then
set recycler[0] = this + 1
else
set recycler[0] = recycler[this]
endif
static if LIBRARY_ErrorMessage then
debug set recycler[this] = -1
endif
static if LIBRARY_MemoryAnalysis then
debug set globalAddress = MemoryMonitor.allocate("thistype")
endif
return this
endmethod
method deallocate takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(recycler[this] != -1, "Alloc", "deallocate", "thistype", this, "Attempted To Deallocate Null Instance.")
endif
static if LIBRARY_MemoryAnalysis then
debug call globalAddress.deallocate()
debug set globalAddress = 0
endif
set recycler[this] = recycler[0]
set recycler[0] = this
endmethod
static if LIBRARY_MemoryAnalysis then
debug method monitor takes string label, integer address returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor(label, address)
debug endmethod
debug method stopMonitor takes integer address returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "stopMonitor", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.stopMonitor(address)
debug endmethod
debug method stopMonitorValue takes handle monitoredHandle returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "stopMonitorValue", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.stopMonitorValue(monitoredHandle)
debug endmethod
debug method operator monitorCount takes nothing returns integer
debug call ThrowError(recycler[this] != -1, "Alloc", "monitorCount", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress.monitorCount
debug endmethod
debug method operator monitorString takes nothing returns string
debug call ThrowError(recycler[this] != -1, "Alloc", "monitorString", "thistype", this, "Attempted To Access Null Instance.")
debug return globalAddress.monitorString
debug endmethod
debug method monitor_widget takes string label, widget handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_widget", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_widget(label, handleToTrack)
debug endmethod
debug method monitor_destructable takes string label, destructable handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_destructable", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_destructable(label, handleToTrack)
debug endmethod
debug method monitor_item takes string label, item handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_item", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_item(label, handleToTrack)
debug endmethod
debug method monitor_unit takes string label, unit handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_unit", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_unit(label, handleToTrack)
debug endmethod
debug method monitor_timer takes string label, timer handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_timer", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_timer(label, handleToTrack)
debug endmethod
debug method monitor_trigger takes string label, trigger handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_trigger", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_trigger(label, handleToTrack)
debug endmethod
debug method monitor_triggercondition takes string label, triggercondition handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_triggercondition", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_triggercondition(label, handleToTrack)
debug endmethod
debug method monitor_triggeraction takes string label, triggeraction handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_triggeraction", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_triggeraction(label, handleToTrack)
debug endmethod
debug method monitor_force takes string label, force handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_force", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_force(label, handleToTrack)
debug endmethod
debug method monitor_group takes string label, group handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_group", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_group(label, handleToTrack)
debug endmethod
debug method monitor_location takes string label, location handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_location", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_location(label, handleToTrack)
debug endmethod
debug method monitor_rect takes string label, rect handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_rect", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_rect(label, handleToTrack)
debug endmethod
debug method monitor_boolexpr takes string label, boolexpr handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_boolexpr", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_boolexpr(label, handleToTrack)
debug endmethod
debug method monitor_effect takes string label, effect handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_effect", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_effect(label, handleToTrack)
debug endmethod
debug method monitor_unitpool takes string label, unitpool handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_unitpool", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_unitpool(label, handleToTrack)
debug endmethod
debug method monitor_itempool takes string label, itempool handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_itempool", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_itempool(label, handleToTrack)
debug endmethod
debug method monitor_quest takes string label, quest handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_quest", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_quest(label, handleToTrack)
debug endmethod
debug method monitor_defeatcondition takes string label, defeatcondition handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_defeatcondition", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_defeatcondition(label, handleToTrack)
debug endmethod
debug method monitor_timerdialog takes string label, timerdialog handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_timerdialog", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_timerdialog(label, handleToTrack)
debug endmethod
debug method monitor_leaderboard takes string label, leaderboard handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_leaderboard", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_leaderboard(label, handleToTrack)
debug endmethod
debug method monitor_multiboard takes string label, multiboard handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_multiboard", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_multiboard(label, handleToTrack)
debug endmethod
debug method monitor_multiboarditem takes string label, multiboarditem handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_multiboarditem", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_multiboarditem(label, handleToTrack)
debug endmethod
debug method monitor_dialog takes string label, dialog handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_dialog", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_dialog(label, handleToTrack)
debug endmethod
debug method monitor_button takes string label, button handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_button", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_button(label, handleToTrack)
debug endmethod
debug method monitor_texttag takes string label, texttag handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_texttag", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_texttag(label, handleToTrack)
debug endmethod
debug method monitor_lightning takes string label, lightning handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_lightning", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_lightning(label, handleToTrack)
debug endmethod
debug method monitor_image takes string label, image handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_image", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_image(label, handleToTrack)
debug endmethod
debug method monitor_ubersplat takes string label, ubersplat handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_ubersplat", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_ubersplat(label, handleToTrack)
debug endmethod
debug method monitor_region takes string label, region handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_region", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_region(label, handleToTrack)
debug endmethod
debug method monitor_fogmodifier takes string label, fogmodifier handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_fogmodifier", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_fogmodifier(label, handleToTrack)
debug endmethod
debug method monitor_hashtable takes string label, hashtable handleToTrack returns nothing
debug call ThrowError(recycler[this] != -1, "Alloc", "monitor_hashtable", "thistype", this, "Attempted To Access Null Instance.")
debug call globalAddress.monitor_hashtable(label, handleToTrack)
debug endmethod
static if DEBUG_MODE then
//! runtextmacro optional MEMORY_ANALYSIS_STATIC_FIELD_NEW("recycler")
static method calculateMemoryUsage takes nothing returns integer
return calculateAllocatedMemory__recycler()
endmethod
static method getAllocatedMemoryAsString takes nothing returns string
return allocatedMemoryString__recycler()
endmethod
endif
endif
/*
* analysis
*/
static if LIBRARY_ErrorMessage then
debug method operator allocated takes nothing returns boolean
debug return recycler[this] == -1
debug endmethod
endif
/*
* initialization
*/
private static method onInit takes nothing returns nothing
set recycler[0] = 1
endmethod
endmodule
endlibrary
library 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 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) : Release 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 UnitRecycler /* v1.4
|=============|
| Author: AGD |
|=============|
*/requires /*
*/ReviveUnit /* https://www.hiveworkshop.com/threads/snippet-reviveunit.186696/#post-1809651/
*/Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/#post-1826447/
*/optional RegisterPlayerUnitEvent /* https://www.hiveworkshop.com/threads/snippet-registerplayerunitevent.203338/#post-2002332/
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
This system is important because CreateUnit() is one of the most processor-intensive function in
the game and there are reports that even after they are removed, they still leave some bit of memory
consumption (0.04 KB) on the RAM. Therefore, it would be very helpful if you can minimize unit
creation or so. This system also allows you to recycle dead units to avoid permanent 0.04 KB memory
leak for each future CreateUnit() call.
As of v1.4, the system now takes into consideration the facing angle of units - like most dummy
recycling systems do.
*/
//! novjass
[Credits]
Aniki - For suggesting ideas on further improvements
|-----|
| API |
|-----|
function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit/*
- Returns unit of specified ID from the stock of recycled units. If there's none in the stock that
matched the specified unit's rawcode, it will create a new unit instead
- Returns null if the rawcode's unit-type is a hero or non-existent
*/function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit/*
- Works similar to GetRecycledUnit() except that if the input rawcode's unit-type is a hero, it will
be created via CreateUnit() instead
- You can use this as an alternative to CreateUnit()
*/function RecycleUnit takes unit u returns boolean/*
- Recycles the specified unit and returns a boolean value depending on the success of the operation
- Does nothing to hero units
*/function RecycleUnitEx takes unit u returns boolean/*
- Works similar to RecycleUnit() except that if <u> is not recyclable, it will be removed via
RemoveUnit() instead
- You can use this as an alternative to RemoveUnit()
*/function RecycleUnitDelayed takes unit u, real delay returns nothing/*
- Recycles the specified unit after <delay> seconds
*/function RecycleUnitDelayedEx takes unit u, real delay returns nothing/*
- Works similar to RecycleUnitDelayed() except that it calls RecycleUnitEx() instead of RecycleUnit()
*/function UnitAddToStock takes integer rawCode returns boolean/*
- Creates a unit of type ID and adds it to the stock of recycled units then returns a boolean value
depending on the success of the operation
*///! endnovjass
//CONFIGURATION SECTION
globals
/*
The owner of the stocked/recycled units */
private constant player OWNER = Player(15)
/*
Determines if dead units will be automatically recycled
after a delay designated by the <constant function
DeathTime below> */
private constant boolean AUTO_RECYCLE_DEAD = true
/*
Angle group count per unit type
If you don't need the precision in unit facing angles, you should set this
value to 1
(Note: The maximum number of different kinds of unit type that you can use
with this system is limited to <8190/ANGLE_COUNT - 1> ) */
private constant integer ANGLE_COUNT = 8
endglobals
/* The delay before dead units will be automatically recycled in case when
AUTO_RECYCLE_DEAD == true */
static if AUTO_RECYCLE_DEAD then
private function DeathTime takes unit u returns real
/*if <condition> then
return someValue
elseif <condition> then
return someValue
endif */
return 8.00
endfunction
endif
/* Filters units allowed for recycling */
private function UnitTypeFilter takes unit u returns boolean
return not IsUnitIllusion(u) and not IsUnitType(u, UNIT_TYPE_SUMMONED)
endfunction
/* When recycling a unit back to the stock, these resets will be applied to the
unit. You can add more actions to this or you can delete this module if you
don't need it. */
private module UnitRecyclerResets
call SetUnitScale(u, 1, 0, 0)
call SetUnitVertexColor(u, 255, 255, 255, 255)
call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0)
endmodule
//END OF CONFIGURATION
/*== Do not do changes below this line if you're not so sure on what you're doing ==*/
native UnitAlive takes unit u returns boolean
globals
private constant real ANGLE_INTERVAL = 360.00/ANGLE_COUNT
private constant real HALF_INTERVAL = ANGLE_INTERVAL/2.00
private real unitCampX
private real unitCampY
endglobals
private struct List extends array
unit unit
thistype recycler
thistype prev
thistype next
debug static Table stocked
static constant method operator head takes nothing returns thistype
return 0
endmethod
method stockUnit takes unit u returns nothing
local thistype node = head.recycler
local thistype last = this.prev
set head.recycler = node.recycler
set this.prev = node
set last.next = node
set node.prev = last
set node.next = this
set node.unit = u
call PauseUnit(u, true)
call SetUnitX(u, unitCampX)
call SetUnitY(u, unitCampY)
debug set stocked.boolean[GetHandleId(u)] = true
endmethod
method addUnit takes unit u, real angle returns boolean
if u != null and not IsUnitType(u, UNIT_TYPE_HERO) and UnitTypeFilter(u) then
if not UnitAlive(u) and not ReviveUnit(u) then
debug call ThrowWarning(true, "UnitRecycler", "addUnit()", "thistype", GetHandleId(u), "Unable to recycle unit: Unable to revive dead unit")
return false
endif
call this.stockUnit(u)
call SetUnitFacing(u, angle)
call SetUnitOwner(u, OWNER, true)
call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE))
call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA))
implement optional UnitRecyclerResets
return true
endif
return false
endmethod
method getUnit takes player owner, integer id, real x, real y, real angle returns unit
local thistype first
local thistype next
local real facing
local real deltaAngle
if not IsHeroUnitId(id) then
set first = this.next
set deltaAngle = RAbsBJ(GetUnitFacing(first.unit) - angle)
if deltaAngle > 180.00 then
set deltaAngle = 360.00 - deltaAngle
endif
if first == this or deltaAngle > HALF_INTERVAL then
set bj_lastCreatedUnit = CreateUnit(owner, id, x, y, angle)
else
set bj_lastCreatedUnit = first.unit
set first.unit = null
set next = first.next
set next.prev = this
set this.next = next
call SetUnitOwner(bj_lastCreatedUnit, owner, true)
call SetUnitPosition(bj_lastCreatedUnit, x, y)
call SetUnitFacing(bj_lastCreatedUnit, angle)
call PauseUnit(bj_lastCreatedUnit, false)
debug call stocked.boolean.remove(GetHandleId(bj_lastCreatedUnit))
endif
return bj_lastCreatedUnit
endif
return null
endmethod
static method init takes nothing returns nothing
local thistype this = 0
set thistype(8190).recycler = 0
loop
set this.recycler = this + 1
set this = this + 1
exitwhen this == 8190
endloop
debug set stocked = Table.create()
endmethod
endstruct
private struct UnitRecycler extends array
private static Table rawCodeIdTable
private static Table timerTable
private static integer rawCodeCount = 0
private static integer array position
private static integer array stackSize
private static integer array indexStack
private static List array head
private static method getRawCodeId takes integer rawCode returns integer
local integer i = rawCodeIdTable[rawCode]
if i == 0 then
set rawCodeCount = rawCodeCount + 1
set rawCodeIdTable[rawCode] = rawCodeCount
set i = rawCodeCount
endif
return i
endmethod
private static method getHead takes integer id, integer index returns List
local List this = head[id*ANGLE_COUNT + index]
if this == 0 then
set this = List.head.recycler
set List.head.recycler = this.recycler
set this.prev = this
set this.next = this
set head[id*ANGLE_COUNT + index] = this
endif
return this
endmethod
private static method getListIndex takes integer id returns integer
if stackSize[id] == 0 then
if position[id] < ANGLE_COUNT - 1 then
set position[id] = position[id] + 1
return position[id]
endif
set position[id] = 0
return 0
endif
set stackSize[id] = stackSize[id] - 1
return indexStack[id*ANGLE_COUNT + stackSize[id]]
endmethod
static method stock takes integer rawCode returns boolean
local integer id
local integer index
local unit u
if not IsHeroUnitId(rawCode) then
set id = getRawCodeId(rawCode)
set index = getListIndex(id)
set u = CreateUnit(OWNER, rawCode, 0.00, 0.00, index*ANGLE_INTERVAL)
if u != null and not IsUnitType(u, UNIT_TYPE_HERO) and UnitTypeFilter(u) then
call getHead(id, index).stockUnit(u)
set u = null
return true
endif
endif
return false
endmethod
static method add takes unit u returns boolean
local integer id = getRawCodeId(GetUnitTypeId(u))
local integer index = getListIndex(id)
return getHead(id, index).addUnit(u, index*ANGLE_INTERVAL)
endmethod
static method get takes player owner, integer rawCode, real x, real y, real angle returns unit
local integer id = getRawCodeId(rawCode)
local integer index = R2I(angle/ANGLE_INTERVAL)
if angle - ANGLE_INTERVAL*index > ANGLE_INTERVAL/2.00 then
if index < ANGLE_COUNT - 1 then
set index = index + 1
else
set index = 0
endif
endif
set indexStack[id*ANGLE_COUNT + stackSize[id]] = index
set stackSize[id] = stackSize[id] + 1
return getHead(id, index).getUnit(owner, rawCode, x, y, angle)
endmethod
static method delayedRecycle takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer key = GetHandleId(t)
call add(timerTable.unit[key])
call timerTable.unit.remove(key)
call DestroyTimer(t)
set t = null
endmethod
static method delayedRecycleEx takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer key = GetHandleId(t)
call add(timerTable.unit[key])
call timerTable.unit.remove(key)
call DestroyTimer(t)
set t = null
endmethod
static method addDelayed takes unit u, real delay, code callback returns nothing
local timer t = CreateTimer()
set timerTable.unit[GetHandleId(t)] = u
call TimerStart(t, delay, false, callback)
set t = null
endmethod
static method init takes nothing returns nothing
local rect bounds = GetWorldBounds()
// Hide recycled units at the top of the map beyond reach of the camera
set unitCampX = 0.00
set unitCampY = GetRectMaxY(bounds) + 1000.00
call RemoveRect(bounds)
set bounds = null
set rawCodeIdTable = Table.create()
set timerTable = Table.create()
endmethod
endstruct
/*========================================================================================================*/
function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit
static if DEBUG_MODE and LIBRARY_ErrorMessage then
call UnitRecycler.get(owner, rawCode, x, y, facing)
call ThrowError(bj_lastCreatedUnit == null, "UnitRecycler", "GetRecycledUnit()", "", 0, "Specified unit type does not exist")
call ThrowError(IsHeroUnitId(rawCode), "UnitRecycler", "GetRecycledUnit()", GetUnitName(bj_lastCreatedUnit), 0, "Specified unit type is a hero")
return bj_lastCreatedUnit
else
return UnitRecycler.get(owner, rawCode, x, y, facing)
endif
endfunction
function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit
if not IsHeroUnitId(rawCode) then
return UnitRecycler.get(owner, rawCode, x, y, facing)
endif
static if LIBRARY_ErrorMessage then
debug call ThrowWarning(true, "UnitRecycler", "GetRecycledUnitEx()", "", 0, "Cannot retrieve a hero unit, creating new unit")
endif
return CreateUnit(owner, rawCode, x, y, facing)
endfunction
function RecycleUnit takes unit u returns boolean
static if LIBRARY_ErrorMessage then
debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "RecycleUnit()", GetUnitName(u), 0, "Attempted to recycle an already recycled unit")
debug call ThrowWarning(u == null, "UnitRecycler", "RecycleUnit()", "", 0, "Attempted to recycle a null unit")
debug call ThrowWarning(IsHeroUnitId(GetUnitTypeId(u)), "UnitRecycler", "RecycleUnit()", GetUnitName(u), 0, "Attempted to recycle a hero unit")
debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "RecycleUnit()", GetUnitName(u), 0, "Attempted to recycle an invalid unit type")
endif
return UnitRecycler.add(u)
endfunction
function RecycleUnitEx takes unit u returns boolean
static if LIBRARY_ErrorMessage then
debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "RecycleUnitEx()", GetUnitName(u), 0, "Attempted to recycle an already recycled unit")
debug call ThrowWarning(u == null, "UnitRecycler", "RecycleUnitEx()", "", 0, "Attempted to recycle a null unit")
debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "RecycleUnitEx()", GetUnitName(u), 0, "Attempted to recycle an invalid unit type")
endif
if not UnitRecycler.add(u) then
call RemoveUnit(u)
static if LIBRARY_ErrorMessage then
debug call ThrowWarning(u != null, "UnitRecycler", "RecycleUnitEx()", GetUnitName(u), 0, "Cannot recycle the specified unit, removing unit")
endif
return false
endif
return true
endfunction
function RecycleUnitDelayed takes unit u, real delay returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "RecycleUnitDelayed()", GetUnitName(u), 0, "Attempted to recycle an already recycled unit")
debug call ThrowWarning(u == null, "UnitRecycler", "RecycleUnitDelayed()", "", 0, "Attempted to recycle a null unit")
debug call ThrowWarning(IsHeroUnitId(GetUnitTypeId(u)), "UnitRecycler", "RecycleUnitDelayed()", GetUnitName(u), 0, "Attempted to recycle a hero unit")
debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "RecycleUnitDelayed()", GetUnitName(u), 0, "Attempted to recycle an invalid unit type")
endif
call UnitRecycler.addDelayed(u, delay, function UnitRecycler.delayedRecycle)
endfunction
function RecycleUnitDelayedEx takes unit u, real delay returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "RecycleUnitDelayedEx()", GetUnitName(u), 0, "Attempted to recycle an already recycled unit")
debug call ThrowWarning(u == null, "UnitRecycler", "RecycleUnitDelayedEx()", "", 0, "Attempted to recycle a null unit")
debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "RecycleUnitDelayedEx()", GetUnitName(u), 0, "Attempted to recycle an invalid unit type")
endif
call UnitRecycler.addDelayed(u, delay, function UnitRecycler.delayedRecycleEx)
endfunction
function UnitAddToStock takes integer rawCode returns boolean
static if LIBRARY_ErrorMessage then
debug local unit u = CreateUnit(OWNER, rawCode, 0, 0, 0)
debug call ThrowWarning(u == null, "UnitRecycler", "UnitAddToStock()", "", 0, "Attempted to stock a non-existent unit type")
debug call ThrowWarning(IsHeroUnitId(rawCode), "UnitRecycler", "UnitAddToStock()", GetUnitName(u), 0, "Attempted to stock a hero unit")
debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "UnitAddToStock()", GetUnitName(u), 0, "Attempted to stock an invalid unit type")
debug call RemoveUnit(u)
debug set u = null
endif
return UnitRecycler.stock(rawCode)
endfunction
/*========================================================================================================*/
private module Init
private static method onInit takes nothing returns nothing
call init()
endmethod
endmodule
private struct Initializer extends array
static if AUTO_RECYCLE_DEAD then
private static method onDeath takes nothing returns nothing
local unit u = GetTriggerUnit()
debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "", GetUnitName(u), 0, "A unit in stock has been killed!")
if UnitTypeFilter(u) and not IsUnitType(u, UNIT_TYPE_HERO) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
call RecycleUnitDelayedEx(u, DeathTime(u))
endif
set u = null
endmethod
private static method autoRecycler takes nothing returns nothing
static if AUTO_RECYCLE_DEAD then
static if RPUE_VERSION_NEW then
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
elseif LIBRARY_RegisterPlayerUnitEvent then
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
else
local trigger t = CreateTrigger()
local code c = function thistype.onDeath
local integer i = 16
loop
set i = i - 1
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
exitwhen i == 0
endloop
call TriggerAddCondition(t, Filter(c))
set t = null
endif
endif
endmethod
endif
private static method init takes nothing returns nothing
call List.init()
call UnitRecycler.init()
static if AUTO_RECYCLE_DEAD then
call autoRecycler()
endif
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|CFFFFCC00UnitRecycler|R library is ready!")
endmethod
implement Init
endstruct
static if DEBUG_MODE then
private function DisplayError takes unit removedUnit returns nothing
call ThrowError(List.stocked.boolean[GetHandleId(removedUnit)], "UnitRecycler", "RemoveUnit()", GetUnitName(removedUnit), 0, "Attempted to remove a stocked unit")
endfunction
hook RemoveUnit DisplayError
endif
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(14)
//The number of indexed angle. The higher the value the:
// - Lesser the turning time for the Dummy Units.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 10 (Max difference of 18 degrees)
private constant integer ANGLES_COUNT = 10
//The number of Dummy units per ANGLES_COUNT. The higher the value the:
// - Higher the number of units that can be recycled per angle, when
// no more units are in queue, the system will resort to use CreateUnit.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 3 to 5 (for less overhead in Map Loading Screen)
private constant integer STORED_UNIT_COUNT = 3
//The maximum number of Dummy units that can exist. When the system resort
//to using CreateUnit, the unit will be permanently added to the Dummy
//List. To avoid spamming Dummy Units and having too much free Dummy
//Units to allocate, the maximum number of Dummy Units is capped.
// Recommended Value: 80 to 120
private constant integer MAX_DUMMY_COUNT = 100
//When a certain angle have less than BORROW_REQUEST units in its list,
//it will start to borrow Dummy Units from the list with the highest
//Dummy Unit count.
// Recommended Value: Half of maximum STORED_UNIT_COUNT
private constant integer BORROW_REQUEST = 5
//It will only return a Dummy if the current dummy is close
//to it's appropriate facing angle. This is to avoid returning
//a Dummy which is still turning to face it's list angle.
private constant real ANGLE_TOLERANCE = 10.0
//An additional option to automatically hide recycled dummy units in the
//corner of the map camera bounds
private constant boolean HIDE_ON_MAP_CORNER = true
endglobals
//Every time a new dummy unit is retrieved, it will apply this resets
//If it is redundant/you dont need it, remove it.
//! textmacro DUMMY_UNIT_RESET
call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
call ShowDummy(bj_lastCreatedUnit, true)
//! endtextmacro
// =============================================================== //
// ==================== END CONFIGURATION ======================== //
// =============================================================== //
globals
private integer dummyCount = ANGLES_COUNT*STORED_UNIT_COUNT
private real array angle
private integer array count
private integer array countHead
private integer array countNext
private integer array countPrev
private integer array next
private integer array prev
private unit array dummy
private integer upper
private integer lower
private integer lastInstance
private constant real FACING_OFFSET = 180.0/ANGLES_COUNT
endglobals
static if HIDE_ON_MAP_CORNER and not LIBRARY_WorldBounds then
private module BoundsInit
readonly static real x
readonly static real y
private static method onInit takes nothing returns nothing
local rect map = GetWorldBounds()
set thistype.x = GetRectMaxX(map)
set thistype.y = GetRectMaxY(map)
call RemoveRect(map)
set map = null
endmethod
endmodule
private struct Bounds extends array
implement BoundsInit
endstruct
endif
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable hash = InitHashtable()
endif
private static method onInit takes nothing returns nothing
local real add = 360.0/ANGLES_COUNT
local real a = 0
local integer this = ANGLES_COUNT
local integer head = 0
local integer cHead = JASS_MAX_ARRAY_SIZE - 1 //avoid allocation collision
local integer i = R2I(MAX_DUMMY_COUNT/ANGLES_COUNT + 0.5)
set upper = STORED_UNIT_COUNT
set lower = STORED_UNIT_COUNT
static if LIBRARY_Table then
set tb = Table.create()
endif
//Initialize countHeads
loop
exitwhen i < 0
set countNext[cHead] = cHead
set countPrev[cHead] = cHead
set countHead[i] = cHead
set cHead = cHead - 1
set i = i - 1
endloop
set cHead = countHead[STORED_UNIT_COUNT] //All heads will be inserted here initially
//Create the Dummy units
loop
exitwhen a >= 360
//Initialize head
set next[head] = head
set prev[head] = head
set count[head] = STORED_UNIT_COUNT
set angle[head] = a
//Insert head in the Count List
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
set i = 0
loop
exitwhen i >= STORED_UNIT_COUNT
//Queued Linked List
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
static if HIDE_ON_MAP_CORNER then
static if LIBRARY_WorldBounds then
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, a)
else
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, Bounds.x, Bounds.y, a)
endif
else
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, 0, 0, a)
endif
call PauseUnit(dummy[this], true)
static if LIBRARY_Table then
set tb[GetHandleId(dummy[this])] = this
else
call SaveInteger(hash, GetHandleId(dummy[this]), 0, this)
endif
set this = this + 1
set i = i + 1
endloop
set head = head + 1
set a = a + add
endloop
set lastInstance = this
endmethod
endmodule
private struct S extends array
implement M
endstruct
private function GetHead takes integer facing returns integer
if facing < 0 or facing >= 360 then
set facing = facing - (facing/360)*360
if facing < 0 then
set facing = facing + 360
endif
endif
return R2I((facing*ANGLES_COUNT/360.0))
endfunction
function ShowDummy takes unit u, boolean flag returns nothing
if IsUnitHidden(u) == flag then
call ShowUnit(u, flag)
if flag and GetUnitTypeId(u) == DUMMY_ID then
call UnitRemoveAbility(u, 'Aloc')
call UnitAddAbility(u, 'Aloc')
endif
endif
endfunction
function GetRecycledDummy takes real x, real y, real z, real facing returns unit
local integer head = GetHead(R2I(facing + FACING_OFFSET))
local integer this = next[head]
local integer cHead
//If there are Dummy Units in the Queue List already facing close to the appropriate angle
if this != head and RAbsBJ(GetUnitFacing(dummy[this]) - angle[head]) <= ANGLE_TOLERANCE then
//Remove from the Queue List
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
//For double free protection
set next[this] = -1
//Unit Properties
set bj_lastCreatedUnit = dummy[this]
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call SetUnitFacing(bj_lastCreatedUnit, facing)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
//! runtextmacro DUMMY_UNIT_RESET()
//Update Count and Bounds
set count[head] = count[head] - 1
//------------------------------------------------
// Unit Sharing
//------------------------------------------------
if count[head] < BORROW_REQUEST and count[countNext[countHead[upper]]] > count[head] then
set count[head] = count[head] + 1
set this = next[countNext[countHead[upper]]]
call SetUnitFacing(dummy[this], angle[head])
//Remove
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
//Add to the Current List
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
set head = countNext[countHead[upper]]
set count[head] = count[head] - 1
endif
//---------------------------
//Update Count Lists
//---------------------------
//Remove from the current Count List
set countNext[countPrev[head]] = countNext[head]
set countPrev[countNext[head]] = countPrev[head]
//Add to the new Count List
set cHead = countHead[count[head]]
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set cHead = countHead[upper]
if countNext[cHead] == cHead then
set upper = upper - 1
endif
if count[head] < lower then
set lower = count[head]
endif
else
set bj_lastCreatedUnit = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
call PauseUnit(bj_lastCreatedUnit, true)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
if dummyCount < MAX_DUMMY_COUNT then
set this = lastInstance
//For double free protection
set next[this] = -1
set dummy[this] = bj_lastCreatedUnit
static if LIBRARY_Table then
set S.tb[GetHandleId(bj_lastCreatedUnit)] = this
else
call SaveInteger(S.hash, GetHandleId(bj_lastCreatedUnit), 0, this)
endif
set lastInstance = lastInstance + 1
endif
set dummyCount = dummyCount + 1
endif
return bj_lastCreatedUnit
endfunction
function RecycleDummy takes unit u returns nothing
static if LIBRARY_Table then
local integer this = S.tb[GetHandleId(u)]
else
local integer this = LoadInteger(S.hash, GetHandleId(u), 0)
endif
local integer head
local integer cHead
//If the unit is a legit Dummy Unit
if this > 0 and next[this] == -1 then
//Find where to insert based on the list having the least number of units
set head = countNext[countHead[lower]]
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
//Update Status
call SetUnitFacing(u, angle[head])
call PauseUnit(u, true)
call SetUnitOwner(u, OWNER, false)
static if HIDE_ON_MAP_CORNER then
static if LIBRARY_WorldBounds then
call SetUnitX(u, WorldBounds.maxX)
call SetUnitY(u, WorldBounds.maxY)
else
call SetUnitX(u, Bounds.x)
call SetUnitY(u, Bounds.y)
endif
else
call SetUnitScale(u, 0, 0, 0)
call SetUnitVertexColor(u, 0, 0, 0, 0)
endif
set count[head] = count[head] + 1
//---------------------------
// Update Count Lists
//---------------------------
//Remove
set countNext[countPrev[head]] = countNext[head]
set countPrev[countNext[head]] = countPrev[head]
//Add to the new Count List
set cHead = countHead[count[head]]
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set cHead = countHead[lower]
if countNext[cHead] == cHead then
set lower = lower + 1
endif
if count[head] > upper then
set upper = count[head]
endif
elseif this == 0 then
call RemoveUnit(u)
debug elseif next[this] != -1 then
debug call BJDebugMsg("|cffffcc00[DummyRecycler]:|r Attempted to recycle a pending/free Dummy Unit.")
endif
endfunction
private function Expires takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
static if LIBRARY_Table then
call RecycleDummy(S.tb.unit[id])
call S.tb.unit.remove(id)
else
call RecycleDummy(LoadUnitHandle(S.hash, id, 0))
call FlushChildHashtable(S.hash, id)
endif
call DestroyTimer(t)
set t = null
endfunction
function DummyAddRecycleTimer takes unit u, real time returns nothing
local timer t = CreateTimer()
static if LIBRARY_Table then
set S.tb.unit[GetHandleId(t)] = u
else
call SaveUnitHandle(S.hash, GetHandleId(t), 0, u)
endif
call TimerStart(t, time, false, function Expires)
set t = null
endfunction
function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
return GetRecycledDummy(x, y, z, angle[countNext[countHead[upper]]])
endfunction
// runtextmacro DUMMY_DEBUG_TOOLS()
endlibrary
library ErrorMessage /* v1.0.1.4
*************************************************************************************
*
* Issue Compliant Error Messages
*
************************************************************************************
*
* debug function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
* - In the event of an error the game will be permanently paused
*
* debug function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
*
************************************************************************************/
static if DEBUG_MODE then
private struct Fields extends array
static constant string COLOR_RED = "|cffff0000"
static constant string COLOR_YELLOW = "|cffffff00"
static string lastError = null
endstruct
private function Pause takes nothing returns nothing
call PauseGame(true)
endfunction
private function ThrowMessage takes string libraryName, string functionName, string objectName, integer objectInstance, string description, string errorType, string color returns nothing
local string str
local string color_braces = "|cff66FF99"
local string orange = "|cffff6600"
set str = "->\n-> " + color_braces + "{|r " + "Library" + color_braces + "(" + orange + libraryName + color_braces + ")"
if (objectName != null) then
if (objectInstance > 0) then
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + " (|rinstance = " + orange + I2S(objectInstance) + color_braces + ") )" + "|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
else
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + ")|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
else
set str = str + "|r." + "Function" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
set str = str + color_braces + " }|r " + "has thrown an exception of type " + color_braces + "(" + color + errorType + color_braces + ")|r."
set Fields.lastError = str + "\n->\n" + "-> " + color + description + "|r\n->"
endfunction
function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Error", Fields.COLOR_RED)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
call TimerStart(CreateTimer(), 0, true, function Pause)
set objectInstance = 1/0
endif
endfunction
function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Warning", Fields.COLOR_YELLOW)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
set Fields.lastError = null
endif
endfunction
endif
endlibrary
library ReviveUnit /* 2.0.0.0
***************************************************
*
* Resurrects a unit from his corpse, retaining
* its handle ID, facing, and position.
*
***************************************************
*
* ReviveUnit(unit whichUnit) returns boolean
* - Resurrects the input unit.
*
***************************************************
*
* Configurables
* - Remove the ! from //! after saving.
*/
// external ObjectMerger w3a AHre URez anam "Dummy Resurrection" aher 0 acat "" atat "" Hre1 1 1 aare 1 0 aran 1 0 acdn 1 0 amcs 1 0 atar 1 "Air,Dead,Enemy,Friend,Ground,Neutral"
// external ObjectMerger w3u ushd eRez unam "Dummy" uabi "Aloc,Avul" ucbs 0 ucpt 0 umdl ".mdl" usca "0.01" ushu "None" umvh 0 umvs 0 ufoo 0 umpi 100000 umpm 100000 umpr 1000
globals
private constant integer DUMMY = 'eRez'
private constant integer RESURRECT = 'URez'
endglobals
/*
***************************************************
*
* Notes
* - Does not work on units without corpses.
* - The resurrected unit's mana is determined
* by the field: "Mana - Initial Amount"
*
***************************************************
*
* Importing: Automatic
* - Copy and paste this trigger.
* - Save the map, close it, and reopen it
* - Remove the exclamation ! from the object
* merger lines above.
*
* Importing: Manual
* - Copy and paste this trigger.
* - Copy and paste the dummy unit and resurrection
* ability from the object editor.
* - Change the configurable raw codes as necessary.
*
***************************************************/
globals
private unit reviver
private real rx
private real ry
endglobals
function ReviveUnit takes unit u returns boolean
local boolean success
if IsUnitType(u, UNIT_TYPE_HERO) then
return ReviveHero(u, GetUnitX(u), GetUnitY(u), false)
else
call SetUnitX(reviver, GetUnitX(u))
call SetUnitY(reviver, GetUnitY(u))
set success = IssueImmediateOrderById(reviver, 852094)
call SetUnitX(reviver, rx)
call SetUnitY(reviver, ry)
endif
return success
endfunction
private module Init
private static method onInit takes nothing returns nothing
set rx = GetRectMaxX(bj_mapInitialPlayableArea) - 1
set ry = GetRectMaxY(bj_mapInitialPlayableArea) - 1
set reviver = CreateUnit(Player(15), DUMMY, rx, ry, 0)
call SetUnitPathing(reviver, false)
call UnitAddAbility(reviver, RESURRECT)
endmethod
endmodule
struct Revive extends array
/* For backwards compatibility */
static method Unit takes unit whichUnit returns boolean
return ReviveUnit(whichUnit)
endmethod
implement Init
endstruct
endlibrary
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
library IllusionEditor /*
*/requires /*
*/DamageEvent /* https://www.hiveworkshop.com/threads/system-physical-damage-detection.228456/#post-2273240/
*/optional Alloc /* https://www.hiveworkshop.com/threads/unique-id-allocation.260897/
*/optional Table /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/#post-1826447/
*/optional TimerUtils /* http://www.wc3c.net/showthread.php?p=1020244
*/optional UnitRecycler /* https://www.hiveworkshop.com/threads/snippet-unit-recycler.286701/#post-3078532/
*/optional DummyRecycler /* https://www.hiveworkshop.com/threads/dummy-recycler-v1-25.277659/
*///! novjass
_______________________
| |
| Illusion Editor v1.5c |
| Written by AGD |
|_______________________|
/*
This system gives you freedom and ease in creating illusions. With this, you
can now create illusions of any type as easily as creating units or make an
illusion copy from an already existing unit, with the ability to change many
of their aspects dynamically.
Hint:
- Use zero or any negative value for duration upon creation to make a permanent illusion
- Upon updating an illusion's duration, a zero value would make it permanent while a
negative value would kill it instantly
To Import:
- Copy all the neccessary Object Editor data or alternatively use the object merger
- Copy this trigger/trigger category together with the requirements
- Configure the configurable object rawcodes
*/
|============|
| Struct API |
|============|
struct Illusion/*
*/readonly static unit lastCreated /* The last created illusion unit
*/readonly static unit dying /* The dying illusion unit (Event response)
*/readonly unit unit /* The illusion unit
*/readonly unit baseUnit /* The unit source from where the illusion was created
*/readonly real elapsed /* The elapsed time of the illusion
*/readonly real remaining /* The remaining time of the illusion
*/real duration /* The duration of the illusion's timed life
*/real damageFactor /* The damage dealt factor
*/real receiveFactor /* The damage receive factor
*/string deathEffect /* The special effect played when the illusion dies
*/static method create takes player whichPlayer, integer unitId, real duration, real damageFactor, real receiveFactor, real x, real y, real angle returns Illusion/*
- Creates a new illusion unit of a specific type
*/static method copy takes player whichPlayer, unit baseUnit, real duration, real damageFactor, real receiveFactor, real x, real y, real angle returns Illusion/*
- Creates a new illusion copy of a given unit
*/static method operator [] takes unit u returns Illusion/*
- Returns the Illusion instance of the illusion unit
*/method destroy takes nothing returns nothing/*
- Destroys an Illusion instance
*/method addDuration takes real duration returns nothing/*
- Adds extra time to the illusion's duration
*/static method registerDeathHandler takes code c returns nothing/*
*/static method unregisterDeathHandler takes code c returns nothing/*
- Registers/Unregisters an illusion death handler code
*/
|==============|
| Function API |
|==============|
function CreateIllusion takes player whichPlayer, integer unitId, real duration, real damageFactor, real receiveFactor, real x, real y, real angle returns unit/*
- Creates an illusion of a specific unit type
*/function CreateIllusionCopy takes player whichPlayer, unit u, real duration, real damageFactor, real receiveFactor, real x, real y, real angle returns unit/*
- Creates an illusion copy of a unit which possesses the same abilities and items as the original unit
*/function SetIllusionDuration takes unit u, real time returns nothing/*
- Sets the duration of the illusion
(This only works for illusions created by this system)
*/function AddIllusionDuration takes unit u, real extraTime returns nothing/*
- Adds an extra duration to an illusion's timed life
(This only works for illusions created by this system)
*/function SetIllusionDamageFactor takes unit u, real factor returns nothing/*
- Sets the damage dealt factor of the illusion
(This only works for illusions created by this system)
*/function SetIllusionReceiveFactor takes unit u, real factor returns nothing/*
- Sets the damage receive factor of the illusion
(This only works for illusions created by this system)
*/function SetIllusionDeathEffect takes unit u, string model returns nothing/*
- Sets the death animation of the illusion
(This only works for illusions created by this system)
*/function GetIllusionElapsedTime takes unit u returns real/*
- Returns the elapsed time of the illusion
(This will return 0 for illusions not created by this system)
*/function GetIllusionRemainingTime takes unit u returns real/*
- Returns the remaining duration of the illusion
(This will return 0 for illusions not created by this system)
*/function GetIllusionDuration takes unit u returns real/*
- Returns the duration of the illusion
(This will return 0 for illusions not created by this system)
*/function GetIllusionDamageFactor takes unit u returns real/*
- Returns the damagedealt factor of the illusion
(This will return 0 for illusions not created by this system)
*/function GetIllusionReceiveFactor takes unit u returns real/*
- Returns the damagereceive factor of the illusion
(This will return 0 for illusions not created by this system)
*/function GetIllusionBaseUnit takes unit u returns unit/*
- Returns the unit that the illusion is copied from
(Only works for illusion products of CopyUnit())
*/function GetIllusionFlag takes unit u returns boolean/*
- Checks if the illusion is created by this system or not
*/function GetLastCreatedIllusion takes nothing returns unit/*
- Returns the last illusion created using this system
*/function GetDyingIllusion takes nothing returns unit/*
- Refers to the dying illusion (Event Response)
*/function RegisterIllusionDeathHandler takes code c returns nothing/*
- Regsiters a code from the illusion death handler
*/function UnregisterIllusionDeathHandler takes code c returns nothing/*
- Unregsiters a code from the illusion death handler
*///! endnovjass
/*=============================== System Configuration ==============================*/
globals
/*
Rawcode of the item used for creating illusions */
private constant integer ABIL_ID = 'Illu'
/*
Rawcode of the dummy caster */
private constant integer CASTER_ID = 'ictr'
/*
Rawcode of the dummy unit used for attaching the illusion's death special effect */
private constant integer DUMMY_ID = 'idum'
/*
The default death animation of the illusions */
private constant string DEFAULT_DEATH_SFX = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageDeathCaster.mdl"
/*
The owning player of the dummy unit */
private constant player DUMMY_OWNER = Player(14)
/*
Order id of the illusion ability */
private constant integer ORDER_ID = 852274
/*
The prefix of the operation error message when debug mode is enabled */
private constant string ERROR_PREFIX = "|CFFFF0000ERROR: "
endglobals
/*=========================== End of System Configuartion ===========================*/
/* Do not change anything below this line if you're not so sure on what you're doing */
/*===================================================================================*/
native UnitAlive takes unit u returns boolean
private module IllusionStructCore
readonly static unit lastCreated = null
readonly static unit dying = null
readonly unit unit
readonly unit baseUnit
real damageFactor
real receiveFactor
string deathEffect
private thistype prev
private thistype next
private boolean permanent
private timer lifeTimer
private real prevElapsed
private static integer deathHandlerCount = 0
private static integer unusedDeathEvent = 0
private static unit dummyCaster
private static boolexpr onDeathExpr
private static trigger onSummonTrigger = CreateTrigger()
private static trigger onDeathTrigger = CreateTrigger()
private static trigger onDeathHandler = null
static if LIBRARY_Table then
private static Table table
else
private static hashtable table = InitHashtable()
endif
implement optional Alloc
debug private static method debug takes string msg returns nothing
debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|CFFFFCC00[IllusionEditor]|R " + msg)
debug endmethod
private static method onExpire takes nothing returns nothing
static if LIBRARY_TimerUtils then
local thistype this = GetTimerData(GetExpiredTimer())
elseif LIBRARY_Table then
local thistype this = table[GetHandleId(GetExpiredTimer())]
else
local thistype this = LoadInteger(table, 0, GetHandleId(GetExpiredTimer()))
endif
call KillUnit(this.unit)
if this.unit == lastCreated then
set lastCreated = this.prev.unit
endif
endmethod
static method copy takes player whichPlayer, unit source, real duration, real damageDealt, real damageReceived, real x, real y, real facing returns thistype
local thistype this
local thistype last
if not IssueTargetOrderById(dummyCaster, ORDER_ID, source) or lastCreated == null then
debug call debug(ERROR_PREFIX + "Unable to create illusion unit")
return 0
endif
set this = allocate()
set last = thistype(0).prev
set this.next = 0
set this.prev = last
set last.next = this
set thistype(0).prev = this
set this.damageFactor = damageDealt
set this.receiveFactor = damageReceived
set this.deathEffect = DEFAULT_DEATH_SFX
set this.baseUnit = source
set this.unit = lastCreated
static if LIBRARY_Table then
set table[GetHandleId(lastCreated)] = this
else
call SaveInteger(table, 0, GetHandleId(lastCreated), this)
endif
static if LIBRARY_TimerUtils then
set this.lifeTimer = NewTimerEx(this)
else
set this.lifeTimer = CreateTimer()
static if LIBRARY_Table then
set table[GetHandleId(this.lifeTimer)] = this
else
call SaveInteger(table, 0, GetHandleId(this.lifeTimer), this)
endif
endif
if duration > 0.00 then
call TimerStart(this.lifeTimer, duration, false, function thistype.onExpire)
else
set this.permanent = true
call TimerStart(this.lifeTimer, 1000000000.00, false, function thistype.onExpire)
endif
call SetUnitOwner(lastCreated, whichPlayer, true)
call SetUnitFacingTimed(lastCreated, facing, 0)
if IsUnitType(lastCreated, UNIT_TYPE_STRUCTURE) then
call SetUnitPosition(lastCreated, x, y)
call SetUnitAnimation(lastCreated, "stand")
else
call SetUnitX(lastCreated, x)
call SetUnitY(lastCreated, y)
endif
call TriggerRegisterUnitStateEvent(onDeathTrigger, lastCreated, UNIT_STATE_LIFE, LESS_THAN, UNIT_MIN_LIFE)
return this
endmethod
static method create takes player whichPlayer, integer rawCode, real duration, real damageDealt, real damageReceived, real x, real y, real angle returns thistype
static if LIBRARY_UnitRecycler then
local unit tempUnit = GetRecycledUnitEx(DUMMY_OWNER, rawCode, x, y, angle)
local thistype this = copy(whichPlayer, tempUnit, duration, damageDealt, damageReceived, x, y, angle)
call RecycleUnitEx(tempUnit)
else
local unit tempUnit = CreateUnit(DUMMY_OWNER, rawCode, x, y, angle)
local thistype this = copy(whichPlayer, tempUnit, duration, damageDealt, damageReceived, x, y, angle)
call RemoveUnit(tempUnit)
endif
set tempUnit = null
return this
endmethod
method destroy takes nothing returns nothing
local thistype prev = this.prev
local thistype next = this.next
debug if this.unit == null then
debug call debug(ERROR_PREFIX + "Attempted to destroy an already destroyed instance")
debug return
debug endif
static if LIBRARY_TimerUtils then
call ReleaseTimer(this.lifeTimer)
else
call DestroyTimer(this.lifeTimer)
static if LIBRARY_Table then
call table.remove(GetHandleId(this.lifeTimer))
else
call RemoveSavedInteger(table, 0, GetHandleId(this.lifeTimer))
endif
endif
static if LIBRARY_Table then
call table.remove(GetHandleId(this.unit))
else
call RemoveSavedInteger(table, 0, GetHandleId(this.unit))
endif
call RemoveUnit(this.unit)
call this.deallocate()
set next.prev = prev
set prev.next = next
set this.permanent = false
set this.lifeTimer = null
set this.unit = null
set this.baseUnit = null
set this.damageFactor = 0
set this.receiveFactor = 0
set this.deathEffect = null
set this.prevElapsed = 0.00
set unusedDeathEvent = unusedDeathEvent + 1
if unusedDeathEvent == 50 then
set unusedDeathEvent = 0
call DestroyTrigger(onDeathTrigger)
set onDeathTrigger = CreateTrigger()
call TriggerAddCondition(onDeathTrigger, onDeathExpr)
set this = thistype(0).next
loop
exitwhen this == 0
call TriggerRegisterUnitStateEvent(onDeathTrigger, this.unit, UNIT_STATE_LIFE, LESS_THAN, UNIT_MIN_LIFE)
set this = this.next
endloop
endif
endmethod
static method operator [] takes unit u returns thistype
static if LIBRARY_Table then
return table[GetHandleId(u)]
else
return LoadInteger(table, 0, GetHandleId(u))
endif
endmethod
method operator duration= takes real duration returns nothing
local real elapsed = TimerGetElapsed(this.lifeTimer) + .prevElapsed
if duration > 0.00 then
/* If the input duration < elapsed time, kill the illusion instantly */
if duration < elapsed then
call TimerStart(this.lifeTimer, 0.00, false, function thistype.onExpire)
/* Else, update the illusion's duration */
else
call TimerStart(this.lifeTimer, duration - elapsed, false, function thistype.onExpire)
endif
/* If the given duration <= 0, make the illusion permanent
The timer is not paused so that the elapsed time can
still be retrieved */
else
set this.permanent = true
call TimerStart(this.lifeTimer, 1000000000.00, false, function thistype.onExpire)
endif
set this.prevElapsed = elapsed
endmethod
method operator duration takes nothing returns real
if this.permanent then
return 0.00
endif
return TimerGetTimeout(this.lifeTimer) + this.prevElapsed
endmethod
method operator remaining takes nothing returns real
if this.permanent then
return 0.00
endif
return TimerGetRemaining(this.lifeTimer)
endmethod
method operator elapsed takes nothing returns real
return TimerGetElapsed(this.lifeTimer) + this.prevElapsed
endmethod
method addDuration takes real duration returns nothing
if not this.permanent then
set this.duration = this.duration + duration
endif
endmethod
static method registerDeathHandler takes code c returns nothing
local boolexpr expr = Filter(c)
static if LIBRARY_Table then
debug if table.handle.has(GetHandleId(expr)) then
debug call debug(ERROR_PREFIX + "Attempted to register an already registered death handler code")
debug return
debug endif
else
debug if HaveSavedHandle(table, 0, GetHandleId(expr)) then
debug call debug(ERROR_PREFIX + "Attempted to register an already registered death handler code")
debug return
debug endif
endif
if deathHandlerCount == 0 then
set onDeathHandler = CreateTrigger()
endif
set deathHandlerCount = deathHandlerCount + 1
static if LIBRARY_Table then
set table.triggercondition[GetHandleId(expr)] = TriggerAddCondition(onDeathHandler, expr)
else
call SaveTriggerConditionHandle(table, 0, GetHandleId(expr), TriggerAddCondition(onDeathHandler, expr))
endif
set expr = null
endmethod
static method unregisterDeathHandler takes code c returns nothing
local integer handleId = GetHandleId(Filter(c))
static if LIBRARY_Table then
debug if not table.handle.has(handleId) then
debug call debug(ERROR_PREFIX + "Attempted to register an already registered death handler code")
debug return
debug endif
call TriggerRemoveCondition(onDeathHandler, table.triggercondition[handleId])
call table.handle.remove(handleId)
else
debug if not HaveSavedHandle(table, 0, handleId) then
debug call debug(ERROR_PREFIX + "Attempted to register an already registered death handler code")
debug return
debug endif
call TriggerRemoveCondition(onDeathHandler, LoadTriggerConditionHandle(table, 0, handleId))
call RemoveSavedHandle(table, 0, handleId)
endif
set deathHandlerCount = deathHandlerCount - 1
if deathHandlerCount == 0 then
call DestroyTrigger(onDeathHandler)
set onDeathHandler = null
endif
endmethod
private static method onSummon takes nothing returns nothing
set lastCreated = GetSummonedUnit()
endmethod
private static method adjustDamage takes nothing returns nothing
local thistype this = thistype[PDDS.source]
local thistype targ = thistype[PDDS.target]
if this != 0 then
set PDDS.amount = PDDS.amount*this.damageFactor
endif
if targ != 0 then
set PDDS.amount = PDDS.amount*targ.receiveFactor
endif
endmethod
private static method onDeath takes nothing returns nothing
local unit dummy
local unit prevDying
local unit u = GetTriggerUnit()
local thistype this = thistype[u]
if this != 0 then
call ShowUnit(u, false)
set prevDying = dying
set dying = u
call TriggerEvaluate(onDeathHandler)
set dying = prevDying
set prevDying = null
if this.deathEffect != "" then
static if LIBRARY_DummyRecycler then
set dummy = GetRecycledDummy(GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u), GetUnitFacing(u))
call DummyAddRecycleTimer(dummy, 2)
elseif LIBRARY_UnitRecycler then
set dummy = GetRecycledUnit(DUMMY_OWNER, DUMMY_ID, GetUnitX(u), GetUnitY(u), GetUnitFacing(u))
call RecycleUnitDelayed(dummy, 2)
call SetUnitFlyHeight(dummy, GetUnitFlyHeight(u), 0)
else
set dummy = CreateUnit(DUMMY_OWNER, DUMMY_ID, GetUnitX(u), GetUnitY(u), GetUnitFacing(u))
call UnitApplyTimedLife(dummy, 'BTLF', 2)
call SetUnitFlyHeight(dummy, GetUnitFlyHeight(u), 0)
endif
call DestroyEffect(AddSpecialEffectTarget(this.deathEffect, dummy, "origin"))
set dummy = null
endif
call this.destroy()
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
local code onSummon = function thistype.onSummon
local code onDeath = function thistype.onDeath
static if LIBRARY_Table then
set table = Table.create()
endif
call Preload(DEFAULT_DEATH_SFX)
set onDeathExpr = Filter(onDeath)
set dummyCaster = CreateUnit(DUMMY_OWNER, CASTER_ID, 0, 0, 0)
call UnitAddAbility(dummyCaster, ABIL_ID)
call AddDamageHandler(function thistype.adjustDamage)
call TriggerAddCondition(onDeathTrigger, onDeathExpr)
call TriggerRegisterUnitEvent(onSummonTrigger, dummyCaster, EVENT_UNIT_SUMMON)
call TriggerAddCondition(onSummonTrigger, Filter(onSummon))
endmethod
endmodule
static if LIBRARY_Alloc then
struct Illusion extends array
implement IllusionStructCore
endstruct
else
struct Illusion
implement IllusionStructCore
endstruct
endif
/*===============================================================================================================*/
function CreateIllusion takes player whichPlayer, integer unitId, real duration, real damageDealt, real damageReceived, real x, real y, real angle returns unit
return Illusion.create(whichPlayer, unitId, duration, damageDealt, damageReceived, x, y, angle).unit
endfunction
function CreateIllusionCopy takes player whichPlayer, unit baseUnit, real duration, real damageDealt, real damageReceived, real x, real y, real angle returns unit
return Illusion.copy(whichPlayer, baseUnit, duration, damageDealt, damageReceived, x, y, angle).unit
endfunction
function SetIllusionDuration takes unit u, real duration returns nothing
set Illusion[u].duration = duration
endfunction
function AddIllusionDuration takes unit u, real duration returns nothing
call Illusion[u].addDuration(duration)
endfunction
function SetIllusionDamageFactor takes unit u, real factor returns nothing
set Illusion[u].damageFactor = factor
endfunction
function SetIllusionReceiveFactor takes unit u, real factor returns nothing
set Illusion[u].receiveFactor = factor
endfunction
function SetIllusionDeathEffect takes unit u, string model returns nothing
set Illusion[u].deathEffect = model
endfunction
function GetIllusionElapsedTime takes unit u returns real
return Illusion[u].elapsed
endfunction
function GetIllusionRemainingTime takes unit u returns real
return Illusion[u].remaining
endfunction
function GetIllusionDuration takes unit u returns real
return Illusion[u].duration
endfunction
function GetIllusionDamageFactor takes unit u returns real
return Illusion[u].damageFactor
endfunction
function GetIllusionReceiveFactor takes unit u returns real
return Illusion[u].receiveFactor
endfunction
function GetIllusionBaseUnit takes unit u returns unit
return Illusion[u].baseUnit
endfunction
function GetIllusionFlag takes unit u returns boolean
return Illusion[u] != 0
endfunction
function GetLastCreatedIllusion takes nothing returns unit
return Illusion.lastCreated
endfunction
function GetDyingIllusion takes nothing returns unit
return Illusion.dying
endfunction
function RegisterIllusionDeathHandler takes code c returns nothing
call Illusion.registerDeathHandler(c)
endfunction
function UnregisterIllusionDeathHandler takes code c returns nothing
call Illusion.unregisterDeathHandler(c)
endfunction
endlibrary
library IllusionEditorGUI initializer Init /*
*/uses /*
*/IllusionEditor /* http://www.hiveworkshop.com/bundles/illusion-editor-v1-5b-vjass-gui-support.286441
*/
private function Operation0 takes nothing returns nothing
set udg_IE_Illusion = GetLastCreatedIllusion()
endfunction
private function Operation1 takes nothing returns nothing
set udg_IE_Illusion = CreateIllusion(udg_IE_Owner, udg_IE_UnitType, udg_IE_Duration, udg_IE_DamageDealtFactor, udg_IE_DamageReceiveFactor, GetLocationX(udg_IE_Position), GetLocationY(udg_IE_Position), udg_IE_Facing)
set udg_IE_Operation = 0
set udg_IE_Owner = null
set udg_IE_UnitType = 0
set udg_IE_Duration = 0.00
set udg_IE_DamageDealtFactor = 0.00
set udg_IE_DamageReceiveFactor = 0.00
set udg_IE_Facing = 0.00
endfunction
private function Operation2 takes nothing returns nothing
set udg_IE_Illusion = CreateIllusionCopy(udg_IE_Owner, udg_IE_BaseUnit, udg_IE_Duration, udg_IE_DamageDealtFactor, udg_IE_DamageReceiveFactor, GetLocationX(udg_IE_Position), GetLocationY(udg_IE_Position), udg_IE_Facing)
set udg_IE_Operation = 0
set udg_IE_Owner = null
set udg_IE_BaseUnit = null
set udg_IE_Duration = 0.00
set udg_IE_DamageDealtFactor = 0.00
set udg_IE_DamageReceiveFactor = 0.00
set udg_IE_Facing = 0.00
endfunction
private function Operation3 takes nothing returns nothing
call SetIllusionDuration(udg_IE_Illusion, udg_IE_Duration)
set udg_IE_Operation = 0
endfunction
private function Operation4 takes nothing returns nothing
call AddIllusionDuration(udg_IE_Illusion, udg_IE_Duration)
set udg_IE_Operation = 0
endfunction
private function Operation5 takes nothing returns nothing
call SetIllusionDamageFactor(udg_IE_Illusion, udg_IE_DamageDealtFactor)
set udg_IE_Operation = 0
endfunction
private function Operation6 takes nothing returns nothing
call SetIllusionReceiveFactor(udg_IE_Illusion, udg_IE_DamageReceiveFactor)
set udg_IE_Operation = 0
endfunction
private function Operation7 takes nothing returns nothing
call SetIllusionDeathEffect(udg_IE_Illusion, udg_IE_DeathEffect)
set udg_IE_Operation = 0
endfunction
private function Operation8 takes nothing returns nothing
set udg_IE_Duration = GetIllusionElapsedTime(udg_IE_Illusion)
set udg_IE_Operation = 0
endfunction
private function Operation9 takes nothing returns nothing
set udg_IE_Duration = GetIllusionRemainingTime(udg_IE_Illusion)
set udg_IE_Operation = 0
endfunction
private function Operation10 takes nothing returns nothing
set udg_IE_Duration = GetIllusionDuration(udg_IE_Illusion)
set udg_IE_Operation = 0
endfunction
private function Operation11 takes nothing returns nothing
set udg_IE_DamageDealtFactor = GetIllusionDamageFactor(udg_IE_Illusion)
set udg_IE_Operation = 0
endfunction
private function Operation12 takes nothing returns nothing
set udg_IE_DamageReceiveFactor = GetIllusionReceiveFactor(udg_IE_Illusion)
set udg_IE_Operation = 0
endfunction
private function Operation13 takes nothing returns nothing
set udg_IE_BaseUnit = GetIllusionBaseUnit(udg_IE_Illusion)
set udg_IE_Operation = 0
endfunction
private function Operation14 takes nothing returns nothing
set udg_IE_Flag = GetIllusionFlag(udg_IE_Illusion)
set udg_IE_Operation = 0
endfunction
private function Operate takes nothing returns nothing
if udg_IE_Operation < 8 then
if udg_IE_Operation < 4 then
if udg_IE_Operation < 2 then
if udg_IE_Operation < 1 then
call Operation0()
else
call Operation1()
endif
else
if udg_IE_Operation < 3 then
call Operation2()
else
call Operation3()
endif
endif
else
if udg_IE_Operation < 6 then
if udg_IE_Operation < 5 then
call Operation4()
else
call Operation5()
endif
else
if udg_IE_Operation < 7 then
call Operation6()
else
call Operation7()
endif
endif
endif
else
if udg_IE_Operation < 12 then
if udg_IE_Operation < 10 then
if udg_IE_Operation < 9 then
call Operation8()
else
call Operation9()
endif
else
if udg_IE_Operation < 11 then
call Operation10()
else
call Operation11()
endif
endif
else
if udg_IE_Operation < 14 then
if udg_IE_Operation < 13 then
call Operation12()
else
call Operation13()
endif
else
call Operation14()
endif
endif
endif
endfunction
private function IllusionDeathEventGUI takes nothing returns nothing
local unit prevDying = udg_IE_DyingIllusion
set udg_IE_DyingIllusion = GetDyingIllusion()
set udg_IE_IllusionDeathEvent = 0.00
set udg_IE_IllusionDeathEvent = 1.00
set udg_IE_IllusionDeathEvent = 0.00
set udg_IE_DyingIllusion = prevDying
set prevDying = null
endfunction
private function Init takes nothing returns nothing
set gg_trg_IllusionEditor_GUI_Support = CreateTrigger()
call TriggerAddAction(gg_trg_IllusionEditor_GUI_Support, function Operate)
call RegisterIllusionDeathHandler(function IllusionDeathEventGUI)
endfunction
endlibrary
function InitTrig_IllusionEditor_GUI_Support takes nothing returns nothing
/* We use an InitTrig function instead of a scope/library
initializer to initialize the values of our variables to
make sure that InitGlobals() runs first so that the values
of our variables would not be reset. */
call Trig_GUI_IllusionEditor_Documentation_Actions.execute()
endfunction
//!| [ ILLUSION EDITOR OBJECT MERGER ] |
/*
Paste this script to your map and it save it. Close your map, open it again and disable/delete this script.
Now you have the all the object data needed for the Illusion Editor in your map.
*/
//! external ObjectMerger w3u nzin ictr anam "DummyCaster" ansf "(IllusionEditor)" uabi "Ainv,Aloc" ucbs 0 ushr 0 umdl "dummy.mdl" upat "" unbm 0 usid 1000000000 usin 1000000000
//! external ObjectMerger w3u nzin idum anam "Dummy" ansf "(IllusionEditor)" uabi "Ainv,Aloc,Amrf" ucbs 0 ushr 0 umdl "dummy.mdl" umvs 522 umvr 3 upat "" unbm 0
//! external ObjectMerger w3a Alil Illu anam "illu" atat "" lild 1 1 lilw 1 1 aare 1 0 aran 1 99999 ahdu 1 1000000000.00 adur 1 1000000000.00 atar 1 "air,alive,allies,ancient,bridge,dead,debris,decoration,enemies,friend,ground,hero,invulnerable,item,mechanical,neutral,nonancient,nonhero,nonsapper,notself,organic,player,self,structure,sapper,terrain,tree,vulnerable,wall,ward"
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
* v1.2.1, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex assigns every unit an unique integer. It attempts to make that number within the
* maximum array limit so you can associate it with one.
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
// external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/* ________________________________________________________________________
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
private module UnitDexConfig
// The raw code of the leave detection ability.
static constant integer DETECT_LEAVE_ABILITY = 'uDex'
// Allow debug messages (debug mode must also be on)
static constant boolean ALLOW_DEBUGGING = true
// Uncomment the lines below to define a filter for units being indexed
/*static method onFilter takes unit u returns boolean
return true
endmethod*/
endmodule
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines except for UnitDexRemove
*
* function GetUnitId takes unit whichUnit returns integer
* function GetUnitById takes integer index returns unit
*
* function UnitDexEnable takes boolean flag returns nothing
* function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
* function IsUnitIndexed takes unit u returns boolean
* function IsIndexingEnabled takes nothing returns boolean
*
* function GetIndexedUnit takes nothing returns unit
* function GetIndexedUnitId takes nothing returns integer
*
* function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
* function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
* function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
* function OnUnitIndex takes code func returns triggercondition
* function OnUnitDeidex takes code func returns triggercondition
* _________________________________________________________________________
* 4. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex.Enabled = false // toggle the indexer
* UnitDex.Initialized // returns true if the preload timer has finished
* UnitDex.Count // returns the amount of units indexed
* UnitDex.Unit[0] // access the UnitDex array directly
* UnitDex.Group // a unit group containing every indexed unit (for enumeration)
* UnitDex.LastIndex // returns the last indexed unit's id
* _________________________________________________________________________
* 5. Public Variables
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* These are to be used with the "eventtype" argument of the event API:
*
* constant integer EVENT_UNIT_INDEX = 0
* constant integer EVENT_UNIT_DEINDEX = 1
* _________________________________________________________________________
* 6. Examples
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Associate a unit with a variable
*
* set UnitFlag[GetUnitId(yourUnit)] = true
*
* 2. Allocate a struct instance using a units assigned ID
*
* local somestruct data = GetUnitId(yourUnit)
*
* 3. Detect when a unit leaves the map
*
* function Exit takes nothing returns nothing
* call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
* endfunction
*
* call OnUnitDeindex(function Exit)
* // or
* call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
* _________________________________________________________________________
* 7. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Enumerate all preplaced units
* 2. TriggerRegisterEnterRegion native to detect when a unit enters the map
* 3. Assign each unit that enters the map a unique integer
* 4. Give every unit an ability based off of defend. There is no native to accurately
* detect when a unit leaves the map but when a unit dies or is removed from the game
* they are issued the "undefend" order
* 5. Catch the "undefend" order and remove unit from the queue
* _________________________________________________________________________
* 8. Notes
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
* - The object merger line should be commented out after saving and restarting.
* - All public functions are inlined except UnitDexRemove.
*
* -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
// Event types
constant integer EVENT_UNIT_INDEX = 0
constant integer EVENT_UNIT_DEINDEX = 1
// System variables
private trigger array IndexTrig
private integer Index = 0
private real E=-1
private boolexpr FilterEnter
endglobals
function GetUnitId takes unit whichUnit returns integer
return GetUnitUserData(whichUnit)
endfunction
function GetUnitById takes integer index returns unit
return UnitDex.Unit[index]
endfunction
function GetIndexedUnit takes nothing returns unit
return UnitDex.Unit[UnitDex.LastIndex]
endfunction
function GetIndexedUnitId takes nothing returns integer
return UnitDex.LastIndex
endfunction
function IsUnitIndexed takes unit u returns boolean
return (GetUnitById(GetUnitId(u)) != null)
endfunction
function UnitDexEnable takes boolean flag returns nothing
set UnitDex.Enabled = flag
endfunction
function IsIndexingEnabled takes nothing returns boolean
return UnitDex.Enabled
endfunction
function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
return TriggerAddCondition(IndexTrig[eventtype], func)
endfunction
function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
call TriggerRemoveCondition(IndexTrig[eventtype], c)
endfunction
function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
endfunction
function OnUnitIndex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
endfunction
function OnUnitDeindex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
endfunction
function UnitDexRemove takes unit u, boolean runEvents returns boolean
return UnitDex.Remove(u, runEvents)
endfunction
/****************************************************************/
private keyword UnitDexCore
struct UnitDex extends array
static boolean Enabled = true
readonly static integer LastIndex
readonly static boolean Initialized=false
readonly static group Group=CreateGroup()
readonly static unit array Unit
readonly static integer Count = 0
readonly static integer array List
implement UnitDexConfig
private static integer Counter = 0
implement UnitDexCore
endstruct
/****************************************************************/
private module UnitDexCore
static method Remove takes unit u, boolean runEvents returns boolean
local integer i
if (IsUnitIndexed(u)) then
set i = GetUnitId(u)
set UnitDex.List[i] = Index
set Index = i
call GroupRemoveUnit(UnitDex.Group, u)
call SetUnitUserData(u, 0)
if (runEvents) then
set UnitDex.LastIndex = i
set E = EVENT_UNIT_DEINDEX
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
set E = -1
endif
set UnitDex.Unit[i] = null
set UnitDex.Count = UnitDex.Count - 1
return true
endif
return false
endmethod
private static method onGameStart takes nothing returns nothing
local integer i = 0
static if (not LIBRARY_GroupUtils) then // Check if GroupUtils exists so we can use it's enumeration group.
local group ENUM_GROUP = CreateGroup() // If not, create the group.
endif
// Index preplaced units
loop
call GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(i), FilterEnter)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
static if (not LIBRARY_GroupUtils) then
call DestroyGroup(ENUM_GROUP)
set ENUM_GROUP = null
endif
// run init triggers
set i = 1
loop
exitwhen i > Counter
set LastIndex = i
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
set E = EVENT_UNIT_INDEX
set E = -1
set i = i + 1
endloop
set LastIndex = Counter
set Initialized = true
set FilterEnter = null
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onEnter takes nothing returns boolean
local unit u = GetFilterUnit()
local integer i = GetUnitId(u)
local integer t = Index
if (i == 0 and thistype.Enabled) then
// If a filter was defined pass the unit through it.
static if (thistype.onFilter.exists) then
if (not thistype.onFilter(u)) then
set u = null
return false // check failed
endif
endif
// Handle debugging
static if (thistype.DEBUG_MODE and thistype.ALLOW_DEBUGGING) then
if (t == 0 and Counter+1 >= JASS_MAX_ARRAY_SIZE) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached!")
set u = null
return false
endif
endif
// Add to group of indexed units
call GroupAddUnit(thistype.Group, u)
// Give unit the leave detection ability
call UnitAddAbility(u, thistype.DETECT_LEAVE_ABILITY)
call UnitMakeAbilityPermanent(u, true, thistype.DETECT_LEAVE_ABILITY)
// Allocate index
if (Index != 0) then
set Index = List[t]
else
set Counter = Counter + 1
set t = Counter
endif
set List[t] = -1
set LastIndex = t
set thistype.Unit[t] = u
set Count = Count + 1
// Store the index
call SetUnitUserData(u, t)
if (thistype.Initialized) then
// Execute custom events registered with RegisterUnitIndexEvent
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_INDEX
// Reset so the event can occur again
set E = -1
endif
endif
set u = null
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit u
local integer i
// Check if order is undefend.
if (thistype.Enabled and GetIssuedOrderId() == 852056) then
set u = GetTriggerUnit()
// If unit was killed (not removed) then don't continue
if (GetUnitAbilityLevel(u, thistype.DETECT_LEAVE_ABILITY) != 0) then
set u = null
return false
endif
set i = GetUnitId(u)
// If unit has been indexed then deindex it
if (i > 0 and i <= Counter and u == GetUnitById(i)) then
// Recycle the index
set List[i] = Index
set Index = i
set LastIndex = i
// Remove to group of indexed units
call GroupRemoveUnit(thistype.Group, u)
// Execute custom events without any associated triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_DEINDEX
// Remove entry
call SetUnitUserData(u, 0)
set thistype.Unit[i] = null
// Decrement unit count
set Count = Count - 1
// Reset so the event can occur again
set E = -1
endif
set u = null
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local player p
local unit u
static if (not LIBRARY_WorldBounds) then // Check if WorldBounts exists, if not then define the necessary vars
local region reg = CreateRegion() // If WorldBounds wasn't found, create the region manually
local rect world = GetWorldBounds()
endif
set FilterEnter = Filter(function thistype.onEnter)
// Begin to index units when they enter the map
static if (LIBRARY_WorldBounds) then
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, FilterEnter)
else
call RegionAddRect(reg, world)
call TriggerRegisterEnterRegion(CreateTrigger(), reg, FilterEnter)
call RemoveRect(world)
set world = null
endif
call TriggerAddCondition(t, Filter(function thistype.onLeave))
set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
loop
set p = Player(i)
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
// Hide the detect ability from players
call SetPlayerAbilityAvailable(p, thistype.DETECT_LEAVE_ABILITY, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TimerStart(CreateTimer(), 0, false, function thistype.onGameStart)
endmethod
endmodule
endlibrary
library StunSystem uses Table
//********************************************************************************
// Stun - Version 1.2.0.0 - By iAyanami aka Ayanami
//********************************************************************************
//
// Stun:
// - An easy to use system that stuns units
// - Able to keep track of the remaining stun duration
//
// Requirements:
// - JASS NewGen
// - Table
//
// Functions:
// - Stun.apply takes unit whichUnit, real duration, boolean stack returns nothing
// * whichUnit is the target to be stunned
// * duration is the duration of the stun
// * stack is to determine if the stun should be a stacking one or not
// * true - the stun will stack, the duration of the stun will add up with the previous duration
// * false - the stun will not stack, the unit will be stunned for the longer stun duration
//
// - Stun.getDuration takes unit whichUnit returns real
// * whichUnit is the target to check
// * returns the remaining stun duration
//
// - Stun.stop takes unit whichUnit returns nothing
// * removes stun from the target
//
// How to import:
// - Copy the whole "Stun" Trigger Folder into your map
// - Save the map. Close and re-opem the map.
// - You should have a new unit (Stun Dummy), ability (Stun (System)) and buff (Stun (System)) created
// - Read through the Configuration part of the code
//
// Credits:
// - Bribe for Table
//
//********************************************************************************
// CONFIGURABLES
//********************************************************************************
globals
// timer period. lower the value, the more accurate but might cause decrease in
// performance
private constant real PERIOD = 0.031250000
// raw code of ability "Stun (System)"
private constant integer ABILID = 'ASTN'
// raw code of buff "Stun (System)"
private constant integer BUFFID = 'BSTN'
// raw code of unit "Stun Dummy"
private constant integer STUNID = 'sTUN'
endglobals
//********************************************************************************
// CODE
//********************************************************************************
// initialization
module Init
private static method onInit takes nothing returns nothing
set table = Table.create()
set caster = CreateUnit(Player(13), STUNID, 0, 0, 0)
call UnitAddAbility(caster, ABILID)
endmethod
endmodule
struct Stun extends array
private unit u
private real dur
private thistype next
private thistype prev
private static Table table
private static timer t = CreateTimer()
private static unit caster
private static integer count = 0
// remove the stun and deallocate
private method destroy takes nothing returns nothing
call UnitRemoveAbility(this.u, BUFFID)
if this.next != 0 then
set this.next.prev = this.prev
endif
set this.prev.next = this.next
set this.dur = 0
set this.prev = thistype(0).prev
set thistype(0).prev = this
if thistype(0).next == 0 then
call PauseTimer(t)
endif
call table.remove(GetHandleId(this.u))
endmethod
// iterating through all instances every PERIOD
private static method iterate takes nothing returns nothing
local thistype this = thistype(0)
loop
set this = this.next
exitwhen this == 0
if this.dur <= 0 or IsUnitType(this.u, UNIT_TYPE_DEAD) or GetUnitTypeId(this.u) == 0 then
call this.destroy()
else
set this.dur = this.dur - PERIOD
endif
endloop
endmethod
// immediately removes stun for the specified unit
// ex: call Stun.stop(whichTarget)
static method stop takes unit u returns nothing
local integer id = GetHandleId(u)
if table.has(id) then
call thistype(table[id]).destroy()
endif
endmethod
// gets the duration left for stun, not stunned units always return 0
// ex: local real r = Stun.getDuration(whichTarget)
static method getDuration takes unit u returns real
return thistype(table[GetHandleId(u)]).dur
endmethod
// stunning specified target and to see if the stun is a stacking one or not
// ex: call Stun.apply(whichTarget, 5.0, false)
static method apply takes unit u, real dur, boolean b returns nothing
local thistype this
local integer id = GetHandleId(u)
if table.has(id) then
set this = table[id]
else
if thistype(0).prev == 0 then
set count = count + 1
set this = count
else
set this = thistype(0).prev
set thistype(0).prev = thistype(0).prev.prev
endif
if thistype(0).next == 0 then
call TimerStart(t, PERIOD, true, function thistype.iterate)
else
set thistype(0).next.prev = this
endif
set this.next = thistype(0).next
set thistype(0).next = this
set this.prev = thistype(0)
set table[id] = this
set this.u = u
set this.dur = 0
call IssueTargetOrder(caster, "firebolt", this.u)
endif
if b and dur > 0 then
set this.dur = this.dur + dur
else
if this.dur < dur then
set this.dur = dur
endif
endif
endmethod
implement Init
endstruct
endlibrary
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
/*
============= Why this is important? =================
1. Does not generate 16 events per spell.
2. This uses one trigger evaluation instead of one for each
individual spell (some maps have quite a few). This helps keep
framerate high and fluid when a spell is cast.
*/
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary
library PhantomStrike initializer Init /*
*/requires /*
*/IllusionEditor /* https://www.hiveworkshop.com/bundles/illusion-editor-v1-5-vjass-gui-support.286441
*/RegisterPlayerUnitEvent /* https://www.hiveworkshop.com/threads/snippet-registerplayerunitevent.203338/#post-2002332/
*/UnitDex /* https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
*/
/*===============================================*/
/* Configuration Section */
/*===============================================*/
globals
/*
Rawcode of the spell */
private constant integer SPELL_ID = 'PS00'
/*
Special effect displayed during the
Phantom's appearance */
private constant string SUMMON_SFX_MODEL = ""
private constant string SUMMON_SFX_ATTACH_POINT = "origin"
/*
Special effect displayed during the
Phantom's death or disappearance */
private constant string DEATH_SFX_MODEL = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageCaster.mdl"
/*
Determines if the phantoms are given the
locust ability */
private constant boolean INVULNERABLE = true
endglobals
/*
Activation chance per attack for the
original unit */
private constant function HeroActivationChance takes integer level returns real
return 80.00 + 0.00*level
endfunction
/*
Activation chance per attack for the
phantoms */
private constant function IllusionActivationChance takes integer level returns real
return 40.00 + 0.00*level
endfunction
/*
Maximum number of phantoms to exist at
the same time */
private constant function PhantomCount takes integer level returns integer
return 13 + 0*level
endfunction
/*
Duration of the phantoms */
private constant function PhantomDuration takes integer level returns real
return 0.50 + 0.25*level
endfunction
/*
The bonus duration given to the phantoms when
they successfully create their own phantom
double */
private constant function PhantomDurationBonus takes integer level returns real
return 0.00 + 0.00*level
endfunction
/*
Damage dealt by the phantoms in relation
to the original unit */
private constant function DamageFactor takes integer level returns real
return 0.15 + 0.00*level
endfunction
/*
Damage received by the phantoms in
relation to the original unit */
private constant function ReceiveFactor takes integer level returns real
return 4.00 + 0.00*level
endfunction
/*
The bonus ability given to the phantom */
private constant function BonusAbility takes integer level returns integer
return 'Aloc'
endfunction
/*===============================================*/
/* End of Configuration */
/*===============================================*/
/*====================================================================*/
globals
private boolean array isPhantom
private integer array illusionSourceId
private integer array count
endglobals
private function OnAttack takes nothing returns nothing
local unit attacker = GetAttacker()
local integer level = GetUnitAbilityLevel(attacker, SPELL_ID)
local unit target
local integer id
local real targetX
local real targetY
local real dx
local real dy
local real dist
local real angle
local real chance
local boolean phantom
if level > 0 then
set id = GetUnitId(attacker)
if isPhantom[id] then
set id = illusionSourceId[id]
set chance = IllusionActivationChance(level)
set phantom = true
else
set chance = HeroActivationChance(level)
set phantom = false
endif
if count[id] < PhantomCount(level) and GetRandomReal(0, 100) <= chance then
set count[id] = count[id] + 1
set target = GetTriggerUnit()
set targetX = GetUnitX(target)
set targetY = GetUnitY(target)
set dx = targetX - GetUnitX(attacker)
set dy = targetY - GetUnitY(attacker)
set dist = SquareRoot(dx*dx + dy*dy)
set angle = GetRandomReal(0, 2.00*bj_PI)
if phantom then
call AddIllusionDuration(attacker, PhantomDurationBonus(level))
endif
if GetUnitAbilityLevel(attacker, 'Aloc') > 0 then
call ShowUnit(attacker, false)
call UnitRemoveAbility(attacker, 'Aloc')
call ShowUnit(attacker, true)
call CreateIllusionCopy(GetOwningPlayer(attacker), attacker, PhantomDuration(level), DamageFactor(level), ReceiveFactor(level), targetX + dist*Cos(angle), targetY + dist*Sin(angle), angle*bj_RADTODEG - 180)
call UnitAddAbility(attacker, 'Aloc')
else
call CreateIllusionCopy(GetOwningPlayer(attacker), attacker, PhantomDuration(level), DamageFactor(level), ReceiveFactor(level), targetX + dist*Cos(angle), targetY + dist*Sin(angle), angle*bj_RADTODEG - 180)
endif
call UnitAddAbility(GetLastCreatedIllusion(), BonusAbility(level))
call DestroyEffect(AddSpecialEffectTarget(SUMMON_SFX_MODEL, GetLastCreatedIllusion(), SUMMON_SFX_ATTACH_POINT))
call SetUnitPathing(GetLastCreatedIllusion(), false)
call IssueTargetOrder(GetLastCreatedIllusion(), "attack", target)
call SetIllusionDeathEffect(GetLastCreatedIllusion(), DEATH_SFX_MODEL)
set isPhantom[GetUnitId(GetLastCreatedIllusion())] = true
loop
exitwhen not isPhantom[id]
set id = illusionSourceId[id]
endloop
set illusionSourceId[GetUnitId(GetLastCreatedIllusion())] = id
set target = null
endif
endif
set attacker = null
endfunction
private function OnDeath takes nothing returns nothing
local integer id = GetUnitId(GetDyingIllusion())
if isPhantom[id] then
set count[illusionSourceId[id]] = count[illusionSourceId[id]] - 1
set illusionSourceId[id] = 0
set isPhantom[id] = false
endif
endfunction
private function Init takes nothing returns nothing
call RegisterIllusionDeathHandler(function OnDeath)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function OnAttack)
endfunction
endlibrary
library SoulBurn initializer Init /*
*/uses /*
*/IllusionEditor /* https://www.hiveworkshop.com/bundles/illusion-editor-v1-5-vjass-gui-support.286441
*/DamageEvent /* https://www.hiveworkshop.com/threads/system-physical-damage-detection.228456/#post-2273240/
*/
/*===============================================*/
/* Configuration Section */
/*===============================================*/
globals
/*
Rawcode of the ability */
private constant integer ABIL_ID = 'SB00'
/*
Attack type */
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
/*
Damage type */
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
/*
Weapon type */
private constant weapontype WEAPON_TYPE = null
/*
Model of the special effect for the
mana burn */
private constant string MANA_BURN_SFX_MODEL = "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
private constant string MANA_BURN_SFX_ATTACH_POINT = "origin"
/*
Model of the special effect attached to the
target when attacked with an empty mana pool */
private constant string SOUL_BURN_SFX_MODEL = "Abilities\\Spells\\Undead\\DeathandDecay\\DeathandDecayTarget.mdl"
private constant string SOUL_BURN_SFX_ATTACH_POINT = "chest"
endglobals
/*
Mana burn amount */
private constant function ManaBurned takes integer level, real damageAmount returns real
return 1.00 + 1.00*level + 0.00*damageAmount
endfunction
/*
Bonus damage dealt to the enemy when being
mana burned */
private constant function BonusDamage takes integer level, real burnAmount, real missingMana returns real
return (1.00 + 0.00*level)*burnAmount + (0.20 + 0.00*level)*missingMana
endfunction
/*
When the target's current mana is equal to
or less than this amount, it will be stunned
for a certain duration */
private constant function ManaPoolLowAmount takes integer level, real maxMana returns real
return (0.40 + 0.00*level)*maxMana
endfunction
/*
The stun duration applied to the target when
it's current mana is equal to or less than
<ManaPoolLowAmount()> */
private constant function StunDuration takes integer level, real currentManaPercent returns real
return 0.01 + 0.00*level + 0.00*currentManaPercent
endfunction
/*
The additional damage dealt to the target
when it's current mana is less than
<ManaBurned()> */
private constant function CriticalDamage takes integer level, real maxMana returns real
return (1.00 + 0.00*level)*maxMana
endfunction
/*
The stun duration applied to the target when
it's current mana is less than
<ManaBurned()> */
private constant function CriticalDamageStunDuration takes integer level, real maxMana returns real
return 0.05 + 0.00*level + 0.00*maxMana
endfunction
/*================================================================*/
private function IncrementDamage takes real amount returns nothing
call UnitDamageTargetEx(PDDS.source, PDDS.target, amount, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
endfunction
private function OnDamage takes nothing returns nothing
local integer level
local real maxMana
local real currentMana
local real damageFactor
if PDDS.damageType != CODE then
set level = GetUnitAbilityLevel(PDDS.source, ABIL_ID)
if level > 0 then
set maxMana = GetUnitState(PDDS.target, UNIT_STATE_MAX_MANA)
if maxMana > 0.00 then
set currentMana = GetUnitState(PDDS.target, UNIT_STATE_MANA) - ManaBurned(level, PDDS.amount)
call SetUnitState(PDDS.target, UNIT_STATE_MANA, currentMana)
call DestroyEffect(AddSpecialEffectTarget(MANA_BURN_SFX_MODEL, PDDS.target, MANA_BURN_SFX_ATTACH_POINT))
if GetIllusionFlag(PDDS.source) then
set damageFactor = GetIllusionDamageFactor(PDDS.source)
else
set damageFactor = 1.00
endif
if currentMana <= ManaPoolLowAmount(level, maxMana) then
if currentMana <= 0.00 then
call DestroyEffect(AddSpecialEffectTarget(SOUL_BURN_SFX_MODEL, PDDS.target, SOUL_BURN_SFX_ATTACH_POINT))
call Stun.apply(PDDS.target, CriticalDamageStunDuration(level, maxMana), false)
call IncrementDamage((BonusDamage(level, ManaBurned(level, PDDS.amount), maxMana - currentMana) + CriticalDamage(level, maxMana))*damageFactor)
else
call Stun.apply(PDDS.target, StunDuration(level, currentMana/maxMana), false)
call IncrementDamage(BonusDamage(level, ManaBurned(level, PDDS.amount), maxMana - currentMana)*damageFactor)
endif
else
call IncrementDamage(BonusDamage(level, ManaBurned(level, PDDS.amount), maxMana - currentMana)*damageFactor)
endif
endif
endif
endif
endfunction
private function Init takes nothing returns nothing
call AddDamageHandler(function OnDamage)
endfunction
endlibrary
library Replicate /*
*/uses /*
*/IllusionEditor /* https://www.hiveworkshop.com/bundles/illusion-editor-v1-5-vjass-gui-support.286441
*/SpellEffectEvent /* https://www.hiveworkshop.com/threads/snippet-spelleffectevent.187193/
*/UnitDex /* https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
*/TimerUtils /* http://www.wc3c.net/showthread.php?p=1020244
*/
/*===============================================*/
/* Configuration Section */
/*===============================================*/
globals
/*
Rawcode of the activation spell */
private constant integer ABIL_ID = 'Repl'
/*
Model of the special effect attached to
illusions upon being summoned */
private constant string SUMMON_SFX_MODEL = "Abilities\\Spells\\Items\\AIil\\AIilTarget.mdl"
private constant string SUMMON_SFX_ATTACH_POINT = "origin"
/*
Model of the special effect played upon an
illusion's death */
private constant string DEATH_SFX_MODEL = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageCaster.mdl"
endglobals
/*
Layers of the summoned illusion army
The army lead is considered the first layer */
private constant function Layers takes integer level returns integer
return 1 + 1*level
endfunction
/*
Duration of the summoned illusion army
The timing starts upon the time the attack
order is given to the illusions */
private constant function Duration takes integer level returns real
return 5.00 + 1.00*level
endfunction
/*
The angle between the left-wing and the
right-wing of the army */
private constant function Angle takes integer level returns real
return 65.00 + 0.00*level
endfunction
/*
The distance interval between each illusion */
private constant function DistanceInterval takes integer level returns real
return 150.00 + 0.00*level
endfunction
/*
The interval between the time of summoning
between each layer */
private constant function SummonInterval takes integer level returns real
return 0.40 + 0.00*level
endfunction
/*
The damage dealt factor for the illusions */
private constant function DamageFactor takes integer level returns real
return 0.15 + 0.00*level
endfunction
/*
The damage receive factor for the illusions */
private constant function ReceiveFactor takes integer level returns real
return 1000.00 + 0.00*level
endfunction
/*
The delay before the illusion army are ordered to
attack after the creation process */
private constant function AttackOrderDelay takes integer level returns real
return 0.50 + 0.00*level
endfunction
/*===============================================*/
/* End of Configuration */
/*===============================================*/
/*====================================================================================*/
private struct Replicate
private unit caster
private unit target
private group group
private integer level
private integer layer
private integer layerPosition
private real originX
private real originY
private real facing
private boolean reselect
private static thistype array prev
private static thistype array next
private static player localPlayer
private method summon takes real x, real y, boolean showSfx returns nothing
local integer level = this.level
local unit caster = this.caster
local boolean hidden = IsUnitHidden(this.caster)
if hidden then
call ShowUnit(caster, true)
endif
call CreateIllusionCopy(GetOwningPlayer(caster), caster, Duration(level) + (Layers(level) - this.layer)*SummonInterval(level) + AttackOrderDelay(level), DamageFactor(level), ReceiveFactor(level), x, y, this.facing)
if hidden then
call ShowUnit(caster, false)
endif
call SetIllusionDeathEffect(GetLastCreatedIllusion(), DEATH_SFX_MODEL)
call SetUnitPathing(GetLastCreatedIllusion(), false)
call PauseUnit(GetLastCreatedIllusion(), true)
call GroupAddUnit(this.group, GetLastCreatedIllusion())
if showSfx then
call DestroyEffect(AddSpecialEffectTarget(SUMMON_SFX_MODEL, GetLastCreatedIllusion(), SUMMON_SFX_ATTACH_POINT))
endif
set caster = null
endmethod
private static method issueAttackOrder takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local unit u
loop
set u = FirstOfGroup(this.group)
exitwhen u == null
call GroupRemoveUnit(this.group, u)
call SetUnitPathing(u, true)
call PauseUnit(u, false)
call IssueTargetOrder(u, "attack", this.target)
endloop
call DestroyGroup(this.group)
set this.group = null
set this.caster = null
set this.target = null
set this.reselect = false
set prev[next[this]] = prev[this]
set next[prev[this]] = next[this]
call this.deallocate()
call ReleaseTimer(t)
set t = null
endmethod
private static method createIllusions takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local integer level = this.level
local real distance = DistanceInterval(level)*this.layer
local real angle1 = (this.facing - 180.00 + Angle(level)/2.00)*bj_DEGTORAD
local real angle2 = angle1 - Angle(level)*bj_DEGTORAD
set this.layer = this.layer + 1
if this.layerPosition == this.layer then
if GetRandomInt(0, 1) == 0 then
call SetUnitX(this.caster, this.originX + distance*Cos(angle1))
call SetUnitY(this.caster, this.originY + distance*Sin(angle1))
call this.summon(this.originX + distance*Cos(angle2), this.originY + distance*Sin(angle2), true)
else
call SetUnitX(this.caster, this.originX + distance*Cos(angle2))
call SetUnitY(this.caster, this.originY + distance*Sin(angle2))
call this.summon(this.originX + distance*Cos(angle1), this.originY + distance*Sin(angle1), true)
endif
call ShowUnit(this.caster, true)
call PauseUnit(this.caster, true)
call GroupAddUnit(this.group, this.caster)
call DestroyEffect(AddSpecialEffectTarget(SUMMON_SFX_MODEL, this.caster, SUMMON_SFX_ATTACH_POINT))
if this.reselect and localPlayer == GetOwningPlayer(this.caster) then
call ClearSelection()
call SelectUnit(this.caster, true)
endif
else
call this.summon(this.originX + distance*Cos(angle1), this.originY + distance*Sin(angle1), true)
call this.summon(this.originX + distance*Cos(angle2), this.originY + distance*Sin(angle2), true)
endif
if this.layer == Layers(level) then
call TimerStart(GetExpiredTimer(), AttackOrderDelay(this.level), false, function thistype.issueAttackOrder)
endif
endmethod
private static method onCast takes nothing returns nothing
local thistype this = allocate()
local unit caster = GetTriggerUnit()
local thistype last
set this.level = GetUnitAbilityLevel(caster, ABIL_ID)
if Layers(this.level) > 1 then
set last = prev[0]
set prev[0] = this
set next[last] = this
set prev[this] = last
set next[this] = 0
set this.group = CreateGroup()
set this.caster = caster
set this.target = GetSpellTargetUnit()
set this.originX = GetUnitX(caster)
set this.originY = GetUnitY(caster)
set this.facing = Atan2(GetSpellTargetY() - this.originY, GetSpellTargetX() - this.originX)*bj_RADTODEG
set this.layerPosition = GetRandomInt(1, Layers(this.level))
set this.layer = 1
call SetUnitFacing(caster, this.facing)
if this.layerPosition == 1 then
call PauseUnit(this.caster, true)
call GroupAddUnit(this.group, this.caster)
else
call this.summon(this.originX, this.originY, false)
call ShowUnit(caster, false)
set this.reselect = IsUnitSelected(this.caster, localPlayer)
if this.reselect and localPlayer == GetTriggerPlayer() then
call ClearSelection()
endif
endif
call TimerStart(NewTimerEx(this), SummonInterval(this.level), true, function thistype.createIllusions)
endif
set caster = null
endmethod
private static method onDeindex takes nothing returns nothing
local thistype node = next[0]
loop
exitwhen node == 0
call GroupRemoveUnit(node.group, GetIndexedUnit())
set node = next[node]
endloop
endmethod
private static method onInit takes nothing returns nothing
local code onDeindex = function thistype.onDeindex
call OnUnitDeindex(onDeindex)
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onCast)
set localPlayer = GetLocalPlayer()
endmethod
endstruct
endlibrary