1. Fill your cup and take your pick among the maps best suited for this year's Hive Cup. The 6th Melee Mapping Contest Poll is up!
    Dismiss Notice
  2. Shoot to thrill, play to kill. Sate your hunger with the 33rd Modeling Contest!
    Dismiss Notice
  3. Do you hear boss music? It's the 17th Mini Mapping Contest!
    Dismiss Notice
  4. 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] Movespeed

Discussion in 'JASS Resources' started by Flux, Aug 18, 2016.

  1. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Code (vJASS):

    library Movespeed /*

                        Movespeed v1.21
                           by Flux
           
            Applies a stacking movespeed modification to a unit
            through code.
           
            Formula:
            New Movespeed = (Default Movespeed + Total FlatBonus)*(1 + Total PercentBonus)
       
        */
    requires /*
           (nothing)
       
        */
    optional Table /*
           If not found, the system will create 1 hashtable. Hashtables are
           limited to 255 per map.
           
        */
    optional TimerUtils /*
           If found, timers for duration will be recycled.
           
       
        ******************************
                       API
        ******************************
       
        struct Movespeed
       
            static method create(unit, percentBonus, flatBonus)
                - Create a Movespeed modification.
                EXAMPLE: local Movespeed ms = Movespeed.create(GetTriggerUnit(), 0.15, 0)
               
            method operator duration=
                - Sets the current duration of the Movespeed instace.
                EXAMPLE: set ms.duration = 5
           
            method operator duration
                - Reads the current duration of the Movespeed instance.
                - Returns zero if the instance has no duration
                EXAMPLE: call BJDebugMsg("Time left: " + R2S(ms.duration))
               
            method change(newPercentBonus, newFlatBonus)
                - Change the movespeed modification of a certain instance
                EXAMPLE: call ms.change(0.20, 0)
           
            method destroy()
                - Remove an instance of movespeed modification.
                - Not needed if thhe Movespeed instance has a duration.
       
        -------------------
               NOTE:
        -------------------
            All in-game movespeed modifiers such as Boots of Speed, Endurance Aura, Slow Aura, etc.
            will still work with this system, but all of them are always applied last.  
           
            Formula:
            New Movespeed = ((Default Movespeed + Total FlatBonus)*(1 + Total PercentBonus) + Total in-game FlatBonus)*(1 + Total in-game PercentBonus)
           
        -----------
          CREDITS
        -----------
            Bribe    - Table
            Vexorian - TimerUtils
            Aniki    - For the movespeed formula used by Warcraft 3

    */
       
        struct Movespeed
           
            readonly real pb
            readonly real fb
            readonly unit u
            private real default
            private timer t
           
            private thistype head
            private integer count
           

            static if LIBRARY_Table then
                private static Table tb
            else
                private static hashtable hash = InitHashtable()
            endif
           
           
            method destroy takes nothing returns nothing
                local thistype head = this.head
                set head.pb = head.pb - this.pb
                set head.fb = head.fb - this.fb
                set head.count = head.count - 1
                if this.t != null then
                    static if LIBRARY_TimerUtils then
                        call ReleaseTimer(this.t)
                    else
                        static if LIBRARY_Table then
                            call thistype.tb.remove(GetHandleId(this.t))
                        else
                            call RemoveSavedInteger(thistype.hash, GetHandleId(this.t), 0)
                        endif
                        call DestroyTimer(this.t)
                    endif
                    set this.t = null
                endif
                if head.count == 0 then
                    static if LIBRARY_Table then
                        call thistype.tb.remove(GetHandleId(this.u))
                    else
                        call RemoveSavedInteger(thistype.hash, GetHandleId(this.u), 0)
                    endif
                    call head.deallocate()
                endif
                call SetUnitMoveSpeed(this.u, (head.default + head.fb)*(1 + head.pb))
                set this.u = null
                call this.deallocate()
            endmethod
           
            method change takes real newPercentBonus, integer newFlatBonus returns nothing
                local thistype head = this.head
                set head.pb = head.pb + newPercentBonus - this.pb
                set head.fb = head.fb + newFlatBonus - this.fb
                set this.pb = newPercentBonus
                set this.fb = newFlatBonus
                call SetUnitMoveSpeed(u, (head.default + head.fb)*(1 + head.pb))
            endmethod
           
            static method create takes unit u, real percentBonus, integer flatBonus returns thistype
                local thistype this = thistype.allocate()
                local integer id = GetHandleId(u)
                local thistype head
                static if LIBRARY_Table then
                    if thistype.tb.has(id) then
                        set head = thistype.tb[id]
                        set head.count = head.count + 1
                    else
                        set head = thistype.allocate()
                        set head.pb = 0
                        set head.fb = 0
                        set head.count = 1
                        set head.default = GetUnitDefaultMoveSpeed(u)
                        set thistype.tb[id] = head
                    endif
                else
                    if HaveSavedInteger(thistype.hash, id, 0) then
                        set head = LoadInteger(thistype.hash, id, 0)
                        set head.count = head.count + 1
                    else
                        set head = thistype.allocate()
                        set head.pb = 0
                        set head.fb = 0
                        set head.count = 1
                        set head.default = GetUnitDefaultMoveSpeed(u)
                        call SaveInteger(thistype.hash, id, 0, head)
                    endif
                endif
                set this.u = u
                set this.pb = percentBonus
                set this.fb = flatBonus
                set this.head = head
                set head.pb = head.pb + this.pb
                set head.fb = head.fb + this.fb
                call SetUnitMoveSpeed(u, (head.default + head.fb)*(1 + head.pb))
                return this
            endmethod
           
            private static method expired takes nothing returns nothing
                static if LIBRARY_TimerUtils then
                    call thistype(GetTimerData(GetExpiredTimer())).destroy()
                elseif LIBRARY_Table then
                    call thistype(thistype.tb[GetHandleId(GetExpiredTimer())]).destroy()
                else
                    call thistype(LoadInteger(thistype.hash, GetHandleId(GetExpiredTimer()), 0)).destroy()
                endif
            endmethod
           
            method operator duration takes nothing returns real
                if this.t == null then
                    return 0.0
                endif
                return TimerGetRemaining(this.t)
            endmethod
           
            method operator duration= takes real time returns nothing
                if this.t == null then
                    static if LIBRARY_TimerUtils then
                        set this.t = NewTimerEx(this)
                    else
                        set this.t = CreateTimer()
                        static if LIBRARY_Table then
                            set thistype.tb[GetHandleId(t)] = this
                        else
                            call SaveInteger(thistype.hash, GetHandleId(t), 0, this)
                        endif
                    endif
                endif
                call TimerStart(this.t, time, false, function thistype.expired)
            endmethod
           
           
            static if LIBRARY_Table then
                private static method onInit takes nothing returns nothing
                    set thistype.tb = Table.create()
                endmethod
            endif
           
        endstruct
    endlibrary
     


    Changelog

    v1.00 - [18 August 2016]
    - Initial Release

    v1.10 - [19 August 2016]
    - Changed movespeed formula.

    v1.11 - [19 September 2016]
    - Fixed inconsistency between having Table and using a hashtable.

    v1.20 - [29 November 2016]
    - Changed API name. (Removed static method createTimed and added method operator duration)
    - Fixed possible double free upon manually destroying a timed instance.
    - Added TimerUtils as an optional requirement.
    - Made instance duration dynamic. It can now be overwritten.

    v1.21 - [2 December 2016]
    - Added reading of instance duration.
    - Removed unused struct attributes.
    - Cached default unit movespeed.
     

    Attached Files:

    Last edited: Dec 1, 2016
  2. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    550
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    I think the formula used by wc3 is:

    move-speed = (default-move-speed + total-flat-bonuses) * (1 + total-percent-bonuses)


    e.g:
    Code (vJASS):

    default-move-speed = 200.0
    total-flat-bonuses = 60 // boots of speed item
    endurance-aura-percent-bonus = 0.10 // level 1 endurance aura from TC
    unholy-aura-percent-bonus = 0.20 // level 2 unholy aura from DK

    move-speed = (200 + 60) * (1 + 0.10 + 0.20) = 260 * (1.30) = 338
     


    slowing percent-bonuses are negative, e.g:
    Code (vJASS):

    default-move-speed = 200.0
    total-flat-bonuses = 60 // boots of speed item
    endurance-aura-percent-bonus = 0.10 // level 1 endurance aura from TC
    unholy-aura-percent-bonus = 0.20 // level 2 unholy aura from DK
    slow-poison = -0.50 // dryad's slow poison

    move-speed = (200 + 60) * (1 + 0.10 + 0.20 + (-0.50)) = 260 * (0.80) = 208
     
     
  3. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Ok, that means I could avoid the O(n^2) operation.

    Updated.
     
    Last edited: Aug 18, 2016
  4. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    550
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    Code (vJASS):

    static if LIBRARY_Table then
        if thistype.tb.has(id) then
            set head = thistype.tb[id]
            set head.count = head.count + 1
        else
            set head = thistype.allocate()
            set head.pb = 0
            set head.fb = 0
            set head.count = 1
            set thistype.tb[id] = head
        endif
    else
        if HaveSavedInteger(thistype.hash, id, 0) then
            set head = LoadInteger(thistype.hash, id, 0)
        else
            set head = thistype.allocate()
            set head.pb = 0
            set head.fb = 0
            call SaveInteger(thistype.hash, id, 0, head)
        endif
    endif
     


    It seems that the code path where Table is missing is not equivalent to the path where it's present.
    Why not make Table and some timer library (TimerUtils, for example) hard requirements, and get rid of all the bug prone static ifs?

    PS: You made me realize (duh...) that units don't need a linked list of "movement speed modifiers" to do scripted movement speed similar to wc3's. Thank you! =)
     
  5. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    You're right, I careless mistake. I can easily fix that.
    I don't want to force people into using other requirements, but instead I like them to know what are the pros/cons of having/not having the said library.

    Yeah, but now it's too simple. I don't know if this one is good enough for the JASS section due to its simplicity.
    (Also not that fun to make anymore, no challenge in making it).
     
  6. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,522
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    It might be useful to give powers over duration, please also have a look at lasts posts here I had with @WereElf.
     
  7. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Powers over duration? Like Purge Movespeed effect? Gradually increasing movespeed?

    You mean controlling the Movespeed instance duration?
     
  8. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,522
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    I mean a method to manipulate the duration which was set to with
    static method createTimed
    .

    WereElf probably doesn't need access to the timer handle itself, though. In case he would really need something that should stay as private member, he maybe can write a module which might be
    implement optional
    'ed. ;D
     
  9. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,522
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    It also might be useful to link to Purge's speed library.
    For example just also as optional requirement.
    It would not change anything in your strcuture of course, but it's just a nice fitting library.
     
  10. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,522
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Submission: Movespeed v1.1
    Date: 28 November 2016
    Status: Approved
    Note:

    A useful library to manipulate a unit's movement speed in percent, and flat increase.
    What was mentioned in posts above could be added.
    You need to
    null
    the local timer in function "createTimed". I know you will fix the leak as soon as possible,
    so I will approve it anyways already, and just check it after some days. Cheers.


    edit:

    Hm, actually not right away, sorry. I see createTimed also returns thistype, and the destroy has no double free protection.
    It seems dangerous if the user has access to manualy destroy his timed Movespeed, when at same time there is no safete actions to also stop/destroy the running timer.
     
  11. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    550
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    I think not using a hashtable and requiring units to have
    SetUnitUserData()
    that is a valid struct instance is better (although I've hardcoded a struct name here):
    Code (vJASS):

    library ModifyMoveSpeed requires Timers /* http://www.hiveworkshop.com/threads/timer-easier-than-timerutils.287441/ */

    struct ModifyMoveSpeed
        Unit u
        real base_amount
        real factor_amount
        Timer t

        static method modify takes Unit u, real base_amount, real factor_amount returns nothing
            local real new_ms
            set u.ms_base = u.ms_base + base_amount
            set u.ms_factor = u.ms_factor + factor_amount
            set new_ms = (u.ms_default + u.ms_base) * (1.0 + u.ms_factor)
            call SetUnitMoveSpeed(u.uu, new_ms)
        endmethod

        method destroy takes nothing returns nothing
            call modify(this.u, -this.base_amount, -this.factor_amount)

            if this.t != 0 then
                call this.t.stop()
            endif

            call this.deallocate()
        endmethod

        static method create takes Unit u, real base_amount, real factor_amount returns thistype
            local thistype this = allocate()

            set this.u = u
            set this.base_amount = base_amount
            set this.factor_amount = factor_amount
            set this.t = 0

            call this.modify(u, base_amount, factor_amount)

            return this
        endmethod

        private static method on_duration_end takes nothing returns nothing
            call thistype(Timer.get_expired_data()).destroy()
        endmethod

        static method create_timed takes Unit u, real base_amount, real factor_amount, real duration returns thistype
            local thistype this = create(u, base_amount, factor_amount)
            set this.t = Timer.start(this, duration, function thistype.on_duration_end)
            return this
        endmethod

    endstruct

    endlibrary

    library ModifyMoveSpeedDemo requires ModifyMoveSpeed

    struct Unit
        unit uu

        real ms_default = 0.0
        real ms_base = 0.0
        real ms_factor = 0.0

        static method from_existing takes unit uu returns thistype
            local thistype this = allocate()

            set this.uu = uu
            call SetUnitUserData(uu, this)

            set this.ms_default = GetUnitDefaultMoveSpeed(uu)

            return this
        endmethod
    endstruct

    endlibrary

    function main takes nothing returns nothing
        local Unit u = Unit.from_existing(CreateUnit(Player(0), 'hfoo', 0.0, 0.0, 270.0))
        call ModifyMoveSpeed.modify(u, 0.0, 0.10)
        call ModifyMoveSpeed.create_timed(u, 0.0, 0.50, 5.0)
    endfunction

     
     
  12. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Right. I can fix this by saving the timer to a struct instance and moving the timer destruction on method destroy. Something like:
    Code (vJASS):

    if this.t != null then
        call DestroyTimer(this.t)
        set this.t = null
    endif
     


    Except that will mess up if users uses a Unit Indexer. Besides, Table is an optional requirement so if you're close to the hashtable limit, just import Table in your map.
     
  13. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    550
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    My point was that you can require units to have a
    GetUnitUserData
    that is a valid struct instance.
    I guess you would have to require some UI (unit indexing) system to initialize the "ms_base = ms_factor = 0.0 and ms_default = GetUnitDefaultMoveSpeed(...)" when a unit gets indexed.

    The "Unit struct" approach is not as "community oriented", I guess, i.e it has to be coded specifically for a map's requirements, although I guess using a module could work as well.

    Of course you can stick to the hashtable approach, if you want.
     
  14. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Updated.

    I'll stick to it. I don't get the hate towards hashtables though.
     
  15. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,522
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Description says "duation=" adds a timeout instead of sets, and why no
    method operator duration
    to get the timeout, for completness sake? :)
     
  16. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    550
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    I don't hate them. I admire your diligence to support both the hashtable and table even at the cost of the many static ifs that kind of reduce the readability (in my opinion).

    Anyway... you might want to remove these unused attributes:
    Code (vJASS):

        private thistype next
        private thistype prev
     


    Could also cache the default move speed:
    Code (vJASS):

    struct Movespeed
    ...
        readonly real default
    ...
                set head = thistype.allocate()
                set head.default = GetUnitDefaultMoveSpeed(u)
    ...
    endstruct
     
     
  17. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Last edited: Dec 1, 2016
  18. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,522
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Submission: MoveSpeed 1.21
    Date: 5 December 2016
    Status: Approved
    Note:

    Very good snippet to manage movement speed manipulations for units.

    Critique.
    The system can apply temporary changes, and infinite changes, but it does not truely allow you
    to change it from temporary to infinite, because the timer will be still in use. How ever you can
    just set it to a very large amount so it would just *never* expire.