• 🏆 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.4b

What does it do?
This ability allows the caster to return/teleport back to it's condition within several seconds ago which is configurable.

JASS:
//==========================================================================================
//====================================CONFIGURATION=========================================

    // The main ability raw code
    constant function TL_SpellID takes nothing returns integer
        return 'A000'
    endfunction
    
    // Length of rewind time per level
    constant function TL_Time takes integer level returns real
        
        if level == 1 then
            return 6.0          // Level 1
        elseif level == 2 then
            return 6.0          // Level 2
        else
            return 6.0          // Level 3
        endif
        
    endfunction
    
    // Sfx created at the cast point
    constant function TL_CastSfx takes nothing returns string
        return "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportCaster.mdl"
    endfunction
    
    // Sfx created at the target point
    constant function TL_TargetSfx takes nothing returns string
        return "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportTarget.mdl"
    endfunction
    
    // How often the Hero's states will be saved
    constant function TL_Accuracy takes nothing returns real
        return 0.25
    endfunction
    
//=====================================END OF CONFIGURATION=================================
//==========================================================================================
 
    function SaveNode takes nothing returns nothing
        
        local unit      u       = GetEnumUnit()
        local integer   dex
        local integer   node
        local integer   i
        local integer   c
        local real      l
        
        if GetUnitTypeId(u) != 0 then
            set l = GetWidgetLife(u)
            if l > 0.405 then
                set dex = GetHandleId(u)
                set node = R2I(TL_Time(LoadInteger(udg_TL_Hashtable, dex, -2)) / TL_Accuracy())
                set i = node
                loop
                    call SaveReal(udg_TL_Hashtable, dex, i, LoadReal(udg_TL_Hashtable, dex, i - 1))
                    call SaveReal(udg_TL_Hashtable, dex, i + node , LoadReal(udg_TL_Hashtable, dex, i + node - 1))
                    call SaveReal(udg_TL_Hashtable, dex, i + node * 2 , LoadReal(udg_TL_Hashtable, dex, i + node * 2 - 1))
                    call SaveReal(udg_TL_Hashtable, dex, i + node * 3 , LoadReal(udg_TL_Hashtable, dex, i + node * 3 - 1))
                    call SaveReal(udg_TL_Hashtable, dex, i + node * 4 , LoadReal(udg_TL_Hashtable, dex, i + node * 4 - 1))
                    set i = i - 1
                    exitwhen i == 1
                endloop
                call SaveReal(udg_TL_Hashtable, dex, 1, l)
                call SaveReal(udg_TL_Hashtable, dex, 1 + node, GetUnitState(u, UNIT_STATE_MANA))
                call SaveReal(udg_TL_Hashtable, dex, 1 + 2 * node, GetUnitFacing(u))
                call SaveReal(udg_TL_Hashtable, dex, 1 + 3 * node, GetUnitX(u))
                call SaveReal(udg_TL_Hashtable, dex, 1 + 4 * node, GetUnitY(u))
                call SaveInteger(udg_TL_Hashtable, dex, -1, LoadInteger(udg_TL_Hashtable, dex, -1) + 1)
            else
                call FlushChildHashtable(udg_TL_Hashtable,dex)
                call SaveInteger(udg_TL_Hashtable, dex, -1, 0)
            endif
        else
            call GroupRemoveUnit(udg_TL_Group, u)
            call FlushChildHashtable(udg_TL_Hashtable,dex)
            set c = LoadInteger(udg_TL_Hashtable, -1, 999) - 1
            call SaveInteger(udg_TL_Hashtable, -1, 999, c)
            if c == 0 then
                call PauseTimer(udg_TL_Timer)
            endif
        endif
        set u = null
        
    endfunction

    function TL_Loop takes nothing returns nothing
        call ForGroup(udg_TL_Group, function SaveNode)
    endfunction

    function learn takes nothing returns boolean

        local integer   i
        local integer   c
        local integer   lv
        local integer   ln = GetLearnedSkill()
        local unit      u
        
        if ln == TL_SpellID() then
            set u = GetTriggerUnit()
            set lv = GetUnitAbilityLevel(u, ln)
            if not IsUnitInGroup(u, udg_TL_Group) then
                set i = GetHandleId(u)
                set c = LoadInteger(udg_TL_Hashtable, -1, 999) + 1
                call SaveInteger(udg_TL_Hashtable, -1, 999, c)
                call SaveInteger(udg_TL_Hashtable, i, -1, 0)
                call SaveInteger(udg_TL_Hashtable, i, -2, lv)
                call GroupAddUnit(udg_TL_Group, u)
                if c == 1 then
                    call TimerStart(udg_TL_Timer, TL_Accuracy(), true, function TL_Loop)
                endif
            endif
            set u = null
        endif
        return false
    endfunction

    function cast takes nothing returns boolean

        local integer   dex
        local integer   node
        local integer   total
        local unit      caster
        local real      x
        local real      y
        
        if GetSpellAbilityId() == TL_SpellID() then
            set caster = GetTriggerUnit()
            if IsUnitInGroup(caster, udg_TL_Group) then
                set dex = GetHandleId(caster)
                set node = R2I(TL_Time(LoadInteger(udg_TL_Hashtable, dex, -2)) / TL_Accuracy())
                set total = LoadInteger(udg_TL_Hashtable, dex, -1)
                if total > 0 then
                    call DestroyEffect(AddSpecialEffect(TL_CastSfx(), GetUnitX(caster), GetUnitY(caster)))
                    if total >= node then
                        call SetWidgetLife(caster,LoadReal(udg_TL_Hashtable, dex, node))
                        call SetUnitState(caster, UNIT_STATE_MANA, LoadReal(udg_TL_Hashtable, dex, 2 * node))
                        call SetUnitFacing(caster, LoadReal(udg_TL_Hashtable, dex, 3 * node))
                        set x = LoadReal(udg_TL_Hashtable, dex, 4 * node)
                        set y = LoadReal(udg_TL_Hashtable, dex, 5 * node)
                        call SetUnitX(caster, x)
                        call SetUnitY(caster, y)
                    else
                        call SetWidgetLife(caster,LoadReal(udg_TL_Hashtable, dex, total))
                        call SetUnitState(caster, UNIT_STATE_MANA, LoadReal(udg_TL_Hashtable, dex, total + node))
                        call SetUnitFacing(caster, LoadReal(udg_TL_Hashtable, dex, total + node * 2))
                        set x = LoadReal(udg_TL_Hashtable, dex, total + node * 3)
                        set y = LoadReal(udg_TL_Hashtable, dex, total + node * 4)
                        call SetUnitX(caster, x)
                        call SetUnitY(caster, y)
                    endif
                    call DestroyEffect(AddSpecialEffect(TL_TargetSfx(), x, y))
                    call FlushChildHashtable(udg_TL_Hashtable, dex)
                    call SaveInteger(udg_TL_Hashtable, dex, -1, 0)
                else
                    call IssueImmediateOrder(caster, "stop")
                endif
            endif
            set caster = null
        endif
        return false
    endfunction

    function InitTrig_Time_Lapse takes nothing returns nothing
        local trigger t1    = CreateTrigger()
        local trigger t2    = CreateTrigger()
        local integer i     = 0
        
        set udg_TL_Hashtable = InitHashtable()
        loop
            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
            exitwhen i == bj_MAX_PLAYER_SLOTS
        endloop
        call SaveInteger(udg_TL_Hashtable, -1, 999, 0)
        call TriggerAddCondition(t1, Condition(function learn))
        call TriggerAddCondition(t2, Condition(function cast))
        set t1 = null
        set t2 = null
    endfunction

