• 🏆 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!

[System] Retro (time travel)

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
spells_3355_screenshot.jpg


The coolest system on the market, Retro logs a unit's status for any amount of time, then restores them to the unit instant by instant. Stats include: x, y, z coordinates, optional facing, optional HP and optional MP. If you want more, ask, this system is very flexible and can easily handle new features..

Video is old and will be updated soon.


To view the AIDS version (if you'd prefer it to AutoIndex), click here.


JASS:
//    _  _ ___ _    _   ___ ___
//   /__/ /  / |   / _  /  /  /
//  /  / /__/  |/|/    /  /__/
//                            Using Bribe's Retro Library

How to use retro to make something of your own:

1.  You'll need to make a struct.
    
    struct new
    endstruct
    
2.  You need to turn this into a Retro struct, and the way to do that is to
    import a module called RETRO.  The struct needs to extend array because
    the module replaces the standard allocate/deallocate methods.
    
    struct new extends array
        implement RETRO
    endstruct
    
3.  What now?
    You've just copied to your struct about 250 lines of script, so your
    struct has many new features that it has access to.
    
    Event-methods:
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    method onStart  takes nothing returns nothing
    method onFinish takes nothing returns nothing
    method onLoop   takes nothing returns nothing
    method onLoopForward takes nothing returns nothing
    method onLoopReverse takes nothing returns nothing
    
    How to use them:
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    By having a method by any of those names inside your struct.
    
    struct new extends array
        method onFinish takes nothing returns nothing
            call BJDebugMsg("Something has happened!")
        endmethod
        
        method onStart takes nothing returns nothing
            call BJDebugMsg("Something is coming...")
        endmethod
        
        implement RETRO
    endstruct
    
    Careful Syntax:
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    An event-method should always be placed <above> the implentation of the
    module.  Nothing like this:
    
    struct new extends array
        implement RETRO
        
        method onLoop takes nothing returns nothing
            call BJDebugMsg("I'm doing nothing...")
        endmethod
    endstruct
    
4.  The things that trigger the methods are pretty much just commands found
    in the module itself. I'll tell you what those commands are:
    
    method initiate takes real duration returns nothing
    /
    This method is your point-and-click time-rewinding mechanism. Call this
    method and the unit will zap back in time for the duration you input...
    /
    call initiate(10.0) // Goes back in time for 10 seconds.
    
    Events called: onFinish
    
    method capture takes real duration returns nothing
    /
    This method is great for spell-casting.  In the simplest definition, it
    "bonds" a unit to its immediate position for the given time, then drags
    the unit back over its tracks until it reaches that location once again.
    Rewind-duration is 25 percent of the input duration.
    /
    call capture(10.0) -> Location memorized for 10 seconds, then goes back
                          through time over its steps for 2.5 seconds.
    
    Events called: onStart, onFinish
    
    method allocate takes unit whichUnit, boolean wantLoops, boolean wantExtraLoops returns thistype
    /
    To get this method, I've troubled you with needing to make your struct
    extend an array.  Here's what you get out of it: a regular unit is
    transformed into a working subject capable of travelling through time.
    /
    local thistype this = allocate(GetTriggerUnit(), true, true)
    
    What do the boolean variables do in this method?  They grant you instant
    access to three event-methods: onLoop, onLoopForward and onLoopReverse.
    
5.  Now that you know what triggers the event methods, what do those events
    do?
    
    method onStart -> if you use .capture, onStart triggers after the 10
    second duration.
    
    method onFinish -> if you use .capture, onFinish triggers after the
    2.5 second duration.  If you use .initiate, onFinish triggers after
    the 10 second duration. Think of this like an onDestroy method, as
    it is automatically called by the deallocate method.
    
    method onLoop -> gets called every RETRO_TIMEOUT (I have it at a 0.03125
    second interval). Turned on or off initially by the first boolean param-
    eter in the allocate method. Can be toggled by setting the boolean value
    .noLoops = false (on) or .noLoops = true (off).
    
    method onLoopForward -> gets called for ONE struct per RETRO_TIMEOUT.
    Is turned on or off initially by the second boolean parameter in the
    allocate method.
    
    method onLoopReverse -> same as above, but only one of these two will be
    called per RETRO_TIMEOUT. Both are toggled on/off by the boolean value
    .noExtras = false (on) or .noExtras = true (off).
    
6.  Oh yeah. Methods that come packaged in the struct (capture, initiate, etc.)
    should only be called by methods that are below the module implentation
    (the opposite of what I told you to do with each event-method). If you are
    in a pickle and absolutely must call the method from the wrong precedence,
    you can use (I discourage this approach) this.deallocate.evaluate(this).
    
7.  Other variables at your disposal:
    
    .subject -> the unit you work with.
    .x, .y, .z -> coordinates and flyheight of .subject, respectively.
    .ang -> the unit's facing at a given moment.
    .hp, .mp -> the unit's hit points/mana at a given moment.
    
8.  Shortcuts:
    
    To get the unit's <this> index, you can use:
    
    local thistype this = retro.create(GetTriggerUnit())
    
    else, if you want to inline it and not worry about whether or not the unit
    is actually qualified to be a time-traveller:
    
    local thistype this = GetUnitId(GetTriggerUnit())
    
    You can also just call the allocate method, but that will destroy what you
    had and start it brand new, as a double-allocate first calls deallocate...
    
    You don't need to destroy one of these kinds of structs, because the index
    is taken directly from AutoIndex (or AIDS, if you're using that version).
    
9.  Gotcha's:
    
    retro.create and thistype.allocate will often return 0 (usually if the unit
    is invalid).  If you want to be extra careful, you should check to make
    sure those methods didn't give you 0 before doing anything with the struct.
    
10. I'm pretty sure this does not answer all of your questions, or maybe it
    didn't even answer any of them. Just ask me for clarification on something.


Retro Library:

JASS:
library Retro requires AutoIndex, AutoEvents
//******************************************
//    ___  ___  ___  ___  ___
//   /__/ /__   /   /__/ /  / 
//  /  | /__   /   /  \ /__/
//                          Created by Bribe
//******************************************
//  Original AutoIndex Version
//
/*  Requirements
 *  ¯¯¯¯¯¯¯¯¯¯¯¯
 *  AutoIndex  by grim001 ~ [url]http://www.wc3c.net/showthread.php?t=105643[/url]
 *  AutoEvents by grim001 ~ [url]http://www.wc3c.net/showthread.php?t=106250[/url]
 *  
 *  Description
 *  ¯¯¯¯¯¯¯¯¯¯¯
 *  Retro provides data retrieval and callback operators for use in simulating a time-travel effect for units.
 *  While there is no promise that this is an easy system to implement, I will try to label everything as easy
 *  to read and as simple as I can.  If you have any questions, just ask.
 *
 *  To Know
 *  ¯¯¯¯¯¯¯
 *  On its own, the retro struct indexes every unit that passes through AutoEvents. It records a unit's data
 *  during each lapse of RETRO_TIMEOUT and saves it all in a three-dimensional hashtable. Simple array values
 *  record the current-moment's data aspects: x, y, z and, if you desire, facing angle, HP and MP.
 *  
 *  To allocate your own struct and have access to Retro's excellent callback features, the struct(s) you use
 *  must extend array and implement the module named RETRO. This module uses allocate and deallocate as method
 *  names to regulate those events.  You are welcome to call allocate/deallocate freely as they have built-in
 *  foolproofs. <deallocate> is also automatically called when a unit is loaded into a transport or dies.
 *
 *  You can only instanciate for a given unit once per struct-type.  You can create numerous structs which all
 *  implement the RETRO module.
 *
 *  Events
 *  ¯¯¯¯¯¯
    method onLoop           -> called during each iteration of RETRO_TIMEOUT.
    method onLoopForward    -> called for only one struct per iteration, if it's in forward-mode.
    method onLoopReverse    -> called for only one struct per iteration, if it's in reverse-mode.
  
    method onStart  ->  if you specify a "capture" real value, this will be called after that time has expired.
    method onFinish ->  is called by the deallocate method. deallocate is called automatically if you specify a
                        real value for "capture" or "initiate". It's basically your "onDestroy" method.
 *
 *  Syntax Example
 *  ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    struct timeStruct extends array
    
        method onFinish takes nothing returns nothing
            call BJDebugMsg(GetUnitName(.subject)+"'s stats:\n")
            call BJDebugMsg("X-coordinate is "+R2S(.x))
            call BJDebugMsg("Y-coordinate is "+R2S(.y))
            call BJDebugMsg("Z-coordinate is "+R2S(.z))
            call BJDebugMsg("facing is "+R2S(.ang))
            call BJDebugMsg("hit points are "+R2S(.hp))
            call BJDebugMsg("mana points are "+R2S(.mp)+"\n")
        endmethod
    
        implement RETRO
        
        static method create takes unit newSubject returns thistype
            local thistype this = thistype.allocate(newSubject, true, false) 
            return this
        endmethod
        
        static method onInit takes nothing returns nothing
            local thistype this = thistype.create(newSubject)
            call this.initiate(0.03)
        endmethod
        
    endstruct
 *
 *
 */
    
    globals
        // Values are in seconds;
        constant    real        RETRO_TIMEOUT       = 0.03125   /* This value is a tribute to Jesus4Lyf's helpful Timer32 */
        constant    real        RETRO_MEMORY_MAX    = 20.0      /* The hashtable will deallocate data past this threshold */
        
        // Static-if controllers;
        private     constant    boolean     BREAKS_CHANNEL      = true      /* <true> uses SetUnitPosition for smoother motion */
        private     constant    boolean     RECORD_FACING       = true      /* If you're an efficiency-freak and don't care, set to false */
        private     constant    boolean     RECORD_HP           = true      /* Can save the health-points of units */
        private     constant    boolean     RECORD_MP           = false     /* Can save the mana-points of units */
    endglobals
    
    private module Validate
        static method create takes unit whichSubject returns retro
        
            if GetUnitDefaultMoveSpeed(whichSubject) == 0.00 or GetUnitAbilityLevel(whichSubject, 'Aloc') > 0 or IsUnitType(whichSubject, UNIT_TYPE_STRUCTURE) then
                return 0
            else
                return GetUnitId(whichSubject)
            endif
            
        endmethod   // Anything that returns 0 will under no condition be added to the system.
    endmodule
    
    globals
        private     hashtable   retro_memory    = InitHashtable()  /* Serves as a three-dimensional array */
        private     trigger     retro_launch    = CreateTrigger()  /* So this system only needs one timer */
        private     trigger     retro_expire    = CreateTrigger()  /* RETRO uses 5 fewer handles this way */
        constant    integer     RETRO_ERASER    = R2I(RETRO_MEMORY_MAX/RETRO_TIMEOUT)
        private     keyword     RetroInitializer
    endglobals
    
    struct retro extends array
        
        private retro prev
        private retro next
        
        readonly unit subject
        readonly integer save
        
        real x
        real y
        real z
        
        static if RECORD_FACING then    // Static ifs for maximum efficiency.
            real ang
        endif
        static if RECORD_HP then
            real hp
        endif
        static if RECORD_MP then
            real mp
        endif
        
        implement Validate
        
        method destroy takes nothing returns nothing
            debug call BJDebugMsg("Error: You cannot manually destroy a retro struct.")
        endmethod
        
        private static integer tick = 0
        
        private static method periodic takes nothing returns nothing
            local retro this = retro(0).next
            set retro.tick = retro.tick + 1
            if (retro.tick == 4) then
                set retro.tick = 0
                loop
                    exitwhen this == 0
                    set .save = .save + 1
                    
                    set .x = GetUnitX(.subject)
                    set .y = GetUnitY(.subject)
                    set .z = GetUnitFlyHeight(.subject)
                    call SaveReal(retro_memory, this * 6 + 0, .save, .x)
                    call SaveReal(retro_memory, this * 6 + 1, .save, .y)
                    call SaveReal(retro_memory, this * 6 + 2, .save, .z)
                    
                    static if RECORD_FACING then
                        set .ang = GetUnitFacing(.subject)
                        call SaveReal(retro_memory, this * 6 + 3, .save, .ang)
                    endif
                    static if RECORD_HP then
                        set .hp = GetWidgetLife(.subject)
                        call SaveReal(retro_memory, this * 6 + 4, .save, .hp)
                    endif
                    static if RECORD_MP then
                        set .mp = GetUnitState(.subject, UNIT_STATE_MANA)
                        call SaveReal(retro_memory, this * 6 + 5, .save, .mp)
                    endif
                    
                    if (.save >= RETRO_ERASER) then
                        call RemoveSavedReal(retro_memory, this * 6 + 0, .save - RETRO_ERASER)
                        call RemoveSavedReal(retro_memory, this * 6 + 1, .save - RETRO_ERASER)
                        call RemoveSavedReal(retro_memory, this * 6 + 2, .save - RETRO_ERASER)
                        static if RECORD_FACING then
                            call RemoveSavedReal(retro_memory, this * 6 + 3, .save - RETRO_ERASER)
                        endif
                        static if RECORD_HP then
                            call RemoveSavedReal(retro_memory, this * 6 + 4, .save - RETRO_ERASER)
                        endif
                        static if RECORD_MP then
                            call RemoveSavedReal(retro_memory, this * 6 + 5, .save - RETRO_ERASER)
                        endif
                    endif
                    set this = .next
                endloop
            endif
            
            // Fire things;
            call TriggerEvaluate(retro_launch)
        endmethod
        
        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), RETRO_TIMEOUT, true, function retro.periodic)
        endmethod
        
        static retro removing
        
        private static method remove takes unit exitSubject returns nothing
            local retro this = retro.create(exitSubject)
            if .subject == exitSubject then
            
                set .prev.next = .next
                set .next.prev = .prev
                
                call FlushChildHashtable(retro_memory, this * 6 + 0)
                call FlushChildHashtable(retro_memory, this * 6 + 1)
                call FlushChildHashtable(retro_memory, this * 6 + 2)
                static if RECORD_FACING then
                    call FlushChildHashtable(retro_memory, this * 6 + 3)
                endif
                static if RECORD_HP then
                    call FlushChildHashtable(retro_memory, this * 6 + 4)
                endif
                static if RECORD_MP then
                    call FlushChildHashtable(retro_memory, this * 6 + 5)
                endif
                
                // Fire things;
                set retro.removing = this
                call TriggerEvaluate(retro_expire)
                
                set .subject = null
            endif
        endmethod
        
        private static method add takes unit newSubject returns nothing
            local retro this = retro.create(newSubject)
            if this != 0 then
                set .subject = newSubject
                set .save = 0
                set retro(0).next.prev = this
                set this.next = retro(0).next
                set retro(0).next = this
                set this.prev = retro(0)
            endif
        endmethod
        
        implement RetroInitializer
         
    endstruct
    
    private module RetroInitializer
        private static method onInit takes nothing returns nothing
            call OnUnitIndexed(retro.add)
            call OnUnitDeindexed(retro.remove)
            call OnUnitLoad(retro.remove)
            call OnUnitUnload(retro.add)
            call OnUnitDeath(retro.remove)
            call OnUnitReincarnationFinish(retro.add)
        endmethod
    endmodule
    
    native UnitAlive takes unit id returns boolean
    
    module RETRO    /* May only be implemented in <extends array> structs */
        
        boolean noLoops     /* To toggle a method on/off that gets called <every single> loop */
        boolean noExtras    /* To toggle the extra methods on/off (called for one instance per loop) */
        
        private thistype prev
        private thistype next
      
        private boolean active
        private boolean reverse
        private boolean captured
        
        private real timeLeft
        private real timePend
        private integer iSave
        
        static if RECORD_FACING then
            method operator ang takes nothing returns real  // Delegate ang from retro
                return retro(this).ang
            endmethod
            method operator ang= takes real r returns nothing
                set retro(this).ang = r
            endmethod
        endif
        static if RECORD_HP then
        
            boolean restoreGoodHP
            boolean restoreBadHP
            boolean restoreAnyHP
            
            method operator hp takes nothing returns real   // Delegate hp from retro
                return retro(this).hp
            endmethod
            method operator hp= takes real r returns nothing
                set retro(this).hp = r
            endmethod
            
        endif
        static if RECORD_MP then
        
            boolean restoreGoodMP
            boolean restoreBadMP
            boolean restoreAnyMP
            
            method operator mp takes nothing returns real   // Delegate mp from retro
                return retro(this).mp
            endmethod
            method operator mp= takes real r returns nothing
                set retro(this).mp = r
            endmethod
            
        endif
        
        method operator x takes nothing returns real        // Delegate x
            return retro(this).x
        endmethod
        method operator x= takes real r returns nothing
            set retro(this).x = r
        endmethod
            
        method operator y takes nothing returns real        // Delegate y
            return retro(this).y
        endmethod
        method operator y= takes real r returns nothing
            set retro(this).y = r
        endmethod
            
        method operator z takes nothing returns real        // Delegate z
            return retro(this).z
        endmethod
        method operator z= takes real r returns nothing
            set retro(this).z = r
        endmethod
            
        method operator subject takes nothing returns unit  // Readonly subject
            return retro(this).subject
        endmethod
        
        method deallocate takes nothing returns nothing
            if .active then
                set .active = false
                set .reverse = false
                set .captured = false
                set .prev.next = .next
                set .next.prev = .prev
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, true)
                endif
                static if thistype.onFinish.exists then
                    call .onFinish()
                endif
            endif
        endmethod
        
        method initiate takes real duration returns nothing
            set .iSave = retro(this).save
            set .timeLeft = .timeLeft + duration
            set .reverse = true
            static if BREAKS_CHANNEL then
                call SetUnitPathing(.subject, false)
            endif
        endmethod   /* <duration> is the time the unit will be in reverse */
        
        method capture takes real duration returns nothing
            set .timeLeft = .timeLeft + duration
            set .captured = true
            if .reverse then
                set .reverse = false
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, true)
                endif
            endif
        endmethod   /* <duration> is the time the unit will be in forward. Reverse is 1/4 of that */
        
        method collapse takes nothing returns nothing
            if not .reverse and .captured then
                set .timeLeft = (.timePend / 4)
                set .iSave = retro(this).save
                set .timePend = 0.00
                set .captured = false
                set .reverse = true
                static if thistype.onStart.exists then
                    call .onStart()
                endif
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, false)
                endif
            endif
        endmethod
        
        private static method handler takes nothing returns boolean
            local thistype array choices
            local thistype this = thistype(0).next
            local thistype rand = 0
            local real r
            loop
                exitwhen (this == 0)
                
                if .reverse then
                    
                    set .x = LoadReal(retro_memory, this * 6 + 0, .iSave)
                    set .y = LoadReal(retro_memory, this * 6 + 1, .iSave)
                    set .z = LoadReal(retro_memory, this * 6 + 2, .iSave)
                    
                    static if BREAKS_CHANNEL then
                        call SetUnitPosition(.subject, .x, .y)
                    else
                        call SetUnitX(.subject, .x)
                        call SetUnitY(.subject, .y)
                    endif
                    call SetUnitFlyHeight(.subject, .z, 0.00)
                    
                    static if RECORD_FACING then
                        set .ang = LoadReal(retro_memory, this * 6 + 3, .iSave)
                        call SetUnitFacing(.subject, .ang)
                    endif
                    static if RECORD_HP then   
                        set r = GetWidgetLife(.subject)
                        set .hp = LoadReal(retro_memory, this * 6 + 4, .iSave)
                        if .restoreAnyHP or (.restoreGoodHP and r < .hp - 1) or (.restoreBadHP and r > .hp + 1) then
                            call SetWidgetLife(.subject, .hp)
                        endif
                    endif
                    static if RECORD_MP then
                        set r = GetUnitState(.subject, UNIT_STATE_MANA)
                        set .mp = LoadReal(retro_memory, this * 6 + 5, .iSave)
                        if .restoreAnyMP or (.restoreGoodMP and r < .mp + 1) or (.restoreBadMP and r > .mp + 1) then
                            call SetUnitState(.subject, UNIT_STATE_MANA, .mp)
                        endif
                    endif
                    
                    set .iSave = .iSave - 1
                    set .timeLeft = .timeLeft - RETRO_TIMEOUT
                    if (.timeLeft <= 0.00 or .iSave <= 0) then
                        call .deallocate()
                    endif
                    
                elseif .captured then
                    set .timePend = .timePend + RETRO_TIMEOUT
                    if (.timePend >= .timeLeft) then
                        call .collapse()
                    endif
                endif
                
                static if thistype.onLoop.exists then
                    if not .noLoops then
                        call .onLoop()
                    endif
                endif
                if not .noExtras then
                    set rand:choices = this
                    set rand = rand + 1
                endif
                set this = .next
            endloop
            
            if (rand > 0) then
                set rand = choices[GetRandomInt(0, rand - 1)]
                if rand.reverse then
                    static if thistype.onLoopReverse.exists then
                        call rand.onLoopReverse()
                    endif
                else
                    static if thistype.onLoopForward.exists then
                        call rand.onLoopForward()
                    endif
                endif
            endif
            
            return false
        endmethod
        
        static method allocate takes unit theSubject, boolean wantLoop, boolean wantLoopEx returns thistype
            local thistype this = retro.create(theSubject)
            
            if this == 0 or retro(this).save == 0 then
                return 0
            elseif .active then
                call .deallocate()
            endif
            
            set .active   = true
            set .noLoops  = not wantLoop
            set .noExtras = not wantLoopEx
            set .timeLeft = 0.00
            set .timePend = 0.00
            
            static if RECORD_HP then
                set .restoreGoodHP = false
                set .restoreBadHP = false
                set .restoreAnyHP = false
            endif
            static if RECORD_MP then
                set .restoreGoodMP = false
                set .restoreBadMP = false
                set .restoreAnyMP = false
            endif
            
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            set this.prev = thistype(0)
            
            return this
        endmethod
        
        private static method onRemoval takes nothing returns boolean
            if thistype(retro.removing).active then
                call thistype(retro.removing).deallocate()
            endif
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(retro_launch, Condition(function thistype.handler))    // Enables the handler event
            call TriggerAddCondition(retro_expire, Condition(function thistype.onRemoval))  // Enables the onRemoval event
        endmethod
        
    endmodule
    
    module RetroTimerModule
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(retro_launch, Condition(function thistype.handler))    // Basically for RetroFade
        endmethod
    endmodule
    
