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

MoveSpeedX for GUI v1.1.0.0

Requires JassNewGenPack v5d and JassHelper 0.A.2.B!

This library allows you to set unit move speeds beyond the 522 limit. Special thanks to Jesus4Lyf for the base code. It has been modified from the normal MoveSpeedX to be compatible with GUI. While the system is in vJASS, you can simply use the GUI function to modify the speed and it will do the rest for you. To retrieve speed properly, you can use the global array named "UnitSpeedX". To retrieve it for a specific unit, it would be UnitSpeedX[Custom value of <unit>]. Note that you need a unit indexer for that to work properly.

It has some quirks with very high speeds (beyond ~1000) but otherwise it works perfectly.

*Note*: Please read the documentation.

System:
JASS:
library MoveSpeedXGUI /* v1.1.0.0
*************************************************************************************
*
*   This library allows you to set unit movement speeds beyond 522 without bugs.
*   This is an extension of the library MoveSpeedX, but is formatted for GUI use.
*   Credits to Jesus4Lyf for the original system.
*
************************************************************************************
*
*   SETTINGS
*/
globals
    private constant real PERIOD = 0.03125
        //  This is the period on which all units will be run.
        // If you lower this value, movement bonuses will be smoother,
        // but will require more processing power (lag more).
        //  Also, the lower this is, the higher the move speed can be
        // before it starts bugging on waypoints. The lowest valid
        // period is 0.00125. A period of 0.00625 is very robust.
    private constant real MARGIN = 0.01
        // This is the margin of approximation when comparing reals.
        // You will most likely not need to change this.
endglobals
/*
************************************************************************************
*
*    Functions
*
*        function GetUnitMoveSpeedX takes unit whichUnit returns real
*           - Returns a unit movement speed. The GUI function will
*           - not return the correct value. This function will always
*           - return the correct value regardless of whether the unit
*           - has a movement speed beyond 522.
*
************************************************************************************
*
*   REQUIREMENTS
*
*       1.  JassNewGen Pack v5d
*       2.  JassHelper 0.A.2.B
*       3.  Any unit indexer
*
*   HOW TO IMPLEMENT
*  
*       1.  Copy the 'folder' MoveSpeedX.
*       2.  Paste it into your map.
*       3.  Open "Advanced -> Gameplay Constants".
*       4.  Checkmark "Use Custom Gameplay Constants".
*       5.  Find the field, "Movement - Unit Speed - Maximum", change
*           that to 522.
*       6.  Find the field, "Movement - Unit Speed - Minimum", hold
*           shift and click, and change it to 0.
*       7.  Read HOW TO USE.
*
************************************************************************************
*
*   HOW TO USE
*
*       This system will automatically work by itself. You can use the
*       normal GUI function for modifying unit movement speeds. Simply
*       use "Unit - Set Movement Speed", input whatever value you want,
*       and you are good to go! It will handle values beyond 522 by itself.
*
*       HOWEVER, the GUI function will not return correct values if a unit
*       has a movement speed greater than 522. To fix this, use the function
*       GetUnitMoveSpeedX to return the correct value. A sample is given in
*       the trigger "Speed Change" in the test map.
*
************************************************************************************
*
*   NOTES
*
*       Units that were issued orders as groups might not *always* end up in the proper
*       "order". (they might not end up in an organized formation) They do sometimes though.
*       This is only for units with speeds above 522.
*
*       This also will not factor in bonuses and probably not slows either.
*
*       Units may waddle around the point for a little bit. Reduce PERIOD to fix
*       it a little bit. I recommend about 0.02 if you have really high speeds.
*
************************************************************************************/

    private function ApproxEqual takes real A, real B returns boolean
        return (A >= (B - MARGIN)) and (A <= (B + MARGIN))
    endfunction
   
    private module M
        private static trigger issued = CreateTrigger()
       
        thistype next
        thistype prev
       
        boolean enabled
        unit curr
        real speed
        real x
        real y
        real ox
        real oy
       
        method destroy takes nothing returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set this.enabled = false
        endmethod
           
        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next // first instance in list
            local real nx // the x-coordinate after tick
            local real ny // the y-coordinate after tick
            local real dx // distance between new-x and old-x
            local real dy // distance between new-y and old-y
            local real d  // distance between new point and old point
            local integer order // the unit's current order
            local unit u // unit being affected 
            loop
                exitwhen this == 0
                set u  = .curr
                set nx = GetUnitX(u)
                set ny = GetUnitY(u)
                if IsUnitType(u, UNIT_TYPE_DEAD) then
                    call this.destroy()
                elseif not ApproxEqual(nx, .x) or not ApproxEqual(ny, .y) then
                    if (not IsUnitPaused(u)) and GetUnitAbilityLevel(u, 'BSTN') == 0 and GetUnitAbilityLevel(u, 'BPSE') == 0 then
                        set order = GetUnitCurrentOrder(u)
                        set dx = nx - .x
                        set dy = ny - .y
                        set d  = SquareRoot(dx * dx + dy * dy)
                        set dx = dx / d * .speed // move the unit offset-x by this
                        set dy = dy / d * .speed // move the unit offset-y by this
                       
                        if (order == 851986 or order == 851971) and /*
                        */ (this.ox - nx)*(this.ox - nx) < (dx*dx) and /*
                        */ (this.oy - ny)*(this.oy - ny) < (dy*dy) then
                            // if the unit is issued a move or smart order and they are near their destination
                            // then move them there instantly (removes a bit of glitchyness towards the end)
                            call SetUnitX(u, .ox)
                            call SetUnitY(u, .oy)
                            set .x = .ox
                            set .y = .oy
                            call IssueImmediateOrderById(u, 851972) // order them to stop
                        else
                            set .x = nx + dx
                            set .y = ny + dy
                            call SetUnitX(u, .x)
                            call SetUnitY(u, .y)
                        endif
                    endif
                endif
                set this = this.next
            endloop
            set u = null
        endmethod
       
        static method create takes unit whichUnit, real newSpeed returns thistype
            local thistype this = GetUnitUserData(whichUnit)
            set this.next = thistype(0).next
            set thistype(0).next.prev = this
            set thistype(0).next = this
            set this.prev  = 0
            set this.curr  = whichUnit
            set this.speed = (newSpeed - 522) * PERIOD
            set this.x     = GetUnitX(whichUnit)
            set this.y     = GetUnitY(whichUnit)
            set this.enabled = true
            return this
        endmethod
       
        static method update takes unit whichUnit, real newSpeed returns nothing
            local thistype this = GetUnitUserData(whichUnit)
            if this.enabled then
                if newSpeed > 522 then
                    set this.speed = (newSpeed - 522) * PERIOD
                else
                    call this.destroy()
                endif
            elseif newSpeed > 522 then
                call thistype.create(whichUnit, newSpeed)
            endif
        endmethod
       
        private static method storeOrderPoint takes nothing returns boolean
            local thistype this = GetUnitUserData(GetTriggerUnit())
            set this.ox = GetOrderPointX()
            set this.oy = GetOrderPointY()
            return false
        endmethod
   
        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), PERIOD, true, function thistype.periodic)
            call TriggerRegisterAnyUnitEventBJ(issued, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
            call TriggerAddCondition(issued, Condition(function thistype.storeOrderPoint))
        endmethod
    endmodule
   
    private struct MoveSpeedStruct extends array
        implement M
    endstruct
   
    function GetUnitMoveSpeedX takes unit whichUnit returns real
        if MoveSpeedStruct(GetUnitUserData(whichUnit)).enabled then
            return udg_UnitSpeedX[GetUnitUserData(whichUnit)]
        endif
        return GetUnitMoveSpeed(whichUnit)
    endfunction
   
    function SetUnitMoveSpeedX takes unit whichUnit, real newSpeed returns nothing
        call MoveSpeedStruct.update(whichUnit, newSpeed)
        set udg_UnitSpeedX[GetUnitUserData(whichUnit)] = newSpeed
    endfunction

    hook SetUnitMoveSpeed SetUnitMoveSpeedX
endlibrary

Please report any bugs.

Changelog:
1.0.0.0 - Initial release
1.0.0.1 - Minor changes to constants.
1.1.0.0 - No longer requires JASS for retrieving speeds. Updated to use a unit indexer for faster speeds. Also is less prone to glitchy movement towards the end.

Keywords:
move, speed, nitro, boost, movespeed, movespeedx, sprint, dash
Contents

MoveSpeedX GUI (Map)

Reviews
24th Nov 2011 Bribe: Approved and highly recommended.
Hi @PurgeandFire , thanks for this system, it's really useful for my map. But I have a few questions:
  • is this running at 0.03 seconds all the time? Or only when you call the function "SetUnitMoveSpeedX"?
  • If not: Does it recongize to automatically turn off as soon as no unit is above 522 speed?

This timer is running all the time. I should've optimized it to only run when needed, but it looks like I didn't. :( Sorry.
 
Level 6
Joined
Dec 29, 2019
Messages
82
Hey guys vJASS wasnt solution for me since my map runs on Lua, so i've implemented Lua version of this feature, logic is pretty much the same, there are only few differences, i am usin Trigger instead of Timer on periodic event, trigger is enabled only when needed, for some reason in original version it ran all the time, and i've hooked both natives SetUnitMoveSpeed and GetUnitMoveSpeed as well, thats probably even better for GUI creators. + Obvsly u dont need any extensions like Unit Indexer ect for usage anymore :), indexing is handled by native GetHandleIdBJ()

Lua:
----------------------------------------------------
---------------MOVESPEED SYSTEM SETUP---------------
----------------------------------------------------
do
    local function tableLength(t)
        local count = 0
        if t then
            for _ in pairs(t) do count = count + 1 end
        end
        return count
    end

    local MSX_PERIOD = 0.00625
    local MSX_MARGIN = 0.01

    local function ApproxEqual (A,B)
        return (A >= (B - MSX_MARGIN)) and (A <= (B + MSX_MARGIN))
    end

    local MOVESPEED_X_TABLE = {}
    local MOVESPEED_X_ISSUED_TRIGGER = CreateTrigger()
    local MOVESPEED_X_TRIGGER = CreateTrigger()
    local oldGetUnitMS = GetUnitMoveSpeed
    local oldSetUnitMS = SetUnitMoveSpeed

    local function MSX_Periodic()
        local u,order,d,dy,dx,ny,nx = nil,nil,nil,nil,nil,nil,nil
        for i,t in pairs(MOVESPEED_X_TABLE) do
            u = t.unit
            nx = GetUnitX(u)
            ny = GetUnitY(u)
            if not(IsUnitAliveBJ(u)) or GetUnitTypeId(u) == 0 then
                MOVESPEED_X_TABLE[i] = nil
            elseif not(ApproxEqual(nx, t.x)) or not(ApproxEqual(ny, t.y)) then
                if not(IsUnitPaused(u)) then
                    order = GetUnitCurrentOrder(u)
                    dx = nx - t.x
                    dy = ny - t.y
                    d  = SquareRoot(dx * dx + dy * dy)
                    dx = dx / d * t.speed
                    dy = dy / d * t.speed
                    if (order == 851986 or order == 851971) and (t.ox - nx)*(t.ox - nx) < (dx*dx) and (t.oy - ny)*(t.oy - ny) < (dy*dy) then
                        SetUnitX(u, t.ox)
                        SetUnitY(u, t.oy)
                        t.x = t.ox
                        t.y = t.oy
                        IssueImmediateOrderById(u, 851972)
                    else
                        t.x = nx + dx
                        t.y = ny + dy
                        SetUnitX(u, t.x)
                        SetUnitY(u, t.y)
                    end
                end
            end
        end
        if tableLength(MOVESPEED_X_TABLE) == 0 then
            DisableTrigger(MOVESPEED_X_TRIGGER)
        end
    end

    local function MSX_storeOrderPoint()
        local u_id = GetHandleIdBJ(GetTriggerUnit())
        if MOVESPEED_X_TABLE[u_id] then
            MOVESPEED_X_TABLE[u_id].ox = GetOrderPointX()
            MOVESPEED_X_TABLE[u_id].oy = GetOrderPointY()
        end
    end

    local function MSX_Create (whichUnit,newSpeed)
        local u_id = GetHandleIdBJ(whichUnit)
        MOVESPEED_X_TABLE[u_id] = MOVESPEED_X_TABLE[u_id] or {
            unit = whichUnit
            ,x = GetUnitX(whichUnit)
            ,y = GetUnitY(whichUnit)
        }
        MOVESPEED_X_TABLE[u_id].speed = (newSpeed - 522) * MSX_PERIOD
        if not(IsTriggerEnabled(MOVESPEED_X_TRIGGER)) then
            EnableTrigger(MOVESPEED_X_TRIGGER)
        end
    end

    local function MSX_Update (whichUnit,newSpeed)
        local u_id = GetHandleIdBJ(whichUnit)
        if newSpeed > 522 then
            MSX_Create(whichUnit,newSpeed)
        else
            MOVESPEED_X_TABLE[u_id] = nil
        end
    end

    function GetUnitMoveSpeed(whichUnit)
        local u_id = GetHandleIdBJ(whichUnit)
        if MOVESPEED_X_TABLE[u_id] then
            return (MOVESPEED_X_TABLE[u_id].speed / MSX_PERIOD) + 522
        end
        return oldGetUnitMS(whichUnit)
    end

    function SetUnitMoveSpeed(whichUnit,newSpeed)
        oldSetUnitMS(whichUnit, newSpeed)
        MSX_Update(whichUnit, newSpeed)
    end

    local oldInit = InitBlizzard
    function InitBlizzard()
        oldInit()

        TriggerRegisterTimerEventPeriodic(MOVESPEED_X_TRIGGER, MSX_PERIOD)
        TriggerAddAction(MOVESPEED_X_TRIGGER, MSX_Periodic)
        TriggerRegisterAnyUnitEventBJ(MOVESPEED_X_ISSUED_TRIGGER, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        TriggerAddAction(MOVESPEED_X_ISSUED_TRIGGER, MSX_storeOrderPoint)
    end
end

More info HERE
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Hey guys vJASS wasnt solution for me since my map runs on Lua, so i've implemented Lua version of this feature, logic is pretty much the same, there are only few differences, i am usin Trigger instead of Timer on periodic event, trigger is enabled only when needed, for some reason in original version it ran all the time, and i've hooked both natives SetUnitMoveSpeed and GetUnitMoveSpeed as well, thats probably even better for GUI creators. + Obvsly u dont need any extensions like Unit Indexer ect for usage anymore :), indexing is handled by native GetHandleIdBJ()

Code:
----------------------------------------------------
---------------MOVESPEED SYSTEM SETUP---------------
----------------------------------------------------

function tableLength(t)
    local count = 0
    if t then
        for _ in pairs(t) do count = count + 1 end
    end
    return count
end

MSX_PERIOD = 0.00625
MSX_MARGIN = 0.01

function ApproxEqual (A,B)
    return (A >= (B - MSX_MARGIN)) and (A <= (B + MSX_MARGIN))
end

MOVESPEED_X_TABLE = {}
MOVESPEED_X_ISSUED_TRIGGER = CreateTrigger()
MOVESPEED_X_TRIGGER = CreateTrigger()

function MSX_Initialize()
    TriggerRegisterTimerEventPeriodic(MOVESPEED_X_TRIGGER, MSX_PERIOD)
    TriggerAddAction(MOVESPEED_X_TRIGGER, MSX_Periodic)
    TriggerRegisterAnyUnitEventBJ(MOVESPEED_X_ISSUED_TRIGGER, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    TriggerAddAction(MOVESPEED_X_ISSUED_TRIGGER, MSX_storeOrderPoint)
end

function MSX_Periodic()
    local u,order,d,dy,dx,ny,nx = nil,nil,nil,nil,nil,nil,nil
    for i,t in pairs(MOVESPEED_X_TABLE) do
        u = t.unit
        nx = GetUnitX(u)
        ny = GetUnitY(u)
        if not(IsUnitAliveBJ(u)) or GetUnitTypeId(u) == 0 then
            MOVESPEED_X_TABLE[i] = nil
        elseif not(ApproxEqual(nx, t.x)) or not(ApproxEqual(ny, t.y)) then
            if not(IsUnitPaused(u)) then
                order = GetUnitCurrentOrder(u)
                dx = nx - t.x
                dy = ny - t.y
                d  = SquareRoot(dx * dx + dy * dy)
                dx = dx / d * t.speed
                dy = dy / d * t.speed
                if (order == 851986 or order == 851971) and (t.ox - nx)*(t.ox - nx) < (dx*dx) and (t.oy - ny)*(t.oy - ny) < (dy*dy) then
                    SetUnitX(u, t.ox)
                    SetUnitY(u, t.oy)
                    t.x = t.ox
                    t.y = t.oy
                    IssueImmediateOrderById(u, 851972)
                else
                    t.x = nx + dx
                    t.y = ny + dy
                    SetUnitX(u, t.x)
                    SetUnitY(u, t.y)
                end
            end
        end
    end
    if tableLength(MOVESPEED_X_TABLE) == 0 then
        if IsTriggerEnabled(MOVESPEED_X_TRIGGER) then
            DisableTrigger(MOVESPEED_X_TRIGGER)
        end
    end
end

function MSX_storeOrderPoint()
    local u_id = GetHandleIdBJ(GetTriggerUnit())
    if MOVESPEED_X_TABLE[u_id] then
        MOVESPEED_X_TABLE[u_id].ox = GetOrderPointX()
        MOVESPEED_X_TABLE[u_id].oy = GetOrderPointY()
    end
end

function MSX_Create (whichUnit,newSpeed)
    local u_id = GetHandleIdBJ(whichUnit)
    MOVESPEED_X_TABLE[u_id] = MOVESPEED_X_TABLE[u_id] or {
        unit = whichUnit
        ,x = GetUnitX(whichUnit)
        ,y = GetUnitY(whichUnit)
    }
    MOVESPEED_X_TABLE[u_id].speed = (newSpeed - 522) * MSX_PERIOD
    if not(IsTriggerEnabled(MOVESPEED_X_TRIGGER)) then
        EnableTrigger(MOVESPEED_X_TRIGGER)
    end
end

function MSX_Update (whichUnit,newSpeed)
    local u_id = GetHandleIdBJ(whichUnit)
    if newSpeed > 522 then
        MSX_Create(whichUnit,newSpeed)
    else
        MOVESPEED_X_TABLE[u_id] = nil
    end
end

oldGetUnitMS = GetUnitMoveSpeed

function GetUnitMoveSpeed(whichUnit)
    local u_id = GetHandleIdBJ(whichUnit)
    if MOVESPEED_X_TABLE[u_id] then
        return (MOVESPEED_X_TABLE[u_id].speed / MSX_PERIOD) + 522
    end
    return oldGetUnitMS(whichUnit)
end

oldSetUnitMS = SetUnitMoveSpeed

function SetUnitMoveSpeed(whichUnit,newSpeed)
    oldSetUnitMS(whichUnit, newSpeed)
    MSX_Update(whichUnit, newSpeed)
end
That's a great inclusion! Lua hooks are one of the main reasons I found Lua to be so useful and innovative. You don't realize just how limited JASS is until you realize how much more user-friendly Lua is.
 
I've done many tests with MoveSpeedX and found a way to make higher speeds more stable. With the attached test map I got 5 * 522 (5 times the normal max speed) to work reasonably well using a period of 0.02. It probably can go lower safely. The trick is to re-issue the order to the unit every so often so it re-calculates the way points. Also having a higher turn rate and/or air pathing helps a lot. Attached is a test map with my optimizations. Sonic's model is from here: Sonic - Chaos Realm
 

Attachments

  • (12)divideandconquer movespeed x test v2.w3m
    530.5 KB · Views: 3
Top