• 🏆 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 (Lua)

This bundle is marked as awaiting update. A staff member has requested changes to it before it can be approved.
  • Like
Reactions: deepstrasz
Hey guys this is Lua solution for MoveSpeedX by PurgeandFire. He inveted really smart way of overhauling hardcoded maximum move speed, by changing X,Y coordinates.

Core of the system is just the same, i've implemented only few changes for optimizing purposes and to simplify GUI usage of this system.

There are no requirements for this system, unit indexing is now handled by natives, so there is no need for anything like unitindexer anymore.

I am using trigger instead of timer, thats just personal preference. Trigger is now enabled only when there are some units which have ms above 522, so only when it needs to be, in original version of this mod, it ran all the time for some reason.

There are 2 native functions hooked, SetUnitMoveSpeed and GetUnitMoveSpeed as well which is even better for GUI creators. So real value of Current Unit MoveSpeed in GUI should return correct values now.

There are no GUI global variables included everything necessary is handled by script itself.

Make sure your map is Lua scripted, which u can change in Map Properties (under Script Language).


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
Contents

MoveSpeedX GUI (Map)

Reviews
Wrda
You don't need the function tableLength to get the length of the table, you can simply use # and then following the table name. E.g: print(#t) local MOVESPEED_X_TABLE = {} local MOVESPEED_X_ISSUED_TRIGGER = CreateTrigger() local...

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
You need to observe proper encapsulation for some of your functions (make them local). It's also better to automate the running of the initialization function. You can use [Lua] Global Initialization, or if you want no extra requirements you can simply change MSX_Initialize() into:
Lua:
do
    ... -- rest of your code

    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
 
Last edited:
Level 6
Joined
Dec 29, 2019
Messages
82
You need to observe proper encapsulation for some of your functions (make them local). It's also better to automate the running of the initialization function. You can use [Lua] Global Initialization, or if you want no extra requirements you can simply change MSX_Initialize() into:
Lua:
...

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
What is the point of making them local if i may ask ?
 
Level 6
Joined
Dec 29, 2019
Messages
82
To make functions not meant to be used by users totally out of their reach, such as those with MSX_ prefix (and others, only expose the functions meant to be really used, in this case the Set/GetUnitMoveSpeed).

Oh i am steal learning lua properly, wasn't sure about some scoping essentials. So when i include the code into "do end" scope and mark "private" functions (not accesible outside of the scope) with "local" keyword it means they wont be usable in other scripts right ? Is this only for access purposes or does it even make memory more optimized ? Like making functions local means memory dont have to keep them stored in global vars ? Not sure how debugger works in here exactly.
 
Level 6
Joined
Dec 29, 2019
Messages
82
Yeah it is only for access purposes. I'm not very familiar with the internal workings of the lua interpreter either, but in this case I'm pretty sure it doesn't affect memory usage in a significant sense (if it even does).

Thanks for explanation, i've edited code in the map + description and hooked native Init function as well (nice advice +rep :))
 
Hi. Do you have a version of this which works for patches below 1.32

You need JNGP or SharpCraft if you want to use it below the 1.31 patch since it is written in vJass.
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
You don't need the function tableLength to get the length of the table, you can simply use # and then following the table name. E.g: print(#t)
Lua:
    local MOVESPEED_X_TABLE = {}
    local MOVESPEED_X_ISSUED_TRIGGER = CreateTrigger()
    local MOVESPEED_X_TRIGGER = CreateTrigger()
I don't remember but maybe the standard conventions of lua in submissions weren't set at the time, but some of them are brought from the vJASS ones. These aren't constants, thus they shouldn't be all capitalized.
You don't need to nil all those local variables at start of MSX_Periodic function, that's their default type.
Note that since you are setting functions as local, you don't even need the prefix "MSX_" (you're not obligated to remove either, of course).
Lua is still being explored due to some special cases that were not needed to take into account in vJASS yet in Lua it's a different story. That being said, what I mean is that looping using pairs the way you're doing can lead to desyncs. More information on this subject here:
In the second link, in Lua fundamentals > tables

The solution would be to make MOVESPEED_X_TABLE a synced table, shown in this resource:

I believe your resource would take benefit from it. This resource already works; however, it has that false sense of security with pairs. I'm looking forward to seeing more lua versions of vJASS resources!
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
This reminds me, a month ago I was modding this resource to "modernize" its Lua utility, as well as trying to fix the glitch where people were getting stuck orbiting one spot for a while when their movement speed was too fast.

Here is the script for reference:

Lua:
OnInit(function(require)
    
    ---@diagnostic disable: cast-local-type, param-type-mismatch, assign-type-mismatch

    local echo = require "Timed.echo" --https://github.com/BribeFromTheHive/Lua-Core/blob/main/Timed.lua

    ----------------------------------------------------
    ---------------MOVESPEED SYSTEM CONFIG--------------
    ----------------------------------------------------
    local _CHECK_PERIOD = 0.03125

    local abs, atan, deg, sqrt = math.abs, math.atan, math.deg, math.sqrt

    ---@alias MoveSpeedX {x: number, y: number, extraSpeed: number, remove: fun()}

    ---@type { [unit]: MoveSpeedX }
    local listed
    
    local oldSums = SetUnitMoveSpeed
    function SetUnitMoveSpeed(u, speed)
        oldSums(u, speed)
        if speed > 522 then
            if not listed then
                listed = {}
                local trig = CreateTrigger()
                TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
                TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
                TriggerAddCondition(trig, Filter(function()
                    local tu = GetTriggerUnit()
                    if listed[tu] then
                        listed[tu].x = GetOrderPointX()
                        listed[tu].y = GetOrderPointY()
                    end
                end))
                trig = CreateTrigger()
                TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_ORDER)
                TriggerAddCondition(trig, Filter(function()
                    local tu = GetTriggerUnit()
                    if listed[tu] then
                        listed[tu].x = nil
                        listed[tu].y = nil
                    end
                end))
            end

            if listed[u] then
                listed[u].remove() --remove the original callback
            end

            local t = {} ---@type MoveSpeedX
            listed[u] = t
            speed = speed - 512
            t.extraSpeed = speed
            local period = 1 / (speed / 50) * _CHECK_PERIOD
            speed = speed * period
            local lastX = GetUnitX(u)
            local lastY = GetUnitY(u)
            --local lastFace = GetUnitFacing(u)

            listed[u].remove = echo(period, function()
                if GetUnitTypeId(u) > 0 then
                    local newX = GetUnitX(u)
                    local newY = GetUnitY(u)
                    --local face = GetUnitFacing(u)
                    if --[[(abs(face - lastFace) < 2.5) and]] ((abs(newX - lastX) > period) or (abs(newY - lastY) > period)) and not IsUnitPaused(u) then
                        local deltaX = newX - lastX
                        local deltaY = newY - lastY
                        local distance  = sqrt(deltaX * deltaX + deltaY * deltaY)
                        --face = deg(atan(deltaY, deltaX))
                        --BlzSetUnitFacingEx(u, face)
                        local nextX = deltaX / distance * speed
                        local nextY = deltaY / distance * speed
                        
                        newX = newX + nextX
                        newY = newY + nextY

                        SetUnitX(u, newX)
                        SetUnitY(u, newY)
                    end
                    lastX = newX
                    lastY = newY
                    --lastFace = face
                else
                    listed[u] = nil
                    return true --stop echoing
                end
            end)
        elseif listed and listed[u] then
            listed[u].remove()
            listed[u] = nil
        end
    end

    local oldGums = GetUnitMoveSpeed
    function GetUnitMoveSpeed(whichUnit)
        if listed and listed[whichUnit] then
            return (listed[whichUnit].extraSpeed / _CHECK_PERIOD) + 522
        end
        return oldGums(whichUnit)
    end
end)

I've also attached the map that I was tinkering with. Basically, the movement speed adjustments are synced to a really really crazy fast timer, so it tends to not push a unit over its "checkpoint". I have tested it with speed 3000 and I haven't witnessed any orbiting. It's not been very thoroughly tested, though, and is sure to be a performance hog with timers running at that frequency.
 

Attachments

  • MoveSpeedXGUI.w3x
    41.2 KB · Views: 3

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
This reminds me, a month ago I was modding this resource to "modernize" its Lua utility, as well as trying to fix the glitch where people were getting stuck orbiting one spot for a while when their movement speed was too fast.

Here is the script for reference:

Lua:
OnInit(function(require)
   
    ---@diagnostic disable: cast-local-type, param-type-mismatch, assign-type-mismatch

    local echo = require "Timed.echo" --https://github.com/BribeFromTheHive/Lua-Core/blob/main/Timed.lua

    ----------------------------------------------------
    ---------------MOVESPEED SYSTEM CONFIG--------------
    ----------------------------------------------------
    local _CHECK_PERIOD = 0.03125

    local abs, atan, deg, sqrt = math.abs, math.atan, math.deg, math.sqrt

    ---@alias MoveSpeedX {x: number, y: number, extraSpeed: number, remove: fun()}

    ---@type { [unit]: MoveSpeedX }
    local listed
   
    local oldSums = SetUnitMoveSpeed
    function SetUnitMoveSpeed(u, speed)
        oldSums(u, speed)
        if speed > 522 then
            if not listed then
                listed = {}
                local trig = CreateTrigger()
                TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
                TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
                TriggerAddCondition(trig, Filter(function()
                    local tu = GetTriggerUnit()
                    if listed[tu] then
                        listed[tu].x = GetOrderPointX()
                        listed[tu].y = GetOrderPointY()
                    end
                end))
                trig = CreateTrigger()
                TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_ORDER)
                TriggerAddCondition(trig, Filter(function()
                    local tu = GetTriggerUnit()
                    if listed[tu] then
                        listed[tu].x = nil
                        listed[tu].y = nil
                    end
                end))
            end

            if listed[u] then
                listed[u].remove() --remove the original callback
            end

            local t = {} ---@type MoveSpeedX
            listed[u] = t
            speed = speed - 512
            t.extraSpeed = speed
            local period = 1 / (speed / 50) * _CHECK_PERIOD
            speed = speed * period
            local lastX = GetUnitX(u)
            local lastY = GetUnitY(u)
            --local lastFace = GetUnitFacing(u)

            listed[u].remove = echo(period, function()
                if GetUnitTypeId(u) > 0 then
                    local newX = GetUnitX(u)
                    local newY = GetUnitY(u)
                    --local face = GetUnitFacing(u)
                    if --[[(abs(face - lastFace) < 2.5) and]] ((abs(newX - lastX) > period) or (abs(newY - lastY) > period)) and not IsUnitPaused(u) then
                        local deltaX = newX - lastX
                        local deltaY = newY - lastY
                        local distance  = sqrt(deltaX * deltaX + deltaY * deltaY)
                        --face = deg(atan(deltaY, deltaX))
                        --BlzSetUnitFacingEx(u, face)
                        local nextX = deltaX / distance * speed
                        local nextY = deltaY / distance * speed
                       
                        newX = newX + nextX
                        newY = newY + nextY

                        SetUnitX(u, newX)
                        SetUnitY(u, newY)
                    end
                    lastX = newX
                    lastY = newY
                    --lastFace = face
                else
                    listed[u] = nil
                    return true --stop echoing
                end
            end)
        elseif listed and listed[u] then
            listed[u].remove()
            listed[u] = nil
        end
    end

    local oldGums = GetUnitMoveSpeed
    function GetUnitMoveSpeed(whichUnit)
        if listed and listed[whichUnit] then
            return (listed[whichUnit].extraSpeed / _CHECK_PERIOD) + 522
        end
        return oldGums(whichUnit)
    end
end)

I've also attached the map that I was tinkering with. Basically, the movement speed adjustments are synced to a really really crazy fast timer, so it tends to not push a unit over its "checkpoint". I have tested it with speed 3000 and I haven't witnessed any orbiting. It's not been very thoroughly tested, though, and is sure to be a performance hog with timers running at that frequency.
I confirm that there's no more "orbiting".
There's just one annoying thing, and that's the unit facing after it reaches its destiny. If you order the death knight to move in an angle of 310~360 and reaches its destiny, it faces somewhat to the opposite angle.

The long range issued orders are a bit of a problem since sometimes the units decide to stutter for a split second and then continue the original path. I don't think there's anything to do against that nonetheless.
 
Top