library TimeLapse /*
Time Lapse v1.20
by Flux
Warps backward to whatever position caster was in 4/5/6 seconds earlier--regaining the HP
and mana from that time.
*/ requires /*
(nothing)
*/ optional Table/*
If not found, the spell will create a hashtable. Hashtables are limited to 255 per map.
*/ optional RegisterPlayerUnitEvent /*
If not found, an extra trigger is created.
*/ optional SpellEffectEvent /*
If not found, an extra trigger is created and GetSpellAbilityId() check will occur.
*/ optional TimerUtils /*
If found, will recycle timers used preventing continuous creation & destruction of timers.
NOTES:
- You cannot kill yourself from using Time Lapse.
- Cannot go back in times that the spell is not learned yet.
CREDITS:
Bribe - Table, SpellEffectEvent
Magtheridon96 - RegisterPlayerUnitEvent
Vexorian - TimerUtils
Kino - TimeLapse Effect (edited)
*/
globals
//Rawcode of the Spell
private constant integer SPELL_ID = 'ATiL'
//Maximum Timing possible at any level
//Determines up to how long in the past data is saved before it is discarded.
private constant real MAX_TIMING = 6.0
//Periodic timeout for storing data
//Lesser value = Greater accuracy at the cost of performance
private constant real TIMEOUT = 0.1
//Visual Effects upon casting the spell
private constant string SFX_TARGET = "Models\\Effects\\TimeLapse.mdx"
private constant string SFX_SOURCE = "Models\\Effects\\TimeLapse.mdx"
private constant real SFX_DURATION = 1.0
//Is Time Lapse going to be a Hero Ability in your map?
private constant boolean HERO_ABILITY = true
//Is Time Lapse going to be a Unit Ability in your map? (Both can be true)
private constant boolean UNIT_ABILITY = true
//Refresh Rate for Units with abilities
//Only essential if UNIT_ABILILTY = true
private constant real UNIT_REFRESH = 60.0
endglobals
//How much time caster goes back in time.
private function Timing takes integer level returns real
return 3.0 + 1.0*level
endfunction
//Units that will have its data stored periodically
static if UNIT_ABILITY then
private function UnitFilter takes unit u returns boolean
return GetUnitAbilityLevel(u, SPELL_ID) > 0
endfunction
endif
private struct Sfx
private effect source
private effect target
private method destroy takes nothing returns nothing
call DestroyEffect(this.source)
call DestroyEffect(this.target)
set this.source = null
set this.target = null
call this.deallocate()
endmethod
private static method expires takes nothing returns nothing
local timer t = GetExpiredTimer()
static if LIBRARY_TimerUtils then
call thistype(GetTimerData(t)).destroy()
call ReleaseTimer(t)
else
local integer id = GetHandleId(t)
static if LIBRARY_Table then
call thistype(TimeLapse.tb[id]).destroy()
call TimeLapse.tb.remove(id)
else
call thistype(LoadInteger(TimeLapse.hash, id, 0)).destroy()
call RemoveSavedInteger(TimeLapse.hash, id, 0)
endif
call DestroyTimer(t)
endif
set t = null
endmethod
static method create takes real x1, real y1, real x2, real y2 returns thistype
local thistype this = thistype.allocate()
static if LIBRARY_TimerUtils then
call TimerStart(NewTimerEx(this), SFX_DURATION, false, function thistype.expires)
else
local timer t = CreateTimer()
static if LIBRARY_Table then
set TimeLapse.tb[GetHandleId(t)] = this
else
call SaveInteger(TimeLapse.hash, GetHandleId(t), 0, this)
endif
call TimerStart(t, SFX_DURATION, false, function thistype.expires)
set t = null
endif
set this.source = AddSpecialEffect(SFX_SOURCE, x1, y1)
set this.target = AddSpecialEffect(SFX_TARGET, x2, y2)
return this
endmethod
endstruct
struct TimeLapse
private unit u
private integer oldest
static if LIBRARY_Table then
private Table hp
private Table mana
private Table xPos
private Table yPos
readonly static Table tb
else
readonly static hashtable hash = InitHashtable()
endif
private thistype next
private thistype prev
private static integer ctr
private static timer t
private static constant integer INDEX_REMOVE_OFFSET = R2I(MAX_TIMING/TIMEOUT)
method destroy takes nothing returns nothing
static if LIBRARY_Table then
call thistype.tb.remove(GetHandleId(this.u))
call this.hp.destroy()
call this.mana.destroy()
call this.xPos.destroy()
call this.yPos.destroy()
else
call RemoveSavedInteger(thistype.hash, GetHandleId(this.u), 0)
call FlushChildHashtable(thistype.hash, this)
call FlushChildHashtable(thistype.hash, this + JASS_MAX_ARRAY_SIZE)
call FlushChildHashtable(thistype.hash, this + 2*JASS_MAX_ARRAY_SIZE)
call FlushChildHashtable(thistype.hash, this + 3*JASS_MAX_ARRAY_SIZE)
endif
set this.prev.next = this.next
set this.next.prev = this.prev
if thistype(0).next == 0 then
call PauseTimer(thistype.t)
endif
set this.u = null
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = thistype(0).next
local integer oldIndex = thistype.ctr - thistype.INDEX_REMOVE_OFFSET
loop
exitwhen this == 0
static if LIBRARY_Table then
set this.hp.real[thistype.ctr] = GetWidgetLife(this.u)
set this.mana.real[thistype.ctr] = GetUnitState(this.u, UNIT_STATE_MANA)
set this.xPos.real[thistype.ctr] = GetUnitX(this.u)
set this.yPos.real[thistype.ctr] = GetUnitY(this.u)
if this.hp.real.has(oldIndex) then
call this.hp.real.remove(oldIndex)
call this.mana.real.remove(oldIndex)
call this.xPos.real.remove(oldIndex)
call this.yPos.real.remove(oldIndex)
set this.oldest = oldIndex + 1
endif
else
call SaveReal(thistype.hash, this, thistype.ctr, GetWidgetLife(this.u))
call SaveReal(thistype.hash, this + JASS_MAX_ARRAY_SIZE, thistype.ctr, GetUnitState(this.u, UNIT_STATE_MANA))
call SaveReal(thistype.hash, this + 2*JASS_MAX_ARRAY_SIZE, thistype.ctr, GetUnitX(this.u))
call SaveReal(thistype.hash, this + 3*JASS_MAX_ARRAY_SIZE, thistype.ctr, GetUnitY(this.u))
if HaveSavedReal(thistype.hash, this, oldIndex) then
call RemoveSavedReal(thistype.hash, this, oldIndex)
call RemoveSavedReal(thistype.hash, this + JASS_MAX_ARRAY_SIZE, oldIndex)
call RemoveSavedReal(thistype.hash, this + 2*JASS_MAX_ARRAY_SIZE, oldIndex)
call RemoveSavedReal(thistype.hash, this + 3*JASS_MAX_ARRAY_SIZE, oldIndex)
endif
endif
set this = this.next
endloop
set thistype.ctr = thistype.ctr + 1
endmethod
static method create takes unit u returns thistype
local thistype this
local integer id = GetHandleId(u)
static if LIBRARY_Table then
if thistype.tb.has(id) then
set this = thistype.tb[id]
else
set this = thistype.allocate()
set this.oldest = thistype.ctr
set this.u = u
set this.hp = Table.create()
set this.mana = Table.create()
set this.xPos = Table.create()
set this.yPos = Table.create()
set this.next = thistype(0)
set this.prev = thistype(0).prev
set this.next.prev = this
set this.prev.next = this
if this.prev == 0 then
call TimerStart(thistype.t, TIMEOUT, true, function thistype.onPeriod)
endif
set thistype.tb[id] = this
endif
else
if HaveSavedInteger(thistype.hash, id, 0) then
set this = LoadInteger(thistype.hash, id, 0)
else
set this = thistype.allocate()
set this.oldest = thistype.ctr
set this.u = u
set this.next = thistype(0)
set this.prev = thistype(0).prev
set this.next.prev = this
set this.prev.next = this
if this.prev == 0 then
call TimerStart(thistype.t, TIMEOUT, true, function thistype.onPeriod)
endif
call SaveInteger(thistype.hash, id, 0, this)
endif
endif
return this
endmethod
method doEffect takes nothing returns nothing
local integer index = thistype.ctr - R2I(Timing(GetUnitAbilityLevel(this.u, SPELL_ID))/TIMEOUT)
local real hp
local real mana
local real x
local real y
if index > this.oldest then
static if LIBRARY_Table then
set hp = this.hp.real[index]
set mana = this.mana.real[index]
set x = this.xPos.real[index]
set y = this.yPos.real[index]
else
set hp = LoadReal(thistype.hash, this, index)
set mana = LoadReal(thistype.hash, this + JASS_MAX_ARRAY_SIZE, index)
set x = LoadReal(thistype.hash, this + 2*JASS_MAX_ARRAY_SIZE, index)
set y = LoadReal(thistype.hash, this + 3*JASS_MAX_ARRAY_SIZE, index)
endif
else
static if LIBRARY_Table then
set hp = this.hp.real[this.oldest]
set mana = this.mana.real[this.oldest]
set x = this.xPos.real[this.oldest]
set y = this.yPos.real[this.oldest]
else
set hp = LoadReal(thistype.hash, this, this.oldest)
set mana = LoadReal(thistype.hash, this + JASS_MAX_ARRAY_SIZE, this.oldest)
set x = LoadReal(thistype.hash, this + 2*JASS_MAX_ARRAY_SIZE, this.oldest)
set y = LoadReal(thistype.hash, this + 3*JASS_MAX_ARRAY_SIZE, this.oldest)
endif
endif
if hp < 0.405 then
set hp = 1.0
endif
call Sfx.create(GetUnitX(this.u), GetUnitY(this.u), x, y)
call SetWidgetLife(this.u, hp)
call SetUnitState(this.u, UNIT_STATE_MANA, mana)
call SetUnitX(this.u, x)
call SetUnitY(this.u, y)
endmethod
private static method delay takes nothing returns nothing
local timer t = GetExpiredTimer()
static if LIBRARY_TimerUtils then
call thistype(GetTimerData(t)).doEffect()
call ReleaseTimer(t)
else
local integer id = GetHandleId(t)
static if LIBRARY_Table then
call thistype(thistype.tb[id]).doEffect()
call thistype.tb.remove(id)
else
call thistype(LoadInteger(thistype.hash, id, 0)).doEffect()
call RemoveSavedInteger(thistype.hash, id, 0)
endif
call DestroyTimer(t)
endif
set t = null
endmethod
private static method onCast takes nothing returns boolean
static if LIBRARY_Table then
local thistype this = thistype.tb[GetHandleId(GetTriggerUnit())]
else
local thistype this = LoadInteger(thistype.hash, GetHandleId(GetTriggerUnit()), 0)
endif
static if LIBRARY_TimerUtils then
call TimerStart(NewTimerEx(this), 0.0, false, function thistype.delay)
else
local timer t = CreateTimer()
static if LIBRARY_Table then
set thistype.tb[GetHandleId(t)] = this
else
call SaveInteger(thistype.hash, GetHandleId(t), 0, this)
endif
call TimerStart(t, 0.0, false, function thistype.delay)
set t = null
endif
return false
endmethod
static if UNIT_ABILITY then
private thistype unext
private thistype uprev
private static timer unitTimer = CreateTimer()
private static method unitRefresh takes nothing returns nothing
local thistype this = thistype(0).unext
loop
exitwhen this == 0
if GetUnitTypeId(this.u) == 0 then
set this.uprev.unext = this.unext
set this.unext.uprev = this.uprev
if thistype(0).unext == 0 then
call PauseTimer(thistype.unitTimer)
endif
call this.destroy()
endif
set this = this.unext
endloop
endmethod
private method insert takes nothing returns nothing
set this.unext = thistype(0)
set this.uprev = thistype(0).uprev
set this.unext.uprev = this
set this.uprev.unext = this
if this.uprev == 0 then
call TimerStart(thistype.unitTimer, UNIT_REFRESH, true, function thistype.unitRefresh)
endif
endmethod
private static method unitEnters takes nothing returns boolean
local unit u = GetTriggerUnit()
if UnitFilter(u) then
call thistype.create(u).insert()
endif
set u = null
return false
endmethod
private static method preplaced takes nothing returns nothing
local group g = CreateGroup()
local unit u
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if UnitFilter(u) then
call thistype.create(u).insert()
endif
endloop
call DestroyGroup(g)
call DestroyTimer(GetExpiredTimer())
set g = null
endmethod
endif
static if HERO_ABILITY then
private static method learn takes nothing returns boolean
if GetLearnedSkill() == SPELL_ID then
call thistype.create(GetTriggerUnit())
endif
return false
endmethod
endif
static if not LIBRARY_SpellEffectEvent then
private static method cond takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID and thistype.onCast()
endmethod
endif
private static method onInit takes nothing returns nothing
static if LIBRARY_RegisterPlayerUnitEvent then
static if UNIT_ABILITY then
local trigger enter = CreateTrigger()
local region rectRegion = CreateRegion()
call RegionAddRect(rectRegion, bj_mapInitialPlayableArea)
call TriggerRegisterEnterRegion(enter, rectRegion, null)
call TriggerAddCondition(enter, Condition(function thistype.unitEnters))
call TimerStart(CreateTimer(), 0.0, false, function thistype.preplaced)
endif
static if HERO_ABILITY then
call RegisterPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function thistype.learn)
endif
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
else
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.cond)
endif
else
local integer i = 0
local trigger t1 = CreateTrigger()
static if HERO_ABILITY then
local trigger t2 = CreateTrigger()
endif
static if UNIT_ABILITY then
local trigger enter = CreateTrigger()
local region rectRegion = CreateRegion()
call RegionAddRect(rectRegion, bj_mapInitialPlayableArea)
call TriggerRegisterEnterRegion(enter, rectRegion, null)
call TriggerAddCondition(enter, Condition(function thistype.unitEnters))
call TimerStart(CreateTimer(), 0.0, false, function thistype.preplaced)
endif
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t1, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
static if HERO_ABILITY then
call TriggerRegisterPlayerUnitEvent(t2, Player(i), EVENT_PLAYER_HERO_SKILL, null)
endif
set i = i + 1
endloop
call TriggerAddCondition(t1, Condition(function thistype.cond))
static if HERO_ABILITY then
call TriggerAddCondition(t2, Condition(function thistype.learn))
endif
endif
static if LIBRARY_Table then
set thistype.tb = Table.create()
endif
set thistype.t = CreateTimer()
set thistype.ctr = 0
endmethod
endstruct
endlibrary