endlibrary


Example:

JASS:
library RetroExample requires Retro
    
    private struct example extends array
    // Retro structs must <extend array>
        
    
        effect attach   // This effect gets attached to the unit for the duration of the spell.
        
        method onFinish takes nothing returns nothing
            call DestroyEffect(.attach)
        endmethod   // <onFinish> is the <onDestroy> method. You don't need to call <deallocate> from it.
        
    /*
     *  <onLoopReverse> or <onLoopForward> methods are called only once per cycle of RETRO_TIMEOUT,
     *  so they help to moderate high numbers of special effects to keep the frames-per-second low.
     */ 
        method onLoopReverse takes nothing returns nothing
            call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\StrongDrink\\BrewmasterMissile.mdl", .x, .y))
            call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Transmute\\GoldBottleMissile.mdl", .x, .y))
        endmethod
        
        
        implement RETRO // The module is below <onFinish> and <onLoopReverse> while above <allocate>
        
        
        static method Selection takes nothing returns boolean
            local thistype this
            
            // <allocate> and <deallocate> are provided by the RETRO module.
            set this = allocate(GetFilterUnit(), false, true)
            
            // <allocate> will return <0> if a unit is invalid; I advise checking that before any continuation;
            if this > 0 and GetHeroProperName(.subject) != "Master of Time" then
            
            /*
             *  <initiate> immediately launches a unit back through time for the
             *  specified duration.  This won't do anything for units created in
             *  the same instant; they haven't had time to record some movement.
             */ 
                call .initiate(2.00)
             
                // The unit you're manipulating is referenced as <.subject>
                set .attach = AddSpecialEffectTarget("Abilities\\Spells\\Other\\Drain\\ManaDrainTarget.mdl", .subject, "overhead")
                
                // <.restoreGoodHP> will revive the unit if its former HP was greater.
                set .restoreGoodHP = true
                
            /*
             *  <.restoreGoodMP> will do the same, but for MP. Un-commenting the line will provide a syntax
             *  error, as I have the Retro constant boolean RECORD_MP set to <false>, preventing the variable
             *  from even being compiled.
             */
                //set .restoreGoodMP = true
                
                call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl", .subject, "origin"))
            endif
            return false
        endmethod
        
        
        static method onInit takes nothing returns nothing
            local integer i = 0
            local trigger t = CreateTrigger()
            loop
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SELECTED, Filter(function thistype.Selection))
                exitwhen i == 11
                set i = i + 1
            endloop
        endmethod   // A normal initialization method.
        
        /*
         *  Note the <onLoop>, <onLoopForward> and <onStart> methods are nowhere in sight. They are optional,
         *  and so are <onLoopReverse> and <onFinish>
         *  
         *  Final Notes
         *  ¯¯¯¯¯¯¯¯¯¯¯
         *  ~ You should only implement an event-method if you have a use for it.
         *  ~ You don't have to call <destroy> on a RETRO-implemented struct unless you wish to call your onFinish method.
         */
         
    endstruct
    