Keywords:
time, lapse, dota, anub'seran
Contents

TimeLapse JASS (Map)

Reviews
BPower 19th Feb 2014 Approved. You can find the full review here 14:46, 11th Feb 2014 BPower: Use a hashtable, a queue, anything ... but not substrings.

Moderator

M

Moderator

BPower 19th Feb 2014
Approved.
You can find the full review here
14:46, 11th Feb 2014
BPower:
Use a hashtable, a queue, anything ... but not substrings.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
A proper way to check wether a unit is alive/removed or not would be
JASS:
    function IsUnitAlive takes unit u returns boolean
        return not IsUnitType(u, UNIT_TYPE_DEAD) and (GetUnitTypeId(u) != 0)
    endfunction
or just for checking life
JASS:
    function IsUnitAlive takes unit u returns boolean
        return GetWidgetLife(u) > 0.405 // and (GetUnitTypeId(u) != 0) 
    endfunction
    // MAGTHERIDON96 SAYS NOOOOOO
Values like mana, life, facing can easily be stored into real arrays, why do you use strings?
For life use SetWidgetLife(..) and GetWidgetLife(..) instead of GetUnitState(...)
Don't use locations, use coordinates only instead. --> DestroyEffect(AddSpecialEffect(effect, x, y))
You use a loop to determine the correct index. A hashtable coupled with HandleId(unit) or simply an array coupled with any UnitIndexer udg_TL_Caster[UnitUserData(unit)] would be better.
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Thnks, may i ask you something? Once i saw a spell without any variable, how can i do that? Or i was just misremembered?

