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

[vJASS] RiseAndFall

JASS:
library RiseAndFall requires optional Table

/*  RiseAndFall v1.06 by Spellbound and revised by JAKEZINC & AGD. Special Thanks to Wareditor.

    This simple library can be used to simulate an airborne effect by causing units to bob up and
    down. It can also be used to make a unit fall from a certain height or rise in the air.
    You can manipulate the height of the unit using this library or for your other purposes easily.

    If you want units with movement types (no other than hover or fly) to be able to use fly height,
    you may put Crow Form ability on them and then remove it at the same time. Alternatively, just get
    AutoFly library and make your life easier. One is available in the test map. Requires UnitDex.

    ------------
     INTERFACE
    ------------

    Operator:

        RiseAndFall[whichUnit] will return the RiseAndFall instance of the unit.

    Static Methods:

        RiseAndFall.create(unit whichUnit, real currentZ, real endZ, real duration, boolean flag)
            ^ This will cause a unit to changed its height from currentZ to endZ over duration.
            The flag determines if the unit should accelerate/decelerate as they reach destination height.

            This function returns the struct instance if you wish to store it.

        RiseAndFall.levitate(unit whichUnit, real currentZ, real endZ, real duration)
            ^ This will make your unit appear to levitate between currentZ and endZ over duration.

            This function returns the struct instance if you wish to store it.

        RiseAndFall.isUnitAirborne(unit whichUnit)
            ^ This returns true/false if a unit is levitating/rising/falling.

    Instance Methods:

        RiseAndFall[whichUnit].end(real endZ, real speed)
            ^ Ends the Rise/Fall/Airborne. real endZ determines the height at which you
            want the unit to end at and the speed determines how fast the unit will get there.
            (calculated as: speed = PERIODIC_INTERVAL / ( (currentHeight - endHeight)/speed )

            Set to the current height of the unit to terminate instantly.
*/
globals
    private constant real PERIODIC_INTERVAL = 0.031250
endglobals