endlibrary

Retro-Fade:
JASS:
//! zinc

library RetroFade {
/*
 *  The current fade-functionality is very primitive at the moment.  If you have dynamic fog in your
 *  map that changes sometimes, I don't recommend this library for you.
 */
    constant real
    
        DURATION                = 2.00      ,
        MAX_DISTANCE            = 1000.0    ,   /* Players whose cameras are out of this range will not see the fog fade */
        FLICKER_MAGNITUDE       = 300.0     ;   /* Factors between Z-End + this & Z-End - this */
        
    constant boolean
    
        FLICKER                 = true      ;
    
    real fog[];
    integer units[];
    boolean paused = true, want = false, to = true;
    constant integer Fz = 18, FZ = 19, Fd = 20, Fr = 21, Fg = 22, Fb = 23;
    
    function onInit() {
        integer i;  /*    Your fog settings     Retro's fog settings
    Fog type                     |                       |                   Allowed values
    ¯¯¯¯¯¯¯¯                     V                       V                   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    |  Z-Start */    fog[0]  = 3000.00   ;     fog[6]  = 1250.0    ; /*      0.00  to  ????
    ---------------------------------------------------------------------------------------
    |    Z-End */    fog[1]  = 5000.00   ;     fog[7]  = 3250.00   ; /*      0.00  to  ????
    ---------------------------------------------------------------------------------------
    |  Density */    fog[2]  = 0.50      ;     fog[8]  = 0.1       ; /*      0.00  to  1.00
    ---------------------------------------------------------------------------------------
    |      Red */    fog[3]  = 1.00      ;     fog[9]  = 0.35      ; /*      0.00  to  1.00
    ---------------------------------------------------------------------------------------
    |    Green */    fog[4]  = 1.00      ;     fog[10] = 0.35      ; /*      0.00  to  1.00
    ---------------------------------------------------------------------------------------
    |     Blue */    fog[5]  = 1.00      ;     fog[11] = 0.00      ; /*      0.00  to  1.00
    ---------------------------------------------------------------------------------------
 */
        for (i = 0; i < 24; i+= 1)  i:units = 0;
        for (i = 0; i < 6;  i+= 1) {
        
            if (i:fog==fog[i + 6]) {
                if (i:fog < 0.999)  i:fog+= 0.001;
                else                i:fog-= 0.001;
            }
            
            fog[i + 12] = (i:fog - fog[i + 6])/(DURATION / RETRO_TIMEOUT);
            fog[i + 18] =  i:fog;
        }
    }
        
    public struct retrofade [] {
    
        private static method handler()-> boolean {
            integer i = 18;
            
            if (!want && paused)
                return false;
         
            static if (FLICKER) {
                if (GetRandomReal(0.0, 2.0)<=RETRO_TIMEOUT)
                    SetTerrainFogEx(0, Fz:fog, GetRandomReal(FZ:fog - FLICKER_MAGNITUDE, FZ:fog + FLICKER_MAGNITUDE), Fd:fog, Fr:fog, Fg:fog, Fb:fog);
            }
            
            if (paused)
                return false;
            
            if (to) while (i < 24) { /* Fade into retro-fog */
            
                if   ( i:fog <= fog[i - 12]) {
                       i:fog  = fog[i - 12];        paused = true;  break;
                } else i:fog -= fog[i -  6];                        i+= 1;
            }
            else while (i < 24) { /* Fade into normal-fog */
            
                if   ( i:fog >= fog[i - 18]) {
                       i:fog  = fog[i - 18];        paused = true;  break;
                } else i:fog += fog[i -  6];                        i+= 1;
            }
            
            SetTerrainFogEx (0, Fz:fog, FZ:fog, Fd:fog, Fr:fog, Fg:fog, Fb:fog);
            
            return false;
        }
    
        module RetroTimerModule;
        
        static method CamCheck (real centerX, real centerY) -> boolean {
         real   cam_x= GetCameraTargetPositionX(), cam_y= GetCameraTargetPositionY();
         return(cam_x >= centerX - MAX_DISTANCE && cam_y >= centerY - MAX_DISTANCE &&
                cam_x <= centerX + MAX_DISTANCE && cam_y <= centerY + MAX_DISTANCE );
        }
        
        static method yes (player whichPlayer, real x, real y) {
            integer id = GetPlayerId(whichPlayer);
            if (GetLocalPlayer() != whichPlayer || whichPlayer == null || id > 11)
                return;
                
            if (CamCheck(x, y)) {
                to = true;
                want = true;
                if (paused)
                    paused = false;
            }
            
            units[id]+= 1;
        }
        
        static method no (player whichPlayer) {
            integer id = GetPlayerId(whichPlayer);
            if (GetLocalPlayer() != whichPlayer || whichPlayer==null || id > 11) 
                return;
                
            units[id]-= 1;
            if (units[id] <= 0) { 
                units[id] = 0;
                to = false;
                want = false;
                if (paused)
                    paused = false;
            }
        }
        
    }
}
//! endzinc