Values like mana, life, facing can easily be stored into real arrays, why do you use strings?
it's because each hero need to save 1 value per TL_Accuracy, I guess using hashtable the spell will be so much simpler, and I don't like simple :ogre_hurrhurr:
 
Last edited:
Level 20
Joined
Aug 13, 2013
Messages
1,696
set loc = Location( GetUnitX(caster), GetUnitY(caster) )
^ Why don't just use coordinates, it is better to use coordinates than locations because it is much faster and it doesn't need to remove.
JASS:
local real x = GetUnitX(caster)
local real y = GetUnitY(caster)
call AddSpecialEffect(TL_CastSfx(), x, y )
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
it's because each hero need to save 1 value per TL_Accuracy, I guess using hashtable the spell will be so much simpler, and I don't like simple
I forgot about that and yes it is possible with a hashtable.

May be over your head at the moment:
In my mind the most beautiful way to solve this problem, is to use a special form of data structure called Queue.
A Queue has the following API -> create(), enqueue(), pop(), clear() and destroy().
You can envision it like the mobile game "snake". You have a queue of nodes (One node per accuracy) with the total number of Time/accuracy (6/0.1 = 60) nodes.
Each clock timeout the first node will be removed and a new last will be added. Each node points to a struct instance with a life, mana, x, y member. On spell cast you read the first node of the Queue.
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
JASS:
constant function TL_Time takes integer level returns real
       
        local real array time
       
        set time[1] = 6.0 // rewind time for level 1
        set time[2] = 6.0 // rewind time for level 2
        set time[3] = 6.0 // rewind time for level 3
       
        return time[level]
    endfunction
->
JASS:
constant function TL_Time takes integer level returns real
    return 6.
endfunction
That can be easily changed like this :
JASS:
constant function TL_Time takes integer level returns real
    return 6.+level
endfunction
Or
JASS:
constant function TL_Time takes integer level returns real
    if level == 1 then
        return 6
    elseif level == 2 then
        return 8
    else
        return 14
    endif
endfunction


JASS:
function IsUnitAlive takes unit u returns boolean
        if ( GetUnitState(u,UNIT_STATE_LIFE) <= 0.24 ) then
            return false
        endif
        return true
    endfunction
->
JASS:
constant function TL_IsUnitAlive takes unit u returns boolean
    return not IsUnitType(u, UNIT_TYPE_ALIVE) and GetUnitTypeId(u)!=0
endfunction

JASS:
call TriggerAddAction( t1, function learn )
call TriggerAddAction( t2, function cast )
->
JASS:
call TriggerAddCondition( t1, Condition(function learn) )
call TriggerAddCondition( t2, Condition(function cast) )
And makes those callback funcs return boolean and return false at the end of these funcs.

Don't use location but real instead.

if ( casted == TL_Ability() ) then
->
if ( GetSpellAbilityId() == TL_Ability() ) then

Rhaaa I stopped when I saw that much strings >_<
Why using strings and not real/integer :/ ?
I'll review it again after changing strings to real/integer ^^
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
JASS:
constant function TL_Time takes integer level returns real
       
        local real array time
       
        set time[1] = 6.0 // rewind time for level 1
        set time[2] = 6.0 // rewind time for level 2
        set time[3] = 6.0 // rewind time for level 3
       
        return time[level]
    endfunction
->
JASS:
constant function TL_Time takes integer level returns real
    return 6.
endfunction
That can be easily changed like this :
JASS:
constant function TL_Time takes integer level returns real
    return 6.+level
