1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. The Melee Mapping Contest #4: 2v2 - Results are out! Step by to congratulate the winners!
    Dismiss Notice
  3. We're hosting the 15th Mini-Mapping Contest with YouTuber Abelhawk! The contestants are to create a custom map that uses the hidden content within Warcraft 3 or is inspired by any of the many secrets within the game.
    Dismiss Notice
  4. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice

Time Lapse v1.20

Submitted by Flux
This bundle is marked as approved. It works and satisfies the submission rules.
Code (vJASS):

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
 


Changelog

v1.00 - [5 November 2016]
- Initial Release.

v1.10 - [13 November 2016]
- Fixed leak handle reference.
- Fixed bug where manacost is considered after going back in time.
- Added support as a unit ability.
- Added TimerUtils as optional requirement.
- Optimized the code.

v1.20 - [29 November 2016]
- Fixed incorrect documentation.
- Made Table an optional requirement.
Contents

Time Lapse v1.20 (Map)

  1. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,099
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    I found this Time Lapse v1.4b

    But your's uses structs now, Tables and:

    Code (vJASS):

        if oldIndex > 0 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

    which the other seems lacking.
     
  2. Quilnez

    Quilnez

    Joined:
    Oct 12, 2011
    Messages:
    3,218
    Resources:
    37
    Icons:
    2
    Tools:
    1
    Maps:
    7
    Spells:
    21
    Tutorials:
    2
    JASS:
    4
    Resources:
    37
  3. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,334
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Damn I need to improve my searching skills. Anyway since this is a spell, the more the merrier? Allows users to choose what they want I guess.

    True.

    Retro (time travel) isn't noob friendly.
     
  4. zv27

    zv27

    Joined:
    Aug 21, 2010
    Messages:
    296
    Resources:
    0
    Resources:
    0
    Dota spell?
     
  5. BlueSaint

    BlueSaint

    Joined:
    Jun 18, 2012
    Messages:
    2,726
    Resources:
    3
    Tools:
    1
    Spells:
    2
    Resources:
    3
    Retro isn't spell though.
     
  6. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,489
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Which Hero?
     
  7. zv27

    zv27

    Joined:
    Aug 21, 2010
    Messages:
    296
    Resources:
    0
    Resources:
    0
    Nerubian Weaver Ultimate
     
    Last edited: Nov 5, 2016
  8. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,334
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    I thought it was pretty obvious.
     
  9. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,489
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    For people who play DotA :p

    Also, I did a quick look at the code. Good encapsulation, but I think both destroy methods should be private.

    Does the original spell in DotA not include facing angle? It only makes sense to me that facing angle also be included in the warp.
     
    Last edited: Nov 6, 2016
  10. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,334
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    method destroy is actually never called. I just provided it in case user needs it. For scenarios where a Hero will unlearn all abilities or something.

    There's no facing angle stated here. Besides, facing angle can't be set instantly. But I forgot to do the Buff removal part. But I think any user can do that himself though, just add one line of code on the onCast method.

    I just noticed that I forgot to remove this unused part of the script.
    Code (vJASS):

            private static method sfxDestroy takes nothing returns nothing
                local integer id = GetHandleId(GetExpiredTimer())
                call DestroyEffect(thistype.tb.effect[id])
                call thistype.tb.effect.remove(id)
            endmethod
     

    If this is the only part that needs to be update, let me know so that I can fix all of it in one update.
     
    Last edited by a moderator: Nov 7, 2016
  11. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,489
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    I plan on reviewing some submissions after my exam on Wednesday. Look out for it then (;
     
  12. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,489
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20

    Needs Fixed


    • In
      method create
      (struct Sfx),
      local timer t
      is not nulled
    • The caster's mana cost is restored before the mana is removed from casting the spell. You will need to add a 0.00 timer before the functions are run so that the mana cost is first used before restoring mana back

    Suggestions


    • Since RegisterPlayerUnitEvent is a requirement for SpellEffectEvent, you can organize your
      static if
      to look a little bit cleaner:
      Code (vJASS):
      private static method onInit takes nothing returns nothing
          static if LIBRARY_RegisterPlayerUnitEvent then
              call RegisterPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function thistype.learn)
              static if LIBRARY_SpellEffectEvent then
                  call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
              else
                  call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
              endif
          else
              local integer i = 0
              local trigger t1 = CreateTrigger()
              local trigger t2 = CreateTrigger()
              loop
                  exitwhen (i == bj_MAX_PLAYER_SLOTS)
                  call TriggerRegisterPlayerUnitEvent(t1, Player(i), EVENT_PLAYER_HERO_SKILL, null)
                  call TriggerRegisterPlayerUnitEvent(t2, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
                  set i = i + 1
              endloop
              call TriggerAddCondition(t1, function thistype.learn)
              call TriggerAddCondition(t2, Condition(function thistype.cond))
          endif
          set thistype.tb = Table.create()
          set thistype.t = CreateTimer()
          set thistype.ctr = 0
      endmethod
    • In
      method learn
      (struct TimeLapse), when
      thistype.tb.has(id)
      is true, you store
      thistype.tb[id]
      into
      this
      . However, I see no purpose for it, do you mind explaining?
    • As you stated,
      method sfxDestroy
      (struct TimeLapse) is pointless and can be removed
    • Recycling timers are always better than destroying and re-creating

    Status


    Awaiting Update
     
  13. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    I guess destroying timers and not nulling is nothing...
    Code (vJASS):

            private static method expires takes nothing returns nothing
                local timer t = GetExpiredTimer()
                local integer id = GetHandleId(t)
                call thistype(thistype.tb[id]).destroy()
                call thistype.tb.remove(id)
                call DestroyTimer(t) // t destroyed rather than recycled
                set t = null
            endmethod

            static method create takes real x1, real y1, real x2, real y2 returns thistype
                local thistype this = thistype.allocate()
                local timer t = CreateTimer()
                set this.source = AddSpecialEffect(SFX_SOURCE, x1, y1)
                set this.target = AddSpecialEffect(SFX_TARGET, x2, y2)
                set thistype.tb[GetHandleId(t)] = this
                call TimerStart(t, SFX_DURATION, false, function thistype.expires)
                // t not nulled
                return this
            endmethod
     


    Also using a hashtable for this spell doesn't seem necessary even for the "worst" case:
    Code (vJASS):
    12(players/heroes) * 10(seconds-max-back-time, currently 7) * 10(ticks-per-second = 1.0 / TIMEOUT) = 1200(nodes);
    i.e 100 nodes for each unit's circular buffer

    assuming a dota like map.

    Even if you absolutely want to use a hashtable then why not overwrite old values in the hashtable rather than removing and inserting new ones:
    Code (vJASS):

        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 oldIndex > 0 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
     


    It also seemed like going back in time where one had 200 mana would result in the hero having 140 (200 - <spell mana cost>) because the mana gets restored before the game removes mana for the mana cost of the spell, i.e you have to start a 0.0 duration timer and reset the mana then (or TSA ;P).
     
  14. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,489
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    I was more focused in the TimeLapse struct (; thanks for pointing out the reference leak + mana cost issue

    Well recycling is always better, but as far as I know, there's not entirely wrong with destroying a timer as long it's finished. But hey, I'll add it as a suggestion. Thanks!

    Keyword "assuming"
     
    Last edited: Nov 8, 2016
  15. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,334
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Thanks for that!

    I forgot to null that timer handle. But the timer recycling part is not that big of a deal.

    Ok fine I'll make TimerUtils an optional requirement.

    Uh no, I won't do that. What if the user decides to make this a Unit Ability? Or what if the max timing is changed to a drastically much higher value? Also what's the hate about hashtable? Is it because it is a little bit slower than variables? The speed difference would be negligible in my opinion.

    I think the speed difference between removing & inserting vs replacing is negligible. It would just complicate the structure of retrieving data from a certain point in time.

    I did not consider that. Thanks.

    I just made this out of the blue though not giving much time checking it. Thanks for taking the time checking it guys!
     
  16. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,334
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Updated.
    v1.10 - [13 November 2016]
    - Fixed leak handle reference.
    - Fixed bug where manacost is considered after going back in time.
    - Added support as a unit ability.
    - Added TimerUtils as optional requirement.
    - Optimized the code.
     
  17. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,489
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20

    Needs Fixed

    • Nothing

    Suggestions

    • Instead of creating a region onInit, you can just use
      bj_mapInitialPlayableArea
      as a parameter for
      TriggerRegisterEnterRegion()
    • I sent you a message about the 0.00 second timer you have before enumerating preplaced units
    • I recommend using a unit indexer so you can have index / deindex events. The index events will allow you to add units to the list without having to make your own "enter" event. The deindex events will remove the need to loop through the list every UNIT_REFRESH seconds and also fulfill your to do list (;
      Code (vJASS):
      To do:
       - Remove instances of dead units (UNIT_ABILITY = true)
      With that in mind, you shouldn't be removing instances of dead units given they can be revived. This is where the deindex event comes in handy

    • I still think Sfx.destroy should be private :p

    Status


    Approved


    OT: inb4 someone points out something obvious I missed again
     
  18. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,334
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    I'm using it. bj_mapInitialPlayableArea is a rect and TriggerRegisterEnterRegion needs a region parameter.

    I cannot remember why I used a 0.0 second timer. The only logical reason I would do that is, it does not work when it is called directly. I'm gonna check if that's really case.

    I was never a fan of Unit Indexer, it adds an overhead in my opinion. How do UnitIndexer detects when a unit is "removed" from the game?

    Actually, I forgot to remove that "To do list" and it is actually done. The instance is removed when the unit is removed from the map, not when it dies.

    Forgot to made that private. Though it doesn't matter much because the struct itself is private.
     
  19. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,489
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Right xP my mistake

    You are right. I just tested it myself.

    Either by doing what you did or by checking the list every X indexed units. You can implement the latter yourself if you prefer doing it every X indexed units instead of every X seconds.