Time-Warp:
JASS:
library TimeWarp requires Retro, Projectile, optional Knockback, optional RetroFade, optional Recycle, optional GroupUtils
//************************************************************************************************************************
//   ___ ___ _   _  ___   _    _  _   ___  ___
//   /   /   /| /  /__    |   / /__/ /__/ /__/
//  /  _/_  / |/| /__     |/|/ /  / /  | /
//                                            Created by Bribe
//************************************************************
/*
 *  Requirements
 *  ¯¯¯¯¯¯¯¯¯¯¯¯
 *  Projectile by Berb ~ [url]http://www.hiveworkshop.com/forums/jass-functions-413/custom-projectiles-162121/[/url]
 *  Vector by Anitarf  ~ [url]http://www.wc3c.net/showthread.php?t=87027[/url]
 *  Retro (provided)
 *  
 *  Optional Requirements
 *  ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
 *  Knockback Lite by Berb    ~ [url]http://www.hiveworkshop.com/forums/jass-functions-413/knock-back-lite-161831/[/url]
 *  GroupUtils by Rising_Dusk ~ [url]http://www.wc3c.net/showthread.php?t=104464[/url]
 *  Recycle by Nestharus      ~ [url]http://www.thehelper.net/forums/showthread.php?t=136087[/url]
 *  RetroFade (provided)
 *  
 */
    globals
        private constant integer    SPELL_ID    = 'A003'    /* The rawcode for the Time Travel spell */
        private constant integer    EXTRA_ID    = 'A001'    /* An ability that collapses the caster's portals */
        private constant integer    DUMMY_ID    = 'n000'    /* This should use the DUMMY.mdx model from XE */
    
