library_once TimerUtils
//*********************************************************************
//* TimerUtils (Blue flavor)
//* ----------
//*
//* 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.wc3campaigns.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//================================================================
globals
private constant integer MAX_HANDLE_ID_COUNT = 408000
// values lower than 8191: very fast, but very unsafe.
// values bigger than 8191: not that fast, the bigger the number is the slower the function gets
// Most maps don't really need a value bigger than 50000 here, but if you are unsure, leave it
// as the rather inflated value of 408000
endglobals
//=================================================================================================
private function H2I takes handle h returns integer
return h
return 0
endfunction
//==================================================================================================
globals
private integer array data[MAX_HANDLE_ID_COUNT]
private constant integer MIN_HANDLE_ID=0x100000
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
debug if(H2I(t)-MIN_HANDLE_ID>=MAX_HANDLE_ID_COUNT) then
debug call BJDebugMsg("SetTimerData: Handle id too big, increase the max handle id count or use gamecache instead")
debug endif
set data[H2I(t)-MIN_HANDLE_ID]=value
endfunction
function GetTimerData takes timer t returns integer
debug if(H2I(t)-MIN_HANDLE_ID>=MAX_HANDLE_ID_COUNT) then
debug call BJDebugMsg("GetTimerData: Handle id too big, increase the max handle id count or use gamecache instead")
debug endif
return data[H2I(t)-MIN_HANDLE_ID]
endfunction
//==========================================================================================
globals
private timer array tT
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
set tT[0]=CreateTimer()
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
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==8191) 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
endlibrary
library RemovalDetection initializer Init needs TimerUtils
// Notes: CANNOT have two defend based abilities on same unit, or fires twice.
// ASSUMES that summoned units cannot be revived in any way. This ought to be true.
// If not, replace IsAnimated with 'return false' and don't use animate dead, or something.
// Devour ain't a problem, but they'll die and then decay, you just can't see them decaying.
//Config
// This line creates a new ability based off of defend - make sure it doesn't conflict.
//! external ObjectMerger w3a Adef rdd& anam "Removal Detection Defend"
globals
private constant integer SPELL_ID = 039;rdd&039; // must be the same as the parameter after "w3a Adef" above
private constant integer ORDER_ID = 852056 // undefend
private constant integer MAX_ARRAY_SIZE = 401000 //for the unit flag arrays
endglobals
private function AddAbility takes unit u returns nothing // have ALL defend based spells here
if (GetUnitAbilityLevel(u, SPELL_ID) + GetUnitAbilityLevel(u, 039;Adef039;)) == 0 then
call UnitAddAbility(u, SPELL_ID)
endif
endfunction
private function IsAnimated takes unit u returns boolean
debug if IsUnitType(u, UNIT_TYPE_SUMMONED) then
// check needed due to animate dead sucking
debug call BJDebugMsg("Animated")
debug endif
return IsUnitType(u, UNIT_TYPE_SUMMONED)
endfunction
// DO NOT EDIT BELOW HERE
globals
//Unit flags
private boolean array dead[MAX_ARRAY_SIZE]
private boolean array animated[MAX_ARRAY_SIZE]
private boolean array reincarnated[MAX_ARRAY_SIZE]
//Event handling
private trigger array reincS
private trigger array reincF
private trigger array rez
private trigger array oos
private integer reincSMax = 0
private integer reincFMax = 0
private integer rezMax = 0
private integer oosMax = 0
private integer cnt = 0
//Event responses
private unit trg = null
private boolean amd = false
endglobals
//"Event Responses" and event registration
function TriggerRegisterReincarnationEvent takes trigger t, boolean finish returns nothing
if finish then
set reincF[reincFMax] = t
set reincFMax = reincFMax + 1
else
set reincS[reincSMax] = t
set reincSMax = reincSMax + 1
endif
endfunction
function TriggerRegisterResurrectionEvent takes trigger t returns nothing
set rez[rezMax] = t
set rezMax = rezMax + 1
endfunction
function TriggerRegisterOutOfScopeEvent takes trigger t returns nothing
set oos[oosMax] = t
set oosMax = oosMax + 1
endfunction
function GetReincarnatingUnit takes nothing returns unit
return trg
endfunction
function GetResurrectedUnit takes nothing returns unit
return trg
endfunction
function GetScopingUnit takes nothing returns unit
return trg
endfunction
function GetWasAnimated takes nothing returns boolean
return amd
endfunction
//System
private function H2I takes handle h returns integer
return h
return 0
endfunction
private function GetIndex takes handle h returns integer
return H2I(h) - 0x100000
endfunction
private struct Data
integer i
unit u
static method create takes unit u, integer i returns Data
local Data dat = Data.allocate()
set dat.u = u
set dat.i = i
return dat
endmethod
endstruct
private function Child takes nothing returns nothing
local Data dat = GetTimerData(GetExpiredTimer())
if reincarnated[dat.i] then
set trg = dat.u
if GetUnitTypeId(trg) != 0 then
set cnt = 0
loop
exitwhen cnt >= reincSMax
if TriggerEvaluate(reincS[cnt]) then
call TriggerExecute(reincS[cnt])
endif
set cnt = cnt + 1
endloop
debug call BJDebugMsg("Reincarnating")
else
set reincarnated[dat.i] = false
debug call BJDebugMsg("Removed")
endif
endif
set dat.u = null
call dat.destroy()
call ReleaseTimer(GetExpiredTimer())
endfunction
private function Ordered takes nothing returns boolean
local integer index = GetIndex(GetTriggerUnit())
local timer t
set trg = GetTriggerUnit()
if GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then
if GetIssuedOrderId() == ORDER_ID then
set amd = false
if IsUnitType(trg,UNIT_TYPE_DEAD) then
if dead[index] or animated[index] then
if dead[index] then
set dead[index] = false //it has just gone out of scope
debug call BJDebugMsg("Out of scope")
else
set animated[index] = false
debug call BJDebugMsg("Out of scope from animated")
set amd = true
endif
set cnt = 0
loop
exitwhen cnt >= oosMax
if TriggerEvaluate(oos[cnt]) then
call TriggerExecute(oos[cnt])
endif
set cnt = cnt + 1
endloop
else
set dead[index] = true
set reincarnated[index] = true //Set to false if the unit
set t = NewTimer() //is registered to have died
call SetTimerData(t,Data.create(trg,index))
call TimerStart(t,0,false,function Child)
set t = null
endif
elseif dead[index] then
if IsAnimated(trg) then
set animated[index] = true
set amd = true
endif
if reincarnated[index] then
set reincarnated[index] = false
set cnt = 0
loop
exitwhen cnt >= reincFMax
if TriggerEvaluate(reincF[cnt]) then
call TriggerExecute(reincF[cnt])
endif
set cnt = cnt + 1
endloop
debug call BJDebugMsg("Reincarnated")
elseif not IsUnitType(trg,UNIT_TYPE_HERO) then
set cnt = 0 //hero revive already has an event
loop
exitwhen cnt >= rezMax
if TriggerEvaluate(rez[cnt]) then
call TriggerExecute(rez[cnt])
endif
set cnt = cnt + 1
endloop
debug call BJDebugMsg("Resurrected")
endif
set dead[index] = false //it has just been resurrected/reincarnated
endif //the else is just a normal call
endif
else
set reincarnated[index] = false //The order fires before the death event
debug call BJDebugMsg("Died") //and thus lame workarounds are required
endif
return false
endfunction
private function Ex takes nothing returns boolean
call AddAbility(GetFilterUnit())
return false
endfunction
private function Enters takes nothing returns boolean
call AddAbility(GetTriggerUnit())
return false
endfunction
private function Init takes nothing returns nothing
local group g = CreateGroup()
local rect r = GetWorldBounds()
local region world = CreateRegion()
local trigger t = CreateTrigger()
local integer i = 0
call RegionAddRect(world, r)
call GroupEnumUnitsInRect(g, r, Condition(function Ex))
call DestroyGroup(g)
set g = null
call RemoveRect(r)
set r = null
call TriggerAddCondition(t, Condition(function Enters))
call TriggerRegisterEnterRegion(t, world, null)
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t, Condition(function Ordered))
loop
exitwhen i >= 16
call SetPlayerAbilityAvailable(Player(i), SPELL_ID, false)
set i = i + 1
endloop
endfunction
endlibrary
scope StompOnRez initializer Init
globals
private integer UNIT_TYPE = 039;Otch039; //Rawcode of reincarnating unit-type
private integer DUMMY_ID = 039;n000039; //Rawcode of dummy unit
private integer ABILITY_ID = 039;A001039; //Rawcode of stomp ability
private string STOMP_ORDER_STRING = "stomp" //Order string of base ability
private boolean USE_DUMMY = true //Have dummy cast or hero cast
//Recommended true
endglobals
private function Conditions takes nothing returns boolean
return GetUnitTypeId(GetTriggerUnit()) == UNIT_TYPE
endfunction
private function AltActions takes nothing returns nothing
local unit rez = GetReincarnatingUnit()
call TriggerSleepAction(0.0) //Required
call IssueImmediateOrder(rez, STOMP_ORDER_STRING)
set rez = null
endfunction
private function Actions takes nothing returns nothing
local unit rez = GetReincarnatingUnit()
local unit u = CreateUnit(GetOwningPlayer(rez), DUMMY_ID, GetUnitX(rez), GetUnitY(rez), 0)
call UnitAddAbility(u, ABILITY_ID)
call IssueImmediateOrder(u, STOMP_ORDER_STRING)
call UnitApplyTimedLife(u, 039;BTLF039;, 3)
call DestroyEffect(AddSpecialEffectTarget("war3mapImported\\FrostStomp.mdx", rez, "origin"))
set rez = null
set u = null
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterReincarnationEvent(t, true)
call TriggerAddCondition(t, Condition(function Conditions))
if USE_DUMMY then
call TriggerAddAction(t, function Actions)
else
call TriggerAddAction(t, function AltActions)
endif
endfunction
endscope
call TriggerRegisterReincarnationEvent(t, true)