1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. The 15th Mini-Mapping Contest came to an end. The Secrets of Warcraft 3 are soon to be revealed! Come and vote in the public poll for your favorite maps.
    Dismiss Notice
  3. The 12th incarnation of the Music Contest is LIVE! The theme is Synthwave. Knight Rider needs a song to listen to on his journey. You should definitely have some fun with this theme!
    Dismiss Notice
  4. Join other hivers in a friendly concept-art contest. The contestants have to create a genie coming out of its container. We wish you the best of luck!
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] RiseAndFall

Discussion in 'JASS Resources' started by Spellbound, Jun 26, 2018.

  1. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,916
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    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.

    ListT version
    Code (vJASS):

    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
     


    TimerUtils version
    Code (vJASS):

    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
     


    Version History
    - 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
     

    Attached Files:

    Last edited: Jul 9, 2018
  2. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,916
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Welp, I'm told there are many inefficient things about this library, so I guess hold off until this is updated.

    So, Wareditor fixed it. Is good now.
     
    Last edited: Jun 26, 2018
  3. Jampion

    Jampion

    JASS Reviewer

    Joined:
    Mar 25, 2016
    Messages:
    1,285
    Resources:
    0
    Resources:
    0
    The test map seems to work fine.

    Code (vJASS):

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

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,916
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    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: Jun 27, 2018
  5. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,632
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Code looks good and it's a nice little effect. It may be a lot of overhead for something you can sort of do with one function (
    SetUnitFlyHeight
    ), but it's useful so approved.

    My only suggestion is maybe
    static method startEx
    could be
    static method create
    .
     
  6. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    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
    Code (vJASS):
    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: Jul 8, 2018
  7. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,916
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Will do.

    Ah, thanks for noticing. Will remove.

    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?

    I'll use the operator to check for the instance and override it if one is already in effect on the unit.

    Right. Updated.

    EDIT: Updated
     
  8. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    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.

    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.