//* >> Knockback Values;
        private constant real   KB_DURATION         = 1.125     /* Duration is in seconds */
        private constant real   KB_MIN_DISTANCE     = 225.0     /* Value in WC3 gridspace */
        private constant real   KB_MAX_DISTANCE     = 525.0
                                
//* >> TimeWarp Values;         
        private constant real   BASE_DURATION       = 3.00      /* Base value at level 1 */
        private constant real   LVL_INC_DURATION    = 1.15      /* Level increment value */
                                
        private constant real   BASE_DAMAGE         = 10.0
        private constant real   LVL_INC_DAMAGE      = 4.00
                                
        private constant real   BASE_AOE            = 150.0
        private constant real   LVL_INC_AOE         = 40.0
        
        private   group array   casterGroup
    /*
     *  If you have the xebasic library in your map, remove these two lines so they won't conflict:
     */ constant integer    XE_DUMMY_UNITID         = DUMMY_ID
        constant real       XE_MAX_COLLISION_SIZE   = 60.00
    endglobals

    static if LIBRARY_RetroFade then
        globals
            private sound callback = CreateMIDISound("FlashBack1Second", 12700, 12700)
        endglobals
    endif
    
    static if LIBRARY_Knockback then
        struct retroKB extends knockback
        
            effect attach
            static constant string fx = "Abilities\\Spells\\Orc\\AncestralSpirit\\AncestralSpiritCaster.mdl"
            
            private method onDestroy takes nothing returns nothing
                call DestroyEffect(this.attach)
            endmethod
            
            private static method onInit takes nothing returns nothing
                call Preload(fx)
            endmethod
            
        endstruct
    endif
    
    struct retroproj extends projectile
        
        static constant string launchSkin = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl"
        static constant string attackSkin = "Abilities\\Spells\\Undead\\DarkSummoning\\DarkSummonMissile.mdl"
        static constant string simpleSkin = "Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl"
        
        static constant string simpImpact = "Abilities\\Spells\\Items\\AIil\\AIilTarget.mdl"
        static constant string painImpact = "Abilities\\Spells\\Demon\\DemonBoltImpact\\DemonBoltImpact.mdl"
        
        private static method onInit takes nothing returns nothing
            call Preload(launchSkin)
            call Preload(attackSkin)
            call Preload(simpleSkin)
            call Preload(simpImpact)
            call Preload(painImpact)
        endmethod
        
        effect      skin
        group       units
        
        boolean     primary = true
        
        real        damage
        real        Time
        
        vector      carryVec
        timewarp    carryWarp
        
        private method onFinish takes nothing returns nothing
            call DestroyEffect(.skin)
            call SetUnitExploded(.toUnit, true)
            if .primary then
                if UnitAlive(.target) then
                    call timewarp.Initiate(this)
                endif
                call .carryVec.destroy()
            else
                set .carryWarp.go = true
                if UnitAlive(.target) and not .carryWarp.to then
                    call DestroyEffect(AddSpecialEffectTarget(painImpact, .target, "overhead"))
                    call UnitDamageTarget(.carryWarp.caster, .target, .carryWarp.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                elseif UnitAlive(.source) then
                    call DestroyEffect(AddSpecialEffectTarget(simpImpact, .source, "origin"))
                endif
            endif
        endmethod
        
    endstruct
    
    struct timewarp extends array
        
        unit        caster
        unit        oriUnit
        group       units
        
        player      casterOwner
        player      victimOwner
        
        effect      attach
        effect      gloomy
        effect      spooky
        
        boolean     go
        boolean     to
        vector      oriVec
        real        damage
        
        static constant string finalFX      = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageDeathCaster.mdl"
        static constant string callbackFX1  = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
        static constant string callbackFX2  = "Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl"
        
        static constant string forwardLoopFX  = "Abilities\\Weapons\\FlyingMachine\\FlyingMachineImpact.mdl"
        static constant string reverseLoopFX1 = "Abilities\\Spells\\Other\\Transmute\\GoldBottleMissile.mdl"
        static constant string reverseLoopFX2 = "Abilities\\Spells\\Other\\StrongDrink\\BrewmasterMissile.mdl"
        
        static constant string caughtFX = "Abilities\\Spells\\Items\\AIso\\AIsoTarget.mdl"
        static constant string gloomyFX = "Abilities\\Spells\\Human\\Banish\\BanishTarget.mdl"
        static constant string spookyFX = "Abilities\\Spells\\NightElf\\shadowstrike\\shadowstrike.mdl"
        static constant string attachFX = "Abilities\\Spells\\Undead\\Possession\\PossessionTarget.mdl"
        
        static constant string epicenterFX  = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl"
        static constant string recallFXCast = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl"
        static constant string recallFXTarg = "Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
        
        private static method PreloadStrings takes nothing returns nothing
            call Preload(finalFX)
            call Preload(callbackFX1)
            call Preload(callbackFX2)
            call Preload(forwardLoopFX)
            call Preload(reverseLoopFX1)
            call Preload(reverseLoopFX2)
            call Preload(caughtFX)
            call Preload(gloomyFX)
            call Preload(spookyFX)
            call Preload(attachFX)
            call Preload(epicenterFX)
            call Preload(recallFXCast)
            call Preload(recallFXTarg)
        endmethod
        
        private method onFinish takes nothing returns nothing
            call DestroyEffect(AddSpecialEffect(finalFX, .x, .y))
            call DestroyEffect(.spooky)
            call DestroyEffect(.gloomy)
            call DestroyEffect(.attach)
            call ExplodeUnitBJ(.oriUnit)
            call GroupRemoveUnit(.units, .subject)
            call SetUnitTimeScale(.subject, 1.00)
            
            if .oriVec > 0 then
                call .oriVec.destroy()
                set .oriVec = 0
            endif
            static if LIBRARY_RetroFade then
                call retrofade.no(.casterOwner)
                call retrofade.no(.victimOwner)
            endif
            
        endmethod
        
        private method onStart takes nothing returns nothing
            call DestroyEffect(AddSpecialEffectTarget(callbackFX1, .subject, "origin"))
            call DestroyEffect(AddSpecialEffectTarget(callbackFX2, .subject, "origin"))
            call SetUnitTimeScale(.subject, 4.00)
            
            set .restoreBadHP = true
            //set .restoreBadMP = true  <- would throw a syntax error because I have RESTORE_MP set to <false>
            
            static if LIBRARY_RetroFade then
                if retrofade.CamCheck(.x, .y) then  // Only plays the sound if a player's camera is near.
                    call StartSound(callback)
                endif
            endif
            
        endmethod
        
        private method onLoopForward takes nothing returns nothing
            call DestroyEffect(AddSpecialEffectTarget(forwardLoopFX, .subject, "overhead"))
        endmethod
        private method onLoopReverse takes nothing returns nothing
            call DestroyEffect(AddSpecialEffectTarget(reverseLoopFX1, .subject, "origin"))
            call DestroyEffect(AddSpecialEffectTarget(reverseLoopFX2, .subject, "origin"))
        endmethod
        
        
        static vector regVec    // This gets <vector.create> during map init.
        
        static method operator bigVec takes nothing returns vector
            return 8191
        endmethod
        
        
        private method onLoop takes nothing returns nothing
            local real x2
            local real y2
            local retroproj pro
            // Will not launch until the other missile has expired.
            // Check the origin vector to make sure it's not a dud.
            if .go and .oriVec!=0 then
                /* Check if the distance is far enough */
                set x2 = .x - .oriVec.x
                set y2 = .y - .oriVec.y
                if ((x2*x2 + y2*y2) > 0xF000) then
                    /* Yet another important check */
                    if UnitAlive(.subject) and UnitAlive(.oriUnit) then
                        /* bigVec = Location(.x, .y) */
                        call bigVec.getTerrainPoint(.x, .y)
                        /* Add flyheight and offset to vector.Z */
                        set  bigVec.z = bigVec.z + this.z + 90.0
                        if .to then
                            /* If flying toward the subject unit */
                            set .to = false
                            set pro = retroproj.create(CreateUnit(Player(15), DUMMY_ID, .oriVec.x, .oriVec.y, bj_RADTODEG * Atan2(.y - .oriVec.y, .x - .oriVec.x)))
                            set pro.target = .subject
                            /* Control the missile-art of the projectile */
                            set pro.skin = AddSpecialEffectTarget(retroproj.attackSkin, pro.toUnit, "origin")
                            /* Control the dimensions and launch-properties of the projectile */
                            call SetUnitScale(pro.toUnit, 0.65, 0.65, 0.65)
                            call pro.doLaunch(.oriVec, bigVec, 900.0, 0.10)
                        else
                            /* If gliding towards the origin point */
                            set .to = true
                            set pro = retroproj.create(CreateUnit(Player(15), DUMMY_ID, .x, .y, bj_RADTODEG * Atan2(.oriVec.y - .y, .oriVec.x - .x)))
                            set pro.source = .oriUnit
                            /* Control the missile-art of the projectile */
                            set pro.skin = AddSpecialEffectTarget(retroproj.simpleSkin, pro.toUnit, "origin")
                            /* Control the dimensions and launch-properties of the projectile */
                            call SetUnitScale(pro.toUnit, 0.85, 0.85, 0.85)
                            call pro.doLaunch(bigVec, .oriVec, 300.0, 0.10)
                        endif
                        
                        set .go             = false
                        set pro.primary     = false
                        set pro.carryWarp   = this
                        
                    endif
                endif
            endif
        endmethod
        
        implement RETRO
        
        static method Initiate takes retroproj h returns nothing
            local real dist
            local real x
            local real y
            local thistype this = allocate(h.target, true, true)
            
            if this == 0 then   /* The allocate method will return 0 for stationary units (buildings, wards) */
                return
            endif
            
            set .caster = h.source
            set .units  = h.units
            set .damage = h.damage
            
            static if LIBRARY_RetroFade then
                set .casterOwner = GetOwningPlayer(.caster)
                set .victimOwner = GetOwningPlayer(.subject)
                call retrofade.yes(.casterOwner, .x, .y)
                call retrofade.yes(.victimOwner, .x, .y)
            endif
            
            if .oriVec == 0 then
                set .oriVec = vector.createTerrainPoint(.x, .y)
                set .oriVec.z = .oriVec.z + this.z + 60.00
            endif
            
            set .oriUnit = CreateUnit(Player(15), DUMMY_ID, .oriVec.x, .oriVec.y, bj_RADTODEG * Atan2(.oriVec.y - .y, .oriVec.x - .x))
            set .go = true
            set .to = false
            call SetUnitScale(.oriUnit, 0.80, 0.80, 0.80)
            call GroupAddUnit(.units, .subject)
            call .capture(h.Time)
            
            call DestroyEffect(AddSpecialEffectTarget(caughtFX, .oriUnit, "origin"))
            set .gloomy = AddSpecialEffectTarget(gloomyFX, .oriUnit, "chest")
            set .spooky = AddSpecialEffectTarget(spookyFX, .oriUnit, "overhead")
            set .attach = AddSpecialEffectTarget(attachFX, .subject, "overhead")
            
            if IsUnitType(.subject, UNIT_TYPE_FLYING) then
                call UnitAddAbility(.oriUnit, 'Amrf')
                call UnitRemoveAbility(.oriUnit, 'Amrf')
                call SetUnitFlyHeight(.oriUnit, .z, 0.00)
            endif   /* The origin turns into a floating menace for air victims */
            
            static if LIBRARY_Knockback then
                set x = .x - h.carryVec.x
                set y = .y - h.carryVec.y
                set dist = KB_MAX_DISTANCE - (SquareRoot(x*x + y*y) / 2.0)
                if dist <= KB_MIN_DISTANCE then
                    set dist = KB_MIN_DISTANCE
                endif
                set retroKB.create(.subject, Atan2(y, x), dist, KB_DURATION).attach = AddSpecialEffectTarget(retroKB.fx, h.target, "chest")
            endif
            
        endmethod
        
        static method enumAOE takes nothing returns boolean
            local retroproj p
            local unit u = GetFilterUnit()
            if UnitAlive(u) and IsUnitEnemy(u, thistype(0).casterOwner) and thistype(GetUnitId(u)).subject == u then
                
                call bigVec.getTerrainPoint(GetUnitX(u), GetUnitY(u))
                set bigVec.z    = bigVec.z + 60.0
                set p           = retroproj.create(CreateUnit(Player(15), DUMMY_ID, regVec.x, regVec.y, bj_RADTODEG * Atan2(bigVec.y - regVec.y, bigVec.x - regVec.x)))
                set p.skin      = AddSpecialEffectTarget(retroproj.launchSkin, p.toUnit, "origin")
                set p.target    = u
                set p.source    = thistype(0).caster
                set p.units     = thistype(0).units
                set p.damage    = thistype(0).damage
                set p.Time      = retroproj(0).Time
                set p.carryVec  = vector.create(regVec.x, regVec.y, regVec.z)
                call p.doLaunch(regVec, bigVec, 900.0, 0.10)
                
            endif
            set u = null
            return false
        endmethod
        
        static method forceCollapse takes nothing returns boolean
            call thistype(GetUnitId(GetEnumUnit())).collapse()
            call DestroyEffect(AddSpecialEffectTarget(recallFXTarg, GetEnumUnit(), "origin"))
            return false
        endmethod
        
        static method onCast takes nothing returns boolean
            local real factor
            local integer id = GetSpellAbilityId()
            
            if (id == SPELL_ID) then
            
                set thistype(0).caster = GetTriggerUnit()
                set thistype(0).casterOwner = GetTriggerPlayer()
                set thistype(0).units = casterGroup[GetUnitId(thistype(0).caster)]
                
                call regVec.getTerrainPoint(GetSpellTargetX(), GetSpellTargetY())
                set regVec.z = regVec.z + 60.00
                
                call DestroyEffect(AddSpecialEffect(epicenterFX, regVec.x, regVec.y))
                
                set factor = GetUnitAbilityLevel(thistype(0).caster, SPELL_ID) - 1.00
                set thistype(0).damage = BASE_DAMAGE   + LVL_INC_DAMAGE   * factor
                set retroproj(0).Time  = BASE_DURATION + LVL_INC_DURATION * factor
                
                static if LIBRARY_GroupUtils then
                    if GetRandomInt(1, 10) == 1 then
                        call GroupRefresh(thistype(0).units)
                    endif
                    call GroupEnumUnitsInArea(ENUM_GROUP, regVec.x, regVec.y, BASE_AOE + LVL_INC_AOE * factor, Filter(function thistype.enumAOE))
                else
                    call GroupEnumUnitsInRange(bj_lastCreatedGroup, regVec.x, regVec.y, BASE_AOE + LVL_INC_AOE * factor, Filter(function thistype.enumAOE))
                endif
                
            elseif (id == EXTRA_ID) then
                call ForGroup(casterGroup[GetUnitId(GetTriggerUnit())], function thistype.forceCollapse)
                call DestroyEffect(AddSpecialEffectTarget(recallFXCast, GetTriggerUnit(), "origin"))
            endif
            
            return false
        endmethod
        
        static method learnSkill takes nothing returns boolean
            if GetLearnedSkill() == SPELL_ID and GetLearnedSkillLevel() == 1 then
                call UnitAddAbility(GetTriggerUnit(), EXTRA_ID)
                
                static if LIBRARY_Recycle then
                    set casterGroup[GetUnitId(GetTriggerUnit())] = Group.get()
                elseif LIBRARY_GroupUtils then
                    set casterGroup[GetUnitId(GetTriggerUnit())] = NewGroup()
                else
                    set casterGroup[GetUnitId(GetTriggerUnit())] = CreateGroup()
                endif
                
            endif
            return false
        endmethod
        
        static method cleanup takes unit caster returns nothing
            local integer id
            static if LIBRARY_AIDS then
                set id = AIDS_GetDecayingIndex()
            else
                set id = GetUnitId(caster)
            endif
            if casterGroup[id] != null then
                static if LIBRARY_Recycle then
                    call Group.release(casterGroup[id])
                elseif LIBRARY_GroupUtils then
                    call ReleaseGroup(casterGroup[id])
                else
                    call GroupClear(casterGroup[id])
                    call DestroyGroup(casterGroup[id])
                endif
                set casterGroup[id] = null
            endif
        endmethod
        
        static if LIBRARY_AIDS then
            private static method AIDS_OnDeallocate takes nothing returns boolean
                call cleanup(null)
                return false
            endmethod
        endif
        
        private static method onInit takes nothing returns nothing
            local trigger t
            
            set t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
            call TriggerAddCondition(t, Condition(function thistype.learnSkill))
            
            set t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.onCast))
            
            set regVec = vector.create(0.0, 0.0, 0.0)
            call PreloadStrings()
            
            static if LIBRARY_AIDS then
                call AIDS_RegisterOnDeallocate(Filter(function thistype.AIDS_OnDeallocate))
            else
                call OnUnitDeindexed(thistype.cleanup)
            endif
        endmethod
        
    endstruct
    