struct RiseAndFall

    private unit unit
    private real heightStart
    private real heightEnd
    private real speed
    private real progress
    private boolean useSmoothstep
    private boolean isAirborne

    private thistype prev
    private thistype next
    private static timer time = CreateTimer()

    static if LIBRARY_Table then
        private static key table
    else
        private static hashtable table = InitHashtable()
    endif

    static method operator [] takes unit whichUnit returns thistype
        static if LIBRARY_Table then
            return Table(table)[GetHandleId(whichUnit)]
        else
            return LoadInteger(table,GetHandleId(whichUnit),0)
        endif
    endmethod

    static method isUnitAirborne takes unit whichUnit returns boolean
        return thistype[whichUnit] != 0
    endmethod

    private method destroy takes nothing returns nothing
        static if LIBRARY_Table then
            call Table(table).remove( GetHandleId(.unit) )
        else
            call RemoveSavedInteger(table,GetHandleId(.unit),0)
        endif
        set this.next.prev = this.prev
        set this.prev.next = this.next
        if thistype(0).next == 0 then
            call PauseTimer(time)
        endif
        call .deallocate()
        set  .unit = null
    endmethod

    private static method update takes nothing returns nothing
        local real x
        local thistype this = thistype(0).next
        loop
            exitwhen this == 0
            set .progress = .progress + .speed

            if  .progress >= 1.0 then
                call SetUnitFlyHeight(.unit,.heightEnd,0)
                if .isAirborne then
                    set x = .heightStart
                    set .heightStart = .heightEnd
                    set .heightEnd = x
                    set .progress = 0.
                else
                    if isUnitAirborne(.unit) then
                        call .destroy()
                    endif
                endif
            else
                set x = .progress
                if .useSmoothstep then
                    call SetUnitFlyHeight(.unit,.heightStart + (.heightEnd - .heightStart)*(x*x*x*(x*(x*6.0-15)+10) ),0)
                else
                    call SetUnitFlyHeight(.unit,.heightStart + (.heightEnd - .heightStart)*(x*x),0)
                endif
            endif

            set this = .next
        endloop
    endmethod

    method end takes real endZ, real speed returns nothing
        local real height = GetUnitFlyHeight(.unit)
        if endZ != height then
            set .heightStart = height
            set .heightEnd = endZ
            set .progress = 0.
            set .isAirborne = false
            set .useSmoothstep = false
            if speed == 0. then // divide by zero prevention
                set speed = 500.
            endif
            set .speed = PERIODIC_INTERVAL / ( (.heightStart - .heightEnd)/speed )
        elseif isUnitAirborne(.unit) then
            set .heightStart = height
            set .heightEnd = endZ
            set .progress = 1.0
            set .isAirborne = false
            set .useSmoothstep = false
            call .destroy()
        endif
    endmethod

    private static method createAirborne takes unit whichUnit, real currentZ, real endZ, real duration, boolean airborne, boolean smooth returns thistype
        local thistype this = RiseAndFall[whichUnit]

        if this != 0 then
            call this.end(GetUnitFlyHeight(whichUnit),1.0)
        endif

        set this = allocate()
        set this.unit = whichUnit
        set this.heightStart = currentZ
        set this.heightEnd = endZ
        set this.progress = 0.
        set this.speed = PERIODIC_INTERVAL/duration
        set this.isAirborne = airborne
        set this.useSmoothstep = smooth

        static if LIBRARY_Table then
            set Table(table)[GetHandleId(whichUnit)] = this
        else
            call SaveInteger(table,GetHandleId(whichUnit),0,this)
        endif

        if thistype(0).next == 0 then
            call TimerStart(time,PERIODIC_INTERVAL,true,function thistype.update)
        endif

        set this.next = 0
        set this.prev = thistype(0).prev
        set thistype(0).prev.next = this
        set thistype(0).prev = this
        return this
    endmethod

    static method create takes unit whichUnit, real currentZ, real endZ, real duration, boolean flag returns thistype
        if RiseAndFall[whichUnit] == 0 then
            return createAirborne(whichUnit,currentZ,endZ,duration,false,flag)
        endif
        return 0
    endmethod

    static method levitate takes unit whichUnit, real currentZ, real endZ, real duration returns thistype
        if RiseAndFall[whichUnit] == 0 then
            return createAirborne(whichUnit,currentZ,endZ,duration,true,true)
        endif
        return 0
    endmethod

endstruct

endlibrary

This simple library can be used to simulate an airborne effect by causing units to bob up and
down. It can also be used to make a unit fall from a certain height or rise in the air. The
purpose of this library is to be used with AttachObject, but one could always find some other
use for it.

This library comes in 2 versions: one that uses ListT and one that uses TimerUtils. You'll probably want the ListT version if you're going to be adding airborne effect to a lot of units.

Demo map attached below.

JASS:
library RiseAndFall requires ListT optional UnitDex
  
