Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library ShieldSystem requires optional TimerUtils
/*********************************************************************************************************************
* This is a shield system that can easily create a shield for a certain unit to absorb damage.It is
* lightweight and does not require any other resource (TimerUtils is only optional but recommanded for
* better efficiency). It can be used both for GUI and Jass users.
*
* Version: 1.0.1
*
* API:
* function ShieldSetUp takes unit u, real amount, real duration, string sfx, string attach returns nothing
* "u" stands for the unit to whom you want to give the shield.
* "amount" is the damage that the shield is able to absorb.
* "duration" is the duration of the shield.
* "sfx" is the string for the effect of the shield.
* "attach" is the attachment point of the special effect on the unit.
*
* The next API has nothing to do with the creation of a protective shield, I just made it for the convenience of
* writing this system, some people may find it useful. It is used for adding and removing a certain ability in a
* period of time, especially useful for adding buff to a certain unit for a period of time.
*
* function AbilityTimedSetUp takes unit u, integer ab, real duration returns nothing
*
* Implementation:
* 1. Copy the "ShieldSystem" trigger to your map;
* 2. Copy the "Life Saver Ability" to your map;
* 3. Make sure to match the ability ID of the "Life Saver Ability" with the constant integer LIFE_SAVER_AB;
* 4. You can copy the "TimerUtils" trigger to your map if you want better efficiency.
* Done!
*
*///*****************************************************************************************************************
globals
private hashtable ht = InitHashtable()
private constant integer LIFE_SAVER_AB = 'A000' //The ability to prevent unit from dying when the damage is more than its HP.
endglobals
//AbilityTimed is used for adding and removing a certain ability in a period of time.
struct AbilityTimed
private integer abil
private unit u
private static method Remove takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this
static if LIBRARY_TimerUtils then
set this = GetTimerData(t)
call UnitRemoveAbility(this.u, this.abil)
call ReleaseTimer(t)
else
set this = LoadInteger(ht, GetHandleId(t), 0)
call FlushChildHashtable(ht, GetHandleId(t))
call UnitRemoveAbility(this.u, this.abil)
call PauseTimer(t)
call DestroyTimer(t)
endif
set this.u = null
call this.destroy()
set t = null
endmethod
static method Create takes unit u, integer ab, real duration returns thistype
local thistype this = thistype.allocate()
local timer t
static if LIBRARY_TimerUtils then
set t = NewTimer()
call SetTimerData(t, this)
else
set t = CreateTimer()
call SaveInteger(ht, GetHandleId(t), 0, this)
endif
call UnitAddAbility(u, ab)
set this.u = u
set this.abil = ab
call TimerStart(t, duration, false, function thistype.Remove)
set t = null
return this
endmethod
endstruct
function AbilityTimedSetUp takes unit u, integer ab, real duration returns nothing
call AbilityTimed.Create(u, ab, duration)
endfunction
//The core of ShieldSystem
struct ShieldSystem
private real shieldAmount
private unit shieldUnit
private trigger shieldTrig
private real shieldDuration
private string shieldSfx
private effect shieldEff
private timer shieldTimer
private real newLife
private boolean b = false
//Removing the shield when the duration ends
private static method Remove takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this
static if LIBRARY_TimerUtils then
set this = GetTimerData(t)
call ReleaseTimer(t)
else
set this = LoadInteger(ht, GetHandleId(t), 0)
call FlushChildHashtable(ht, GetHandleId(t))
call PauseTimer(t)
call DestroyTimer(t)
endif
if this.shieldTrig != null then
call FlushChildHashtable(ht, GetHandleId(this.shieldTrig))
call DestroyTrigger(this.shieldTrig)
endif
set this.shieldTrig = null
set this.shieldUnit = null
if this.shieldEff != null then
call DestroyEffect(this.shieldEff)
endif
set this.shieldEff = null
set this.shieldTimer = null
call this.destroy()
set t = null
endmethod
private static method AdjustHP takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this
static if LIBRARY_TimerUtils then
set this = GetTimerData(t)
call ReleaseTimer(t)
else
set this = LoadInteger(ht, GetHandleId(t), 0)
call FlushChildHashtable(ht, GetHandleId(t))
call PauseTimer(t)
call DestroyTimer(t)
endif
call SetWidgetLife(this.shieldUnit, this.newLife)
if this.b then
set this.shieldUnit = null
call this.destroy()
endif
set t = null
endmethod
private static method DamageHandler takes nothing returns nothing
local trigger trig = GetTriggeringTrigger()
local thistype this = LoadInteger(ht, GetHandleId(trig), 0)
local real dmg = GetEventDamage()
local real currentLife = GetWidgetLife(this.shieldUnit)
local real life
local timer t
if dmg > currentLife then
/*If the damage is able to kill the target, then
adding Life Saver Ability to prevent the target from dying*/
call AbilityTimedSetUp(this.shieldUnit, LIFE_SAVER_AB, 0.00)
//Abosrbing the incoming damage.
set this.shieldAmount = this.shieldAmount - dmg
//If the incoming damage is more than the shield can handle, then...
if this.shieldAmount <= 0 then
//If the reduced damage is still more than the target's current life, then kill the target.
if RAbsBJ(this.shieldAmount) >= currentLife then
//Stop the duration timer
static if LIBRARY_TimerUtils then
call ReleaseTimer(this.shieldTimer)
else
call PauseTimer(this.shieldTimer)
call DestroyTimer(this.shieldTimer)
endif
call KillUnit(this.shieldUnit)
//Release memory
call DestroyEffect(this.shieldEff)
call FlushChildHashtable(ht, GetHandleId(trig))
call DestroyTrigger(trig)
set this.shieldUnit = null
set this.shieldTrig = null
set this.shieldEff = null
set this.shieldTimer = null
call this.destroy()
else
/*If the reduced damage is less than the target's current life,
then set the target's life to the correct amount by using a 0 second timer. */
//Stop the duration timer
static if LIBRARY_TimerUtils then
call ReleaseTimer(this.shieldTimer)
else
call PauseTimer(this.shieldTimer)
call DestroyTimer(this.shieldTimer)
endif
set life = currentLife - RAbsBJ(this.shieldAmount)
static if LIBRARY_TimerUtils then
set t = NewTimerEx(this)
set this.newLife = life
else
set t = CreateTimer()
call SaveInteger(ht, GetHandleId(t), 0, this)
set this.newLife = life
endif
//Set this to true in order to to null "this.shieldUnit" in the timer callback
set this.b = true
call TimerStart(t, 0.00, false, function thistype.AdjustHP)
//Release memory
call DestroyEffect(this.shieldEff)
call FlushChildHashtable(ht, GetHandleId(trig))
call DestroyTrigger(trig)
set this.shieldTrig = null
set this.shieldEff = null
set this.shieldTimer = null
set t = null
endif
else
//If the shield is strong enough to withstand the damage.
set life = GetWidgetLife(this.shieldUnit) + dmg
call SetWidgetLife(this.shieldUnit, life)
endif
else
//If the damage is less than the target's current life.
set this.shieldAmount = this.shieldAmount - dmg
//If there is still HP left of the shield, then...
if this.shieldAmount > 0 then
set life = currentLife + dmg
static if LIBRARY_TimerUtils then
set t = NewTimerEx(this)
set this.newLife = life
else
set t = CreateTimer()
call SaveInteger(ht, GetHandleId(t), 0, this)
set this.newLife = life
endif
call TimerStart(t, 0.00, false, function thistype.AdjustHP)
else
//Stop the duration timer
static if LIBRARY_TimerUtils then
call ReleaseTimer(this.shieldTimer)
else
call PauseTimer(this.shieldTimer)
call DestroyTimer(this.shieldTimer)
endif
set life = currentLife - RAbsBJ(this.shieldAmount)
static if LIBRARY_TimerUtils then
set t = NewTimerEx(this)
set this.newLife = life
else
set t = CreateTimer()
call SaveInteger(ht, GetHandleId(t), 0, this)
set this.newLife = life
endif
//Set this to true in order to to null "this.shieldUnit" in the timer callback
set this.b = true
call TimerStart(t, 0.00, false, function thistype.AdjustHP)
//Release memory
call DestroyEffect(this.shieldEff)
call FlushChildHashtable(ht, GetHandleId(trig))
call DestroyTrigger(trig)
set this.shieldTrig = null
set this.shieldEff = null
set this.shieldTimer = null
endif
endif
set trig = null
endmethod
static method Create takes unit u, real amout, real duration, string sfx, string attach returns thistype
local thistype this = thistype.allocate()
local timer t
local trigger trig = CreateTrigger()
call SaveInteger(ht, GetHandleId(trig), 0 , this)
static if LIBRARY_TimerUtils then
set this.shieldTimer = NewTimerEx(this)
call SetTimerData(this.shieldTimer, this)
else
set this.shieldTimer = CreateTimer()
call SaveInteger(ht, GetHandleId(this.shieldTimer), 0, this)
endif
//Registering damage event
call TriggerRegisterUnitEvent(trig, u, EVENT_UNIT_DAMAGED)
call TriggerAddCondition(trig, Condition(function thistype.DamageHandler))
//Storing data
set this.shieldUnit = u
set this.shieldAmount = amout
set this.shieldTrig = trig
set this.shieldDuration = duration
set this.shieldSfx = sfx
set this.shieldEff = AddSpecialEffectTarget(this.shieldSfx, this.shieldUnit, attach)
call TimerStart(this.shieldTimer, duration, false, function thistype.Remove)
set t = null
set trig = null
return this
endmethod
endstruct
function ShieldSetUp takes unit u, real amount, real duration, string sfx, string attach returns nothing
call ShieldSystem.Create(u, amount, duration, sfx, attach)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Revive_Actions takes nothing returns nothing
local unit tri = GetTriggerUnit()
if tri == gg_unit_Hamg_0002 then
call ReviveHero( gg_unit_Hamg_0002, GetRectCenterX(gg_rct_1), GetRectCenterY(gg_rct_1), true )
call ShieldSetUp(gg_unit_Hamg_0002, 5000, 120, "Abilities\\Spells\\Undead\\AntiMagicShell\\AntiMagicShell.mdl", "overhead")
set ShieldAmount1 = 5000
elseif tri == gg_unit_Hpal_0001 then
call ReviveHero( gg_unit_Hpal_0001, GetRectCenterX(gg_rct_2), GetRectCenterY(gg_rct_2), true )
call ShieldSetUp(gg_unit_Hpal_0001, 200, 120, "Abilities\\Spells\\Undead\\Cripple\\CrippleTarget.mdl", "origin")
set ShieldAmount2 = 200
endif
set tri = null
endfunction
//===========================================================================
function InitTrig_Revive takes nothing returns nothing
set gg_trg_Revive = CreateTrigger( )
call TriggerRegisterUnitEvent( gg_trg_Revive, gg_unit_Hamg_0002, EVENT_UNIT_DEATH )
call TriggerRegisterUnitEvent( gg_trg_Revive, gg_unit_Hpal_0001, EVENT_UNIT_DEATH )
call TriggerAddAction( gg_trg_Revive, function Trig_Revive_Actions )
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Test
globals
real ShieldAmount1 = 5000
real ShieldAmount2 = 200
endglobals
function Run takes nothing returns boolean
local unit target = GetTriggerUnit()
local real dmg = GetEventDamage()
if target == gg_unit_Hamg_0002 then
set ShieldAmount1 = ShieldAmount1 - dmg
call DisplayTimedTextToPlayer(Player(0), 0, 0, 10, "Damage : " + R2S(dmg) + " to " + GetUnitName(target))
if ShieldAmount1 > 0 then
call DisplayTimedTextToPlayer(Player(0), 0, 0, 10, "The shield on " + GetUnitName(target) + " has " + R2S(ShieldAmount1) + " remaning HP")
else
call DisplayTimedTextToPlayer(Player(0), 0, 0, 10, "The shield on " + GetUnitName(target) +" is gone")
endif
elseif target == gg_unit_Hpal_0001 then
set ShieldAmount2 = ShieldAmount2 - dmg
call DisplayTimedTextToPlayer(Player(0), 0, 0, 10, "Damage : " + R2S(dmg) + " to " + GetUnitName(target))
if ShieldAmount2 > 0 then
call DisplayTimedTextToPlayer(Player(0), 0, 0, 10, "The shield on " + GetUnitName(target) + " has " + R2S(ShieldAmount2) + " remaning HP")
else
call DisplayTimedTextToPlayer(Player(0), 0, 0, 10, "The shield on " + GetUnitName(target) +" is gone")
endif
endif
set target = null
return false
endfunction
//===========================================================================
function InitTrig_TestMessage takes nothing returns nothing
set gg_trg_TestMessage = CreateTrigger( )
call TriggerRegisterUnitEvent( gg_trg_TestMessage, gg_unit_Hamg_0002, EVENT_UNIT_DAMAGED )
call TriggerRegisterUnitEvent( gg_trg_TestMessage, gg_unit_Hpal_0001, EVENT_UNIT_DAMAGED )
call TriggerAddCondition( gg_trg_TestMessage, Condition(function Run ))
endfunction
endlibrary