• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Time Lapse v1.20

JASS:
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


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)

Level 22
Joined
Feb 6, 2014
Messages
2,466

Deleted member 219079

D

Deleted member 219079

Retro isn't spell though.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
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.
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.

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.
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.
JASS:
        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:
Level 37
Joined
Jul 22, 2015
Messages
3,485

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:
    JASS:
    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
 
Level 13
Joined
Nov 7, 2014
Messages
571
Needs Fixed
* Nothing

I guess destroying timers and not nulling is nothing...
JASS:
        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:
JASS:
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:
JASS:
    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).
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
I was more focused in the TimeLapse struct (; thanks for pointing out the reference leak + mana cost issue

I guess destroying timers...is nothing...
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!

Also using a hashtable for this spell doesn't seem necessary even for the "worst" case...


assuming a dota like map.
Keyword "assuming"
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
Since RegisterPlayerUnitEvent is a requirement for SpellEffectEvent, you can organize your
static if
to look a little bit cleaner:
Thanks for that!

I guess destroying timers and not nulling is nothing...
I forgot to null that timer handle. But the timer recycling part is not that big of a deal.

Recycling timers are always better than destroying and re-creating
Ok fine I'll make TimerUtils an optional requirement.

Also using a hashtable for this spell doesn't seem necessary even for the "worst" case:
JASS:
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.
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.

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:
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.

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).
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!
 
Level 37
Joined
Jul 22, 2015
Messages
3,485

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 (;
    JASS:
    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
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Instead of creating a region onInit, you can just use
bj_mapInitialPlayableArea
as a parameter for
TriggerRegisterEnterRegion()
I'm using it. bj_mapInitialPlayableArea is a rect and TriggerRegisterEnterRegion needs a region parameter.

I sent you a message about the 0.00 second timer you have before enumerating preplaced units
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 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 (;
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?

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
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.

I still think Sfx.destroy should be private :p
Forgot to made that private. Though it doesn't matter much because the struct itself is private.
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
TriggerRegisterEnterRegion needs a region parameter.
Right xP my mistake

The only logical reason I would do that is, it does not work when it is called directly.
You are right. I just tested it myself.

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?
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.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
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.
I think I'll stick to checking units every 60 seconds. I don't see a significant advantage of using a Unit Indexer or simulating how Unit Indexer detects removed units.

Iirc the unit gets in a custom "defend" ability state, which just stays permanent, and when a unit is removed from game they found out the unit will order "undefend" to return to normal state. So they would need to catch the order.
Yes, I thinks that's true. I was thinking that the mechanics was something like that.
 
Top