endlibrary
    
library_once xebasic
/* AutoIndex and GroupUtils will interpret this to set some default values */
endlibrary
 

Attachments

  • TimeWarp.w3x
    146.3 KB · Views: 384
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
I just saw this now. Wow this looks almost exactly like my code. In fact it almost looks better. I like some of your naming prefixes, such as fade_. It unifies a group of members nicely.

Edit -- continued reading, Jesus man. Lmao.

Well, I've read the code and I do not have a damn idea what it does. I got confused when there were fog settings.

Oh I see. I think.

/facepalm -- didn't see the attached test-map.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
JASS:
if (start == 0) or (finish == 0) or (.active) then
    return false

Start vector I have at 8191, finish vector I wanted to be at 0 (to avoid occupying slots on the array). This is what I meant by 0-vector; the system rejects it. The vector isn't null, those x, y and z values were obtained by using vec.getTerrainPoint(x, y).
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
finish vector I wanted to be at 0

If your vector "v" is v == 0 then that means it has no yet been created; you cannot have a vector that has values and is equal to 0, since the stack starts at 1.

If you have a vector "v" such that v.x == 0 and v.y == 0 then that is not the same thing; the actual vector will be an integer from 1 to 8191.

Since vector.getTerrainPoint(x, y) returns an allocated vector, it will never return 0.

Start vector I have at 8191, finish vector I wanted to be at 0

It isn't possible to have a finish vector at 0, the smallest vector that may contain values is 1.

Just to clarify, vector.getTerrainPoint(0, 0) isn't going to return 0. If it is the very first vector being created then it will return 1; there is no way of having a vector that is equal to 0 and contains data. It would be like CreateUnit(null, 'hfoo', 0, 0, 0) - you can't have a null player so that would cause the function to return null, rather than creating a unit for a null player. Similarly, launching a projectile that has a null start/finish doesn't make sense, and as such the function will return false.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
JASS:
vector(0).getTerrainPoint(.x, .y)
vector(8191).getTerrainPoint(.x, .y)

Both fill the array indexes. I'm not really sure why you even bothered to implement a check to make sure the vectors aren't zero, since if the user knows what vectors are that user will definitely know what values he's putting in. I am just trying to use minimal vectors because I only use these in that one instant, so there is no need for continuity or allocation/deallocation.

Besides, you only use these launch vectors as reference points, you just copy those x, y, z values onto your system's vectors, so the array index that you're putting into the system to read those should be irrelevant to the system altogether. Just my thoughts.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
The struct index 0 is used as a null object; by using vector(0) you're going to create a lot of problems for yourself, and the value of that vector is not reliable. I don't really see what you're doing, it seems like you're trying to handle everything JassHelper does with structs by yourself; you shouldn't be blindly typecasting integers into struct types.

I'm not really sure why you even bothered to implement a check to make sure the vectors aren't zero

Because a vector that has been destroyed will be equal to 0; the purpose of the doLaunch method returning a boolean is so that users can debug to see whether or not their projectiles are being executed properly.

I am just trying to use minimal vectors

Then use a global vector. Do not use typecast values.

so the array index that you're putting into the system to read those should be irrelevant to the system altogether

Okay; look at the code:

JASS:
        if (start == 0) or (finish == 0) or (.active) then
            return false
            //There are rules to launching a projectile. If any of these rules are broken, the method will
            //return false. A projectile cannot be launched if it has already been launched.
        endif
        
        set .posVec.x   = start.x     
        set .posVec.y   = start.y       
        set .posVec.z   = start.z
        set .tarVec.x   = finish.x
        set .tarVec.y   = finish.y
        set .tarVec.z   = finish.z
        set .strVec.x   = start.x
        set .strVec.y   = start.y
        set .strVec.z   = start.z
        set .speed      = speed
        set .arc        = arc

If you pass this method null vectors, then you're going to run into errors when you're "copying" the values. If you pass a null (0) vector, then the components of that vector are not necessarily always going to be x=0 and y=0. Rather than launch the projectile improperly and possibly causing malfunctions, I disallow the projectile from being launched.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Ah, got it. That actually makes sense now that you've explained why you have that check. Though I can't deny I like typecasting real values :p

The idea behind me using zero was to maximize the number of vectors that can exist (even by so minimal as 2, since I'm also using 8191, I figure, hey, why not). Since I don't need that vector past the instant I use it, I couldn't care less what happens to it after that time, because I set it fresh each time.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Actually it is as minimal as 1 index. Struct indexes use every integer from 1 to the default array size, 8191. This means that there are 8190 possible indexes and therefore 8190 possible instances at any given time.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
In JASS an array can have a value at index "8191".

I tested by printing the values of several array indexes, including index "8191" (which worked) and "8192" (which did not work).

Actually JASS arrays can either go 0 through 8191 or 1 through 8192.

You cannot have a value saved at the 8192th element of an array in JASS; any elements above 8191 in an array will simply return the default value for that type (0 for an integer).
 
Level 8
Joined
Oct 3, 2008
Messages
367
Don't listen to Nestharus, I think he forgot how Jass arrays work.

Valid array indices go from 0 - 8191. But, 8191 is a bad index to use as it gets erased whenever the game is saved and loaded. And God knows what other weird behavior it might have.

And for the code itself...

Hrm. Is there a reason that RetroInitializer module isn't private? If it never needs to be accessed externally, I don't think you should be able to.

And getting rid of the GroupUtils requirement would be nice. It's as easy as pie to insert your own group recycler in about ten lines of simple code which is more efficient than this GroupUtils thing, and gets rid of a requirement. Of course, you can use a single global group if you never need a group for a duration, but I haven't checked for that. If memory serves, it goes something like this.


JASS:
private struct Group
    group g

    static method create takes nothing returns thistype
        local thistype this = thistype.allocate()
        if g == null then
            set g = CreateGroup()
        else
            call GroupClear(g)
        endif
        return this
    endmethod

endstruct

//It's used something like this.

local Group unitgroupofsometype = Group.create()
call GroupAddUnit(unitgroupofsometype.g, ...)
//Do stuff.

call unitgroupofsometype.destroy()
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Don't listen to Nestharus, I think he forgot how Jass arrays work.
I know how they work.... but I used to do it back when I was a GUI user many many years ago (1.11 or earlier?) and I just tested it out so apparently 0 through 8191 only. You know how GUI users think though, and I was speaking from memory : P. It could have been that the game didn't crash when I used 8192, and to a GUI user, that means you can just plainly use it, lol.

Oh well, perhaps I shouldn't trust my early beliefs anymore ; |, but luckily I think that was the only one left I had from back then >: D.

Oh, you could also use Recycle if you want your entire map to run through the same group/timer recycling. Private recycling isn't typically a good idea: better to have everything run through the same recycling ; P.

Recycle was graveyarded here because this community supports GroupUtils and TimerUtils, even if you don't need data attachment, which doesn't make sense, but w/e : P.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
These are already approved submissions, why digress these resources to the Submissions form simply because you could do it better - they were good enough when they were approved what makes them not good enough now?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I am working on a major update.

This is what the current main system looks like, note this represents a huge, huge jump in performance making it more than 2x faster than the previous version:

JASS:
library Retro requires UnitIndexer, CTL, UnitPropWindow /*
    
      ___  ___  ___  ___  ___
     /__/ /__   /   /__/ /  /
    /  | /__   /   /  \ /__/
                            Created by Bribe
    
    New UnitIndexer Version
  
    Requirements
    ¯¯¯¯¯¯¯¯¯¯¯¯
    UnitIndexer by Nestharus - hiveworkshop.com/forums/showthread.php?t=172090
    CTL by Nestharus - hiveworkshop.com/forums/showthread.php?t=201381
    UnitPropWindow by Bribe - hiveworkshop.com/forums/2052422-post676.html
*/
    
    globals
        //How many seconds of data are stored by Retro. The max is just under
        //256 (8191/32) seconds, so it's more than enough. If you need to re-
        //wind a unit for such a long time I'd have to question your sanity.
        //10 seconds is already a very long time to rewind a unit for and it
        //can get boring to watch.
        private constant real MAX_MEMORY = 10.0
    endglobals
    
    private function UnitFilter takes unit u returns boolean
        return GetUnitAbilityLevel(u, 'Amov') > 0 and GetUnitAbilityLevel(u, 'Aloc') == 0
    endfunction
    
    private module Init
        private static method onInit takes nothing returns nothing
            local integer i = R2I(MAX_MEMORY / 0.03125)
            local integer j
            set .load[0] = i
            loop
                set j = i - 1
                set .save[j] = i //Save next
                set .load[i] = j //Save previous
                exitwhen i == 0
                set i = j
            endloop
        endmethod
    endmodule
    
    struct Retro extends array
        
        readonly boolean rewinding
        
        private thistype prev
        private thistype next
        
        private static hashtable ht = InitHashtable()
        private static integer tick = 0
        private static integer pos = 0
        private static integer array save
        private static integer array load
        
        private method deindex takes nothing returns nothing
            set this.prev.next = this.next
            set this.next.prev = this.prev
            set this.prev = 0
            call FlushChildHashtable(.ht, this)
        endmethod
        
        private method index takes nothing returns nothing
            local integer i = .load[0] * 4
            local unit u = GetUnitById(this)
            local real x = GetUnitX(u)
            local real y = GetUnitY(u)
            local real z = GetUnitFlyHeight(u)
            local real f = GetUnitFacing(u)
            set u = null
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            loop
                call SaveReal(.ht, this, i, x)
                call SaveReal(.ht, this, i + 1, y)
                call SaveReal(.ht, this, i + 2, z)
                call SaveReal(.ht, this, i + 3, f)
                exitwhen i == 0
                set i = i - 4
            endloop
        endmethod
        
        private static method filter takes unit u returns Retro
            return UnitFilter(u)
        endmethod
        
        implement UnitIndexStruct
        
        private thistype vnex
        private thistype vpre
        private integer  vpos
        
        implement CT32
            local thistype this = thistype(0).next
            local integer i = .pos * 4
            local unit u
            set .tick = .tick + 1
            if .tick == 4 then
                set .tick = 0
                loop
                    exitwhen this == 0
                    set u = this.unit
                    call SaveReal(.ht, this, i, GetUnitX(u))
                    call SaveReal(.ht, this, i + 1, GetUnitY(u))
                    call SaveReal(.ht, this, i + 2, GetUnitFlyHeight(u))
                    call SaveReal(.ht, this, i + 3, GetUnitFacing(u))
                    set this = this.next
                endloop
                set .pos = .save[.pos]
            endif
            set this = thistype(0).vnex
            loop
                exitwhen this == 0
                set u = this.unit
                set i = this.vpos - 4
                if i < 0 or IsUnitType(u, UNIT_TYPE_DEAD) or u == null then
                    set this.vnex.vpre = this.vpre
                    set this.vpre.vnex = this.vnex
                    set this.vpre = 0
                    set this.rewinding = false
                    call UnitPropWindow(this).unlock()
                    call FlushChildHashtable(.ht, -this)
                else
                    call SetUnitX(u, LoadReal(.ht, -this, i))
                    call SetUnitY(u, LoadReal(.ht, -this, i + 1))
                    call SetUnitFlyHeight(u, LoadReal(.ht, -this, i + 2), 0)
                    call SetUnitFacing(u, LoadReal(.ht, -this, i + 3))
                endif
                set this = this.vnex
            endloop
            set u = null
        implement CT32End
        
        method rewind takes real duration returns boolean
            local integer i = .pos * 4 //Retrieve from system
            local integer j = 4 * R2I(duration / 0.03125) //Load to volatile
            if this.allocated then
                if not IsUnitType(this.unit, UNIT_TYPE_DEAD) then
                    if duration <= MAX_MEMORY then
                        set this.vpos = j
                        if not this.rewinding then
                            set this.rewinding = true
                            set thistype(0).vnex.vpre = this
                            set this.vnex = thistype(0).vnex
                            set thistype(0).vnex = this
                            call UnitPropWindow(this).unlock()
                        endif
                        loop
                            call SaveReal(.ht, -this, j, LoadReal(.ht, this, i))
                            call SaveReal(.ht, -this, j + 1, LoadReal(.ht, this, i + 1))
                            call SaveReal(.ht, -this, j + 2, LoadReal(.ht, this, i + 2))
                            call SaveReal(.ht, -this, j + 3, LoadReal(.ht, this, i + 3))
                            exitwhen j == 0
                            set j = j - 4
                            set i = .load[i / 4] * 4
                        endloop
                        return true
                    debug else
                        debug call BJDebugMsg("[Retro] Tried to rewind a unit for too long!")
                    endif
                debug else
                    debug call BJDebugMsg("[Retro] Tried to rewind a dead unit!")
                endif
            debug else
                debug call BJDebugMsg("[Retro] Tried to rewind unregistered unit!")
            endif
            return false
        endmethod
        
    endstruct
    
    function RewindUnit takes unit u, real duration returns boolean
        return Retro[u].rewind(duration)
    endfunction
    
endlibrary

JASS:
library RetroExample requires Retro, RegisterPlayerUnitEvent
    
    private struct Example extends array
        
        real timeLeft
        
        implement CTList
            local unit u
            local real x
            local real y
            implement CTListExpire
                set this.timeLeft = this.timeLeft - 0.03125
                if Retro(this).rewinding and this.timeLeft > 0 then
                    set u = GetUnitById(this)
                    set x = GetUnitX(u)
                    set y = GetUnitY(u)
                    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\StrongDrink\\BrewmasterMissile.mdl", x, y))
                    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Transmute\\GoldBottleMissile.mdl", x, y))
                else
                    set this.timeLeft = 0
                    call this.remove()
                endif
            implement CTListNull
            set u = null
        implement CTListEnd
        
        static method selection takes nothing returns nothing
            local unit u = GetTriggerUnit()
            local thistype this = GetUnitUserData(u)
            
            if GetHeroProperName(u) != "Master of Time" then
            
                if not RewindUnit(u, 2.) then
                    return //RewindUnit failed, better luck next time.
                endif
                
                if this.timeLeft == 0 then
                    call this.insert()
                endif
                set this.timeLeft = 2
                
                call DestroyFX(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Drain\\ManaDrainTarget.mdl", u, "overhead"), 2)
                
                call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl", u, "origin"))
            endif
            set u = null
        endmethod
        
        static method onInit takes nothing returns nothing
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SELECTED, function thistype.selection)
        endmethod
        
    endstruct
    
endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
OK, I have updated the above post. Once I compile it and make double sure it's not going to bug, this will be the final version.

Instead of using a module to "extend" Retro, I simply added a "RewindUnit" function. This function does everything the user would need it to do, and all the fancy stuff I had before the user could implement easily on their own, which also would make it more intuitive and modular for them in case they didn't want all the fancy pants stuff.

I have also removed the recording of HP and MP because I feel they just don't belong. Restoring coordinates and facing is the most important.
 
Top