endfunction
Or
JASS:
constant function TL_Time takes integer level returns real
    if level == 1 then
        return 6
    elseif level == 2 then
        return 8
    else
        return 14
    endif
endfunction

Mine allows user to define it per level


JASS:
function IsUnitAlive takes unit u returns boolean
        if ( GetUnitState(u,UNIT_STATE_LIFE) <= 0.24 ) then
            return false
        endif
        return true
    endfunction
->
JASS:
constant function TL_IsUnitAlive takes unit u returns boolean
    return not IsUnitType(u, UNIT_TYPE_ALIVE) and GetUnitTypeId(u)!=0
endfunction

I have fixed it on the newest version

JASS:
call TriggerAddAction( t1, function learn )
call TriggerAddAction( t2, function cast )
->
JASS:
call TriggerAddCondition( t1, Condition(function learn) )
call TriggerAddCondition( t2, Condition(function cast) )
And makes those callback funcs return boolean and return false at the end of these funcs.

okay

Don't use location but real instead.
fixed

if ( casted == TL_Ability() ) then
->
if ( GetSpellAbilityId() == TL_Ability() ) then

local casted = GetSpellAbilityId()

Rhaaa I stopped when I saw that much strings >_<
Why using strings and not real/integer :/ ?
I'll review it again after changing strings to real/integer ^^
Values like mana, life, facing can easily be stored into real arrays, why do you use strings?
it's because each hero need to save 1 value per TL_Accuracy, I guess using hashtable the spell will be so much simpler, and I don't like simple :ogre_hurrhurr:

I have updated it, please check the newest one ;) thnks alot anyway
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Just to bring light into the darkness, those are the requirements for inlined functions. You can also find this info inside the JassHelper manual.
  • The function is a one-liner
  • If the function is called by call the function's contents must begin with set or call or be a return of a single function.
  • If the inlined function is an assigment (set) it should not assign one of its arguments.
  • Every argument must be evaluated once and only once by the function, in the same order as they appear in the arguments list.
  • If the function contains function calls, they should all be evaluated after the arguments UNLESS the function is marked as non-state changing, at the moment some very few native functions and also return bug exploiters are considered as non-state changing.
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
Ahah don't worry we all try those kind of things...
But if you benchmarked this against the classic way this is very slower ;)

After I can't really tell notes since I usually can't download xD !
So I just review codes most of the time :)
I think I'll be off for today no more battery sadly :/
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
There is also a lot of feedback from Malhorne in the thread, who is also an experienced wc3 coder.
I might be the one moderating your resource, but other opinions are also very valuable.:thumbs_up:

You have to present a coding concept "we" you, me and other members can work with. Come up with another solution, then I can give you specific tips how to improve it.

I'm not going to code the whole spell for you. If you need help to get started you just have to ask for it.
I think working with Table (Bribes/Vexorians) or an Hashtable would be a solid solution for a new approach.

Not recommended, mainly because it is more difficult, by me is the queue thing, but if you really want to go for it I will write a queue module for you.
The jass section is in need of one anyway.
It would take some time, because I'm currently short in time :/)
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
malbro has only one suggestion that is just like yours, "dont use strings" :) I will change it anyway.. ;)

EDIT:
if I change to hashtable, you will say, "the spell is too simple" because, really, using hashtable the spell will be looks just like a sandbox, I mean very heavenly craby patty simple..

EDIT 2:
I think I will work with the table or queue that you have mentioned above, thanks for the suggestion, BPow!
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
There is also a lot of feedback from Malhorne in the thread, who is also an experienced wc3 coder.
I might be the one moderating your resource, but other opinions are also very valuable.:thumbs_up:

Loved it <3

Queue isn't the best way : linked list > queue :p

Then I suggested some code improvements but I didn't finish to review the code because substrings messed the code :(
I'll redo it again after those changements :D
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,870
EDIT:
if I change to hashtable, you will say, "the spell is too simple" because, really, using hashtable the spell will be looks just like a sandbox, I mean very heavenly craby patty simple..
Then you better code in chinese, if it is so simple :p
It's better something simple and with effectiveness than something complex and totally CRAP and confusing.
So do by the easiest way :p
Keep your stuff always as simple as possible without losing important features.
Noone ever said needlessly complicated == cool.
Follow these sentences, best advice :)
 
Last edited:
Level 18
Joined
Sep 14, 2012
Messages
3,413
JASS:
local unit caster = GetTriggerUnit()
        local real accuracy = TL_Accuracy()
Init this values only if the condition is true so you can put the set caster = null in the if block too.
The reason of this optimization is that every time you run a spell this will create vars and init their values and null it whereas if you do as I said it will do it only if the spell is the good one.

local integer learned = GetLearnedSkill() local unit u = GetTriggerUnit()
Same there :)
And so you can remove the learned variable :p
Because if you do with my way there is only one reference (don't count the cond)

I suggest to inline your ForGroup with a FoG loop :D

JASS:
if CountUnitsInGroup(udg_TL_Group) == 0 then
                call DestroyTimer(udg_TL_Timer)
            endif
Just PauseTimer don't destroy it ;)
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
The spell is leakless and it works.
Instuctions and objects are also ok.

TriggerRegisterPlayerUnitEvent and a loop from 0 to bj_MAX_PLAYER_SLOTS is exactly the same as TriggerRegisterAnyUnitEventBJ unless you add a specific player filter (no computers, etc...)
The ForGroup operation loses efficiency the more units are affected, however as long as the number is kept low it's no big deal.
Talking about efficiency the way you save/move unit values might critically harm the ingame performance if:
  • The accuracy is high
  • The number of considered units is too large
  • TL_Time is set to a high value, while the accuracy is also high
You could add a notice for users that (TL_Time/TL_Accuracy*considered units) should not surpass a certain number. I would suggest ~350.
A different strategy would remove this problem.

I think this one --> call IssueImmediateOrder(caster, "stop") is not required, because the channel ability itself interupts the current order.
Unlikely to happen, but if so the ability would go into cooldown while nothing happens.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I think this one --> call IssueImmediateOrder(caster, "stop") is not required, because the channel ability itself interupts the current order.
Unlikely to happen, but if so the ability would go into cooldown while nothing happens.
that's why I order the caster to stop so that if the hashtable is still empty the spell won't be cooldowned.. and finally... thnks for the review and approval!!
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
it's not stupid, but good instead ;)
look at the 'else' part
JASS:
            if l > 0.405 then
                set dex = GetHandleId(u)
                set node = R2I(TL_Time(LoadInteger(udg_TL_Hashtable, dex, -2)) / TL_Accuracy())
                set i = node
                loop
                    call SaveReal(udg_TL_Hashtable, dex, i, LoadReal(udg_TL_Hashtable, dex, i - 1))
                    call SaveReal(udg_TL_Hashtable, dex, i + node , LoadReal(udg_TL_Hashtable, dex, i + node - 1))
                    call SaveReal(udg_TL_Hashtable, dex, i + node * 2 , LoadReal(udg_TL_Hashtable, dex, i + node * 2 - 1))
                    call SaveReal(udg_TL_Hashtable, dex, i + node * 3 , LoadReal(udg_TL_Hashtable, dex, i + node * 3 - 1))
                    call SaveReal(udg_TL_Hashtable, dex, i + node * 4 , LoadReal(udg_TL_Hashtable, dex, i + node * 4 - 1))
                    set i = i - 1
                    exitwhen i == 1
                endloop
                call SaveReal(udg_TL_Hashtable, dex, 1, l)
                call SaveReal(udg_TL_Hashtable, dex, 1 + node, GetUnitState(u, UNIT_STATE_MANA))
                call SaveReal(udg_TL_Hashtable, dex, 1 + 2 * node, GetUnitFacing(u))
                call SaveReal(udg_TL_Hashtable, dex, 1 + 3 * node, GetUnitX(u))
                call SaveReal(udg_TL_Hashtable, dex, 1 + 4 * node, GetUnitY(u))
                call SaveInteger(udg_TL_Hashtable, dex, -1, LoadInteger(udg_TL_Hashtable, dex, -1) + 1)
            else
                call FlushChildHashtable(udg_TL_Hashtable,dex)
                call SaveInteger(udg_TL_Hashtable, dex, -1, 0)
when the Hero is dead, the hashtable get flushed ;) then the counter is reseted to 0, where if the counter is still 0, the Hero will automatically ordered to stop.. so it's safe..

JASS:
                if total > 0 then
                else
                    call IssueImmediateOrder(caster, "stop")
 
Top