/*
  
    RiseAndFall v1.05
    by Spellbound
  
    Special thanks to Wareditor for the math and re-organising my code and got rid of a lot of
    needless lines and methods.
  
    This simple library can be used to simulate an airborne effect by causing units to bob up and
    down. It can also be used to make a unit fall from a certain height or rise in the air. The
    purpose of this library is to be used with AttachObject, but one could always find some other
    use for it.
  
  
    _________________________________________________________________________
    REQUIREMENTS
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  
    Required:
  
        ListT: https://www.hiveworkshop.com/threads/containers-list-t.249011/
  
    Optional
  
        UnitDex: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
        UnitDex will replace hashtables for retrieving airbone instances. Highly recommended so
        as to avoid the hashtable limit.
      
    PS if you want units with non-hover or non-flying movement type to be able to levitate, you
    need to give them and then immediately remove Crow Form from the. Alternatively, just get an
    Autofly library and make your life easier. One is available in the test map. Requires UnitDex.
  
  
    _________________________________________________________________________
    API
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  
    operator:
  
        RiseAndFall[yourUnit] will return the RiseAndFall instance of a unit.
  
    static methods:
  
        RiseAndFall.create(unit u, real currentZ, real endZ, real duration, boolean flag)
            ^ This will cause a unit to changed its height from currentZ to endZ over duration.
            If the flag is true, the unit will decelerate as they reach destination height. This
            can be useful for units that fly or hover.
          
            This function returns the struct instance if you wish to store it.
          
        RiseAndFall.createAirborne(unit u, real currentZ, real endZ, real duration)
            ^ This will make your unit appear to levitate between currentZ and endZ over duration.
            Set a narrow distance for a more realistic effect.
          
            This function returns the struct instance if you wish to store it.
          
        RiseAndFall.isUnitAirborne(unit u)
            ^ This returns true if a unit is airborne/falling/rising.
  
    non-static methods:
  
        RiseAndFall.end(real endZ, real speed)
            ^ Ends the Rise/Fall/Airborne of a unit. real endZ determines the height at which you
            want the unit to end at and speed determines how fast in gets there. use Warcraft 3
            missile speed for referrence.
          
            Set to the current height of the unit to terminate immediately.
  
*/
globals
    private constant real INTERVAL = .03125
endglobals

private module Init
    private static method onInit takes nothing returns nothing
        static if not LIBRARY_UnitDex then
            set instance_storage = InitHashtable()
        endif
        set list = IntegerList.create()
    endmethod
endmodule

struct RiseAndFall
    private unit u
    private real heightStart
    private real heightEnd
    private real speed
    private real progress
    private boolean useSmoothstep
    private boolean isAirborne
  
    private static IntegerList list
    private static timer clock = CreateTimer()
  
    static if LIBRARY_UnitDex then
        private static integer array instance
    else
        private static hashtable instance_storage
    endif
  
    static method operator [] takes unit u returns thistype
        static if LIBRARY_UnitDex then
            return instance[GetUnitId(u)]
        else
            return LoadInteger(instance_storage, GetHandleId(u), 0)
        endif
    endmethod
  
    static method isUnitAirborne takes unit u returns boolean
        static if LIBRARY_UnitDex then
            return instance[GetUnitId(u)] != 0
        else
            return LoadInteger(instance_storage, GetHandleId(u), 0) != 0
        endif
    endmethod
  
    method destroy takes nothing returns nothing
        static if LIBRARY_UnitDex then
            set instance[GetUnitId(u)] = 0
        else
            call FlushChildHashtable(instance_storage, GetHandleId(.u))
        endif
        set .u = null
        call list.removeElem(this)
        if list.size() == 0 then
            call PauseTimer(clock)
        endif
        call .deallocate()
    endmethod
  
    private static method update takes nothing returns nothing
        local thistype this
        local IntegerListItem node = list.first
        local IntegerListItem nodeNext
        local real s
        local real hs
      
        loop
            exitwhen node == 0
            set nodeNext = node.next
            set this = node.data
          
            set .progress = .progress + .speed
          
            if .progress >= 1.then
                call SetUnitFlyHeight(.u, .heightEnd, 0.)
                if .isAirborne then
                    set hs = .heightStart
                    set .heightStart = .heightEnd
                    set .heightEnd = hs
                    set .progress = 0.
                else
                    call .destroy()
                endif
            else
              
                set s = .progress
                if .useSmoothstep then
                    call SetUnitFlyHeight(.u, .heightStart + ( .heightEnd - .heightStart ) * (s * s * s * (s * (s * 6 - 15) + 10)), 0.) //smoothstep
                else
                    call SetUnitFlyHeight(.u, .heightStart + ( .heightEnd - .heightStart ) * (s * s), 0.)
                endif
            endif
      
            set node = nodeNext
        endloop
    endmethod
  
    method end takes real endZ, real spd returns nothing
        local real height = GetUnitFlyHeight(.u)
        if endZ != height then
            set .heightStart = height
            set .heightEnd = endZ
            set .progress = 0.
            set .isAirborne = false
            set .useSmoothstep = false
            if spd == 0. then // divide by zero prevention
                set spd = 600.
            endif
            set .speed = INTERVAL / ((.heightStart - .heightEnd) / spd)
        else
            call .destroy()
        endif
    endmethod
  
    private static method createEx takes unit u, real currentZ, real endZ, real duration, boolean smooth, boolean airborn returns thistype
        local thistype this = RiseAndFall[u]
      
        if this != 0 then
            call this.end(GetUnitFlyHeight(u), 1.)
        endif
      
        set this = allocate()
        set this.u = u
        set this.heightStart = currentZ
        set this.heightEnd = endZ
        set this.progress = 0.
        set this.speed = INTERVAL/duration
        set this.useSmoothstep = smooth
        set this.isAirborne = airborn
      
        static if LIBRARY_UnitDex then
            set instance[GetUnitId(u)] = this
        else
            call SaveInteger(instance_storage, GetHandleId(u), 0, this)
        endif
      
        call list.push(this)
        if list.size() == 1 then
            call TimerStart(clock, INTERVAL, true, function thistype.update)
        endif
      
        return this
    endmethod
  
    static method create takes unit u, real currentZ, real endZ, real duration, boolean smooth returns thistype
        return createEx(u, currentZ, endZ, duration, smooth, false)
    endmethod
  
    static method createAirborne takes unit u, real currentZ, real endZ, real duration returns thistype
        return createEx(u, currentZ, endZ, duration, true, true)
    endmethod
  
    implement Init
endstruct

endlibrary

JASS:
library RiseAndFall requires TimerUtils optional UnitDex
  
/*
  
    RiseAndFall v1.05
    by Spellbound
  
    Special thanks to Wareditor for the math and re-organising my code and got rid of a lot of
    needless lines and methods.
  
    This simple library can be used to simulate an airborne effect by causing units to bob up and
    down. It can also be used to make a unit fall from a certain height or rise in the air. The
    purpose of this library is to be used with AttachObject, but one could always find some other
    use for it.
  
  
    _________________________________________________________________________
    REQUIREMENTS
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  
    Required:
  
        TimerUtils: http://www.wc3c.net/showthread.php?t=101322
  
    Optional
  
        UnitDex: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
        UnitDex will replace hashtables for retrieving airbone instances. Highly recommended so
        as to avoid the hashtable limit.
      
    PS if you want units with non-hover or non-flying movement type to be able to levitate, you
    need to give them and then immediately remove Crow Form from the. Alternatively, just get an
    Autofly library and make your life easier. One is available in the test map. Requires UnitDex.
  
  
    _________________________________________________________________________
    API
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  
    operator:
  
        RiseAndFall[yourUnit] will return the RiseAndFall instance of a unit.
  
    static methods:
  
        RiseAndFall.create(unit u, real currentZ, real endZ, real duration, boolean flag)
            ^ This will cause a unit to changed its height from currentZ to endZ over duration.
            If the flag is true, the unit will decelerate as they reach destination height. This
            can be useful for units that fly or hover.
          
            This function returns the struct instance if you wish to store it.
          
        RiseAndFall.createAirborne(unit u, real currentZ, real endZ, real duration)
            ^ This will make your unit appear to levitate between currentZ and endZ over duration.
            Set a narrow distance for a more realistic effect.
          
            This function returns the struct instance if you wish to store it.
          
        RiseAndFall.isUnitAirborne(unit u)
            ^ This returns true if a unit is airborne/falling/rising.
  
    non-static methods:
  
        RiseAndFall.end(real endZ)
            ^ Ends the Rise/Fall/Airborne of a unit. real endZ determines the height at which you
            want the unit to end at and speed determines how fast in gets there. use Warcraft 3
            missile speed for referrence.
          
            Set to the current height of the unit to terminate immediately.
  
*/
globals
    private constant real INTERVAL = .03125
endglobals

private module Init
    private static method onInit takes nothing returns nothing
        static if not LIBRARY_UnitDex then
            set instance_storage = InitHashtable()
        endif
        set list = IntegerList.create()
    endmethod
endmodule

struct RiseAndFall
    private unit u
    private real heightStart
    private real heightEnd
    private real speed
    private real progress
    private boolean useSmoothstep
    private boolean isAirborne
  
    private static timer clock = CreateTimer()
  
    static if LIBRARY_UnitDex then
        private static integer array instance
    else
        private static hashtable instance_storage
    endif
  
    static method operator [] takes unit u returns thistype
        static if LIBRARY_UnitDex then
            return instance[GetUnitId(u)]
        else
            return LoadInteger(instance_storage, GetHandleId(u), 0)
        endif
    endmethod
  
    static method isUnitAirborne takes unit u returns boolean
        static if LIBRARY_UnitDex then
            return instance[GetUnitId(u)] != 0
        else
            return LoadInteger(instance_storage, GetHandleId(u), 0) != 0
        endif
    endmethod
  
    method destroy takes nothing returns nothing
        static if LIBRARY_UnitDex then
            set instance[GetUnitId(u)] = 0
        else
            call FlushChildHashtable(instance_storage, GetHandleId(.u))
        endif
        set .u = null
        call .deallocate()
    endmethod
  
    private static method update takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local thistype this = GetTimerData(t)
        local real s
        local real hs
          
        set .progress = .progress + .speed
      
        if .progress >= 1.then
            call SetUnitFlyHeight(.u, .heightEnd, 0.)
            if .isAirborne then
                set hs = .heightStart
                set .heightStart = .heightEnd
                set .heightEnd = hs
                set .progress = 0.
            else
                call ReleaseTimer(t)
                call .destroy()
            endif
        else
          
            set s = .progress
            if .useSmoothstep then
                call SetUnitFlyHeight(.u, .heightStart + ( .heightEnd - .heightStart ) * (s * s * s * (s * (s * 6 - 15) + 10)), 0.) //smoothstep
            else
                call SetUnitFlyHeight(.u, .heightStart + ( .heightEnd - .heightStart ) * (s * s), 0.)
            endif
        endif
      
        set t = null
    endmethod
  
    method end takes real endZ, real spd returns nothing
        local real height = GetUnitFlyHeight(.u)
        if endZ != height then
            set .heightStart = height
            set .heightEnd = endZ
            set .progress = 0.
            set .isAirborne = false
            set .useSmoothstep = false
            if spd == 0. then // divide by zero prevention
                set spd = 600.
            endif
            set .speed = INTERVAL / ((.heightStart - .heightEnd) / spd)
        else
            call .destroy()
        endif
    endmethod
  
    private static method createEx takes unit u, real currentZ, real endZ, real duration, boolean smooth, boolean airborn returns thistype
        local thistype this = RiseAndFall[u]
      
        if this != 0 then
            call this.end(GetUnitFlyHeight(u), 1.)
        endif
      
        set this = allocate()
        set this.u = u
        set this.heightStart = currentZ
        set this.heightEnd = endZ
        set this.progress = 0.
        set this.speed = INTERVAL/duration
        set this.useSmoothstep = smooth
        set this.isAirborne = airborn
      
        static if LIBRARY_UnitDex then
            set instance[GetUnitId(u)] = this
        else
            call SaveInteger(instance_storage, GetHandleId(u), 0, this)
        endif
      
        call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.update)
      
        return this
    endmethod
  
    static method create takes unit u, real currentZ, real endZ, real duration, boolean smooth returns thistype
        if RiseAndFall[u] == 0 then
            return createEx(u, currentZ, endZ, duration, smooth, false)
        endif
        return 0
    endmethod
  
    static method startAirborne takes unit u, real currentZ, real endZ, real duration returns thistype
        if RiseAndFall[u] == 0 then
            return createEx(u, currentZ, endZ, duration, true, true)
        endif
        return 0
    endmethod
  
    implement Init
endstruct

endlibrary

- v1.06 (JAKEZINC version) Fixed double-free bug and has no more dependencies. .createAirborne is now .levitate. [vJASS] - RiseAndFall
- v1.05 creator methods renamed to create/createAirborne. Added safety to the private .createEx method as well as to the .end method. Updated documentation. Removed unused/redundant lines.
- v1.04 fixed an issue with the initialization order which was causing libraries depending on RiseAndFall to freeze when saving. Also added a speed parameter to .end for greater control.
- v1.03 RiseAndFall.end now requires a real rather than a boolean.
- v1.02 RiseAndFall.start now takes a boolean argument rather than a integer. RiseAndFall.end now takes a real argument for the endZ.
- v1.01 updated code in great part thanks to Wareditor.
- v1.00 Initial release
 

Attachments

  • RiseAndFall v1.05.w3x
    62.1 KB · Views: 144
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
The test map seems to work fine.

JASS:
static method start takes unit u, real currentZ, real endZ, real duration, integer moveType returns thistype
        return startEx(u, currentZ, endZ, duration, (moveType == 8 or moveType == 2), false)
    endmethod
Why use moveType over a boolean? If a user wants only flying units to have smooth movement this can be done manually.
Right now I feel it is just weird to use integer instead of boolean, where 8 and 2 is true.

(s * s * s * (s * (s * 6 - 15) + 10)) looks like an interpolation. Can you tell me which interpolation method you used?

A few nitpicks:
instance_storage -> instanceStorage
You can look up the conventions here: JPAG - JASS Proper Application Guide
// put 0. here if wanted
If something in your code is supposed to be configurable, you should use constants for that or put it at the top, so people can see it when reading the documentation.
You could also say something in the docuemantation about it: "if you want units to be reset to fly height 0, you can change that in function ..."
Addiotionally "if wanted" does not specify what would change.
 
RiseAndFall was initially meant to be use with another library of mine where movetype was necessary, but I guess I can just detect movement type in that library itself and pass a boolean depending on the type. I changed the argument to boolean.

The interpolation function is Smoothstep - Wikipedia

I'm told the jury's still out on the naming convention of static members?

That comment was supposed to have been removed, but I forgot. You pointing it out made me realised I should have added another argument to .end, so I did that for v1.02.

EDIT: Actually, I might just remove the boolean flag in .end and just keep the real parameter. If the current height matches the endZ, it destroys immediately.

Updated to v1.03. .end now takes a real instead of a boolean.

EDIT2: Updated to v1.04
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
There is a little leftover in the timerutils version: private static IntegerList list and also inside the init module

In the ListT version, you should make the init struct extend an array, or better to remove it and implement the Init module directly in the RiseAndFall struct since the method setup() should also be private.

And lastly, your allocated instances could potentially leak I mean you should add a check and limit the instance to one per unit
JASS:
local thistype this = allocate()
//...
set instance[GetUnitId(u)] = this

EDIT: The API in the documentation doesn't also match with the actual method's API
 
Last edited:
My only suggestion is maybe static method startEx could be static method create.
Will do.

There is a little leftover in the timerutils version: private static IntegerList list and also inside the init module
Ah, thanks for noticing. Will remove.

In the ListT version, you should make the init struct extend an array, or better to remove it and implement the Init module directly in the RiseAndFall struct since the method setup() should also be private.
Hmm, actually that's something I need clarification on. Is there any initialization order difference between implementing a module in a private vs a non-private struct? I was always under the impression that things could bug if the struct wasn't private?

And lastly, your allocated instances could potentially leak I mean you should add a check and limit the instance to one per unit
JASS:
local thistype this = allocate()
//...
set instance[GetUnitId(u)] = this
I'll use the operator to check for the instance and override it if one is already in effect on the unit.

EDIT: The API in the documentation doesn't also match with the actual method's API
Right. Updated.

EDIT: Updated
- v1.05 creator methods renamed to create/createAirborne. Added safety to the private .createEx method as well as to the .end method. Updated documentation. Removed unused/redundant lines.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Hmm, actually that's something I need clarification on. Is there any initialization order difference between implementing a module in a private vs a non-private struct? I was always under the impression that things could bug if the struct wasn't private?
There will no difference. What causes a bug is if the onInit method is not private inside a module, which will be recognized as a struct onInit, hence will be given lower priority in initialization.

I'll use the operator to check for the instance and override it if one is already in effect on the unit.
But it would be better if you will allocate an instance only if the unit isn't affected yet. If it already is, just use the existing instance.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
I understand, glad you respond. The only reason why I nudge you about this is because I'm using it on my next resource.
In fact, I've already got this fixed days ago, although from a TimerUtils version as it's the only flavor I'm using.
But yeah, I'll consider posting it here soon (including the List version) after I've got my available time for them.

Otherwise, I must say, this resource is useful just like how the knockback systems were made to be useful out there.
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
Here's the safest version of it:
JASS:
library RiseAndFall requires optional Table

/*  RiseAndFall v1.05 by Spellbound and revised by JAKEZINC & AGD. Special Thanks to Wareditor.

    This simple library can be used to simulate an airborne effect by causing units to bob up and
    down. It can also be used to make a unit fall from a certain height or rise in the air.
    You can manipulate the height of the unit using this library or for your other purposes easily.

    If you want units with movement types (no other than hover or fly) to be able to use fly height,
    you may put Crow Form ability on them and then remove it at the same time. Alternatively, just get
    AutoFly library and make your life easier. One is available in the test map. Requires UnitDex.

    ------------
     INTERFACE
    ------------

    Operator:

        RiseAndFall[whichUnit] will return the RiseAndFall instance of the unit.

    Static Methods:

        RiseAndFall.create(unit whichUnit, real currentZ, real endZ, real duration, boolean flag)
            ^ This will cause a unit to changed its height from currentZ to endZ over duration.
            The flag determines if the unit should accelerate/decelerate as they reach destination height.

            This function returns the struct instance if you wish to store it.

        RiseAndFall.levitate(unit whichUnit, real currentZ, real endZ, real duration)
            ^ This will make your unit appear to levitate between currentZ and endZ over duration.

            This function returns the struct instance if you wish to store it.

        RiseAndFall.isUnitAirborne(unit whichUnit)
            ^ This returns true/false if a unit is levitating/rising/falling.

    Instance Methods:

        RiseAndFall[whichUnit].end(real endZ, real speed)
            ^ Ends the Rise/Fall/Airborne. real endZ determines the height at which you
            want the unit to end at and the speed determines how fast the unit will get there.
            (calculated as: speed = PERIODIC_INTERVAL / ( (currentHeight - endHeight)/speed )

            Set to the current height of the unit to terminate instantly.
*/
globals
    private constant real PERIODIC_INTERVAL = 0.031250
endglobals

struct RiseAndFall

    private unit unit
    private real heightStart
    private real heightEnd
    private real speed
    private real progress
    private boolean useSmoothstep
    private boolean isAirborne

    private thistype prev
    private thistype next
    private static timer time = CreateTimer()

    static if LIBRARY_Table then
        private static key table
    else
        private static hashtable table = InitHashtable()
    endif

    static method operator [] takes unit whichUnit returns thistype
        static if LIBRARY_Table then
            return Table(table)[GetHandleId(whichUnit)]
        else
            return LoadInteger(table,GetHandleId(whichUnit),0)
        endif
    endmethod

    static method isUnitAirborne takes unit whichUnit returns boolean
        return thistype[whichUnit] != 0
    endmethod

    private method destroy takes nothing returns nothing
        static if LIBRARY_Table then
            call Table(table).remove( GetHandleId(.unit) )
        else
            call RemoveSavedInteger(table,GetHandleId(.unit),0)
        endif
        set this.next.prev = this.prev
        set this.prev.next = this.next
        if thistype(0).next == 0 then
            call PauseTimer(time)
        endif
        call .deallocate()
        set  .unit = null
    endmethod

    private static method update takes nothing returns nothing
        local real x
        local thistype this = thistype(0).next
        loop
            exitwhen this == 0
            set .progress = .progress + .speed

            if  .progress >= 1.0 then
                call SetUnitFlyHeight(.unit,.heightEnd,0)
                if .isAirborne then
                    set x = .heightStart
                    set .heightStart = .heightEnd
                    set .heightEnd = x
                    set .progress = 0.
                else
                    if isUnitAirborne(.unit) then
                        call .destroy()
                    endif
                endif
            else
                set x = .progress
                if .useSmoothstep then
                    call SetUnitFlyHeight(.unit,.heightStart + (.heightEnd - .heightStart)*(x*x*x*(x*(x*6.0-15)+10) ),0)
                else
                    call SetUnitFlyHeight(.unit,.heightStart + (.heightEnd - .heightStart)*(x*x),0)
                endif
            endif

            set this = .next
        endloop
    endmethod

    method end takes real endZ, real speed returns nothing
        local real height = GetUnitFlyHeight(.unit)
        if endZ != height then
            set .heightStart = height
            set .heightEnd = endZ
            set .progress = 0.
            set .isAirborne = false
            set .useSmoothstep = false
            if speed == 0. then // divide by zero prevention
                set speed = 500.
            endif
            set .speed = PERIODIC_INTERVAL / ( (.heightStart - .heightEnd)/speed )
        elseif isUnitAirborne(.unit) then
            set .heightStart = height
            set .heightEnd = endZ
            set .progress = 1.0
            set .isAirborne = false
            set .useSmoothstep = false
            call .destroy()
        endif
    endmethod

    private static method createAirborne takes unit whichUnit, real currentZ, real endZ, real duration, boolean airborne, boolean smooth returns thistype
        local thistype this = RiseAndFall[whichUnit]

        if this != 0 then
            call this.end(GetUnitFlyHeight(whichUnit),1.0)
        endif

        set this = allocate()
        set this.unit = whichUnit
        set this.heightStart = currentZ
        set this.heightEnd = endZ
        set this.progress = 0.
        set this.speed = PERIODIC_INTERVAL/duration
        set this.isAirborne = airborne
        set this.useSmoothstep = smooth

        static if LIBRARY_Table then
            set Table(table)[GetHandleId(whichUnit)] = this
        else
            call SaveInteger(table,GetHandleId(whichUnit),0,this)
        endif

        if thistype(0).next == 0 then
            call TimerStart(time,PERIODIC_INTERVAL,true,function thistype.update)
        endif

        set this.next = 0
        set this.prev = thistype(0).prev
        set thistype(0).prev.next = this
        set thistype(0).prev = this
        return this
    endmethod

    static method create takes unit whichUnit, real currentZ, real endZ, real duration, boolean flag returns thistype
        if RiseAndFall[whichUnit] == 0 then
            return createAirborne(whichUnit,currentZ,endZ,duration,false,flag)
        endif
        return 0
    endmethod

    static method levitate takes unit whichUnit, real currentZ, real endZ, real duration returns thistype
        if RiseAndFall[whichUnit] == 0 then
            return createAirborne(whichUnit,currentZ,endZ,duration,true,true)
        endif
        return 0
    endmethod

endstruct

endlibrary
Plus, now it requires nothing to make it work by the way.
Also thanks to AGD for implementing the optional Table within it.
(I've already included him as one of the revisers)

I've discarded the use of unit indexer from it as the system gets problematic for the instantiated indexes upon unit removal.
 
Top