• 🏆 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] Solved! Looking for the cause of a freeze with this library

In my map I've isolated a freeze to a hero using a modified version of MoveSpeedX for GUI v1.1.0.0 and attacking another unit. By freeze I mean "this program has stopped responding" not bliz's crash handler. It freezes just as the hero lands an attack but not every time. I've had 2 freezes with a hero moving fast this way and 0 otherwise. Possibly only fatal hits trigger the freeze but that's not confirmed. If I were to guess it has something to do with the ticks and MAX_TICKS variables, since I added those to make fast movement smoother. Upon analyzing this I can probably optimize the GetClosestPathablePointX function to re-use a location but unless its leaking memory I don't think that's the cause. I thought a 2nd set of eyes might help me find something I missed.

P.S. Ignore the comments, they are not up to date

edit: Code has been revised see a few posts down

JASS:
//TESH.scrollpos=175
//TESH.alwaysfold=0
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.00125
        //  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.
    private constant real MAX_TICKS = 40.

private integer itemtypeid = 'moon'

private item itm=null

endglobals

private function GetClosestPathablePointX takes real x, real y returns location
    local real returnx
    local real returny
    if GetItemTypeId(itm)==0 then
            set itm = CreateItem( itemtypeid, 32256.,32256. )
            call SetItemVisible(itm,false)
    endif
    call SetItemPosition(itm,x,y)
    set returnx = GetItemX(itm)
    set returny = GetItemY(itm)
    call SetItemPosition(itm,32256.,32256.)
    return Location(returnx,returny)
endfunction
/*
************************************************************************************
*
*    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 issuedpoint = CreateTrigger()
        private static trigger issuedtarget = CreateTrigger()
      
        thistype next
        thistype prev
      
        boolean enabled
        unit curr
        real speed
        real x
        real y
        real ox
        real oy
    real ticks
    integer oi
    widget ot
      
        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
        local location loc=null
            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 and GetUnitCurrentOrder(u)== this.oi 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 this.ot != null then
                set this.ox = GetWidgetX(ot)
                set this.oy = GetWidgetY(ot)
            endif

                        if (this.oi == 851986 or this.oi == 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
            elseif ticks >=MAX_TICKS then
                set .ticks = 0
                set loc=GetClosestPathablePointX(nx+dx,ny+dy)
                            set .x = GetLocationX(loc)
                            set .y = GetLocationY(loc)
                if this.ot != null then
                call IssueTargetOrderById(u,oi,ot)
                else
                call IssuePointOrderById(u,oi,ox,oy)
                endif
                        else
                set .ticks = .ticks + 1
            //call BJDebugMsg(R2S(.ticks))
            //call BJDebugMsg(R2S(MAX_TICKS))
                set loc=GetClosestPathablePointX(nx+dx,ny+dy)
                            set .x = GetLocationX(loc)
                            set .y = GetLocationY(loc)
                            call SetUnitX(u, .x)
                            call SetUnitY(u, .y)
                        endif
                    endif
                endif
        call RemoveLocation(loc)
        set loc=null
                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()
        set this.ot = null
        set this.oi = GetUnitCurrentOrder(GetTriggerUnit())
            return false
        endmethod
  
        private static method storeOrderTarget takes nothing returns boolean
            local thistype this = GetUnitUserData(GetTriggerUnit())
        set this.ot = GetOrderTarget()
        set this.oi = GetUnitCurrentOrder(GetTriggerUnit())
            return false
        endmethod

        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), PERIOD, true, function thistype.periodic)
            call TriggerRegisterAnyUnitEventBJ(issuedpoint, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
            call TriggerAddCondition(issuedpoint, Condition(function thistype.storeOrderPoint))
            call TriggerRegisterAnyUnitEventBJ(issuedtarget, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
            call TriggerAddCondition(issuedtarget, Condition(function thistype.storeOrderTarget))
        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 SetUnitMoveSpeed(whichUnit,newSpeed)
        call MoveSpeedStruct.update(whichUnit, newSpeed)
        set udg_UnitSpeedX[GetUnitUserData(whichUnit)] = newSpeed
    endfunction
endlibrary
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,555
Ticks and MAX_TICKS should be Integers. You'll run into precision error when using Arithmetic on a Real which produces results like 39.9999 instead of 40.00.

Other than that, I don't see any issues.

Some random ideas:
1) You're creating an infinite loop of issued orders. The solution is to "turn off order events, issue new order, turn on order events".
2) A unit's Custom Value was set to a negative value (doubt this would crash).
3) A unit is being moved out of map bounds. SetUnitX/Y can be weird sometimes, I've noticed strange behavior on recent patches where the unit's "true" position does not reflect where it appears to be.

Hard to say.
 
Last edited:
Level 20
Joined
Feb 27, 2019
Messages
592
Level 19
Joined
Aug 16, 2007
Messages
881
JASS:
private function GetClosestPathablePointX takes real x, real y returns location
    local real returnx
    local real returny
    if GetItemTypeId(itm)==0 then
            set itm = CreateItem( itemtypeid, 32256.,32256. )
            call SetItemVisible(itm,false)
    endif
    call SetItemPosition(itm,x,y)
    set returnx = GetItemX(itm)
    set returny = GetItemY(itm)
    call SetItemPosition(itm,32256.,32256.)
    return Location(returnx,returny)
endfunction

My guess is that the item is created/moved outside of the map bounds, causing the crash/freeze.
 
I've deleted my additions to movespeedX 1 by 1 until only the base library is left, albeit with a low value for PERIOD

JASS:
//TESH.scrollpos=175
//TESH.alwaysfold=0
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.00125
        //  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

endlibrary

Does anyone have any more ideas?
 
Level 20
Joined
Feb 27, 2019
Messages
592
The explanation of the period makes me question which range of period should be used. On one point it says it can go down to 0.00125 but in another that 0.02 is good for very fast speeds... though I dont think the period alone is the issue. I use this system myself with 0.00625 as a period without any (to my knowledge, issues) so Id be interested to know which period actually makes sense to use.

I had an issue using this system before because it didnt appear that units that were removed from the game were de-indexed, I got some help to fix it and by checking UnitTypeId == 0 in conjunction with or checking if the unit was dead it worked fine. No freeze or crash though. The issue I was experiencing was that the movespeed was not applied at certain intervals. That thread is linked in the replies.
 
The explanation of the period makes me question which range of period should be used. On one point it says it can go down to 0.00125 but in another that 0.02 is good for very fast speeds... though I dont think the period alone is the issue. I use this system myself with 0.00625 as a period without any (to my knowledge, issues) so Id be interested to know which period actually makes sense to use.

I had an issue using this system before because it didnt appear that units that were removed from the game were de-indexed, I got some help to fix it and by checking UnitTypeId == 0 in conjunction with or checking if the unit was dead it worked fine. No freeze or crash though. The issue I was experiencing was that the movespeed was not applied at certain intervals. That thread is linked in the replies.
This thread right? I'll try it out [Solved] - Remove Unit issue with MoveSpeedX v1.1.0.0
 
Level 20
Joined
Feb 27, 2019
Messages
592
Last edited:
My latest movespeedX code is below, its still no good. I've also noticed that only fatal damage triggers the freeze. Therefore this trigger might be relevant since it triggers on fatal damage from the same hero.

JASS:
function DistanceBetweenPointsReal takes real XA,real YA,real XB,real YB returns real
    local real dx= XB - XA
    local real dy= YB - YA
    return SquareRoot(dx * dx + dy * dy)
endfunction

function Trig_Speedster_Hero_Dies_Bonus_Conditions takes nothing returns boolean
    if not UnitAlive(udg_speedster_unit) then
    return false
    endif
    if not IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) then
    return false
    endif
    if ( ( GetKillingUnit() == udg_speedster_unit ) ) then
        return true
    endif
    if DistanceBetweenPointsReal(GetUnitX(udg_speedster_unit) , GetUnitY(udg_speedster_unit) , GetUnitX(GetTriggerUnit()) , GetUnitY(GetTriggerUnit())) <= 2000.0 then
    return true
    endif
    return false
endfunction

function Trig_Speedster_Hero_Dies_Bonus_Actions takes nothing returns nothing
    call CriticalTextForPlayer(udg_speedster_unit , "Death Bonus!" , 1.5 , 0 , 255 , 0 , 255 , 0.019 , GetOwningPlayer(udg_speedster_unit))
    call BlzSetUnitWeaponRealField(udg_speedster_unit, UNIT_WEAPON_RF_ATTACK_BASE_COOLDOWN, 0, RMaxBJ(BlzGetUnitWeaponRealField(udg_speedster_unit, UNIT_WEAPON_RF_ATTACK_BASE_COOLDOWN, 0) - ( udg_as_value * 2 ), 0.01))
    call BlzSetUnitWeaponRealField(udg_speedster_unit, UNIT_WEAPON_RF_ATTACK_BASE_COOLDOWN, 1, RMaxBJ(BlzGetUnitWeaponRealField(udg_speedster_unit, UNIT_WEAPON_RF_ATTACK_BASE_COOLDOWN, 1) - ( udg_as_value * 2 ), 0.01))
    call UnitAddCooldownReductionFlat(udg_speedster_unit , udg_cdr_value * 2)
endfunction

//===========================================================================
function InitTrig_Speedster_Hero_Dies_Bonus takes nothing returns nothing
    set gg_trg_Speedster_Hero_Dies_Bonus=CreateTrigger()
    call DisableTrigger(gg_trg_Speedster_Hero_Dies_Bonus)
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Speedster_Hero_Dies_Bonus, EVENT_PLAYER_UNIT_DEATH)
    call TriggerAddCondition(gg_trg_Speedster_Hero_Dies_Bonus, Condition(function Trig_Speedster_Hero_Dies_Bonus_Conditions))
    call TriggerAddAction(gg_trg_Speedster_Hero_Dies_Bonus, function Trig_Speedster_Hero_Dies_Bonus_Actions)
endfunction

UnitAddCooldownReductionFlat is from here:


JASS:
//TESH.scrollpos=175
//TESH.alwaysfold=0
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.
    private constant integer MAX_TICKS = 1

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 issuedpoint = CreateTrigger()
        private static trigger issuedtarget = CreateTrigger()
        
        thistype next 
        thistype prev
        
        boolean enabled
        unit curr
        real speed
        real x
        real y
        real ox
        real oy
    integer ticks
    integer oi
    widget ot
        
        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) or GetUnitTypeId(u) == 0 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 and GetUnitCurrentOrder(u)== this.oi 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 this.ot != null then
                set this.ox = GetWidgetX(ot)
                set this.oy = GetWidgetY(ot)
            endif

                        if (this.oi == 851986 or this.oi == 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
            elseif ticks >=MAX_TICKS then
                set .ticks = 0
                            set .x = nx + dx
                            set .y = ny + dy
                            call SetUnitX(u, .x)
                            call SetUnitY(u, .y)
                if this.ot != null then
                call IssueTargetOrderById(u,oi,ot)
                else
                call IssuePointOrderById(u,oi,ox,oy)
                endif
                        else
                set .ticks = .ticks + 1
            //call BJDebugMsg(R2S(.ticks))
            //call BJDebugMsg(R2S(MAX_TICKS))
                            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()
        set this.ot = null
        set this.oi = GetUnitCurrentOrder(GetTriggerUnit())
            return false
        endmethod
    
        private static method storeOrderTarget takes nothing returns boolean
            local thistype this = GetUnitUserData(GetTriggerUnit())
        set this.ot = GetOrderTarget()
        set this.oi = GetUnitCurrentOrder(GetTriggerUnit())
            return false
        endmethod

        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), PERIOD, true, function thistype.periodic)
            call TriggerRegisterAnyUnitEventBJ(issuedpoint, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
            call TriggerAddCondition(issuedpoint, Condition(function thistype.storeOrderPoint))
            call TriggerRegisterAnyUnitEventBJ(issuedtarget, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
            call TriggerAddCondition(issuedtarget, Condition(function thistype.storeOrderTarget))
        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 SetUnitMoveSpeed(whichUnit,newSpeed)
        call MoveSpeedStruct.update(whichUnit, newSpeed)
        set udg_UnitSpeedX[GetUnitUserData(whichUnit)] = newSpeed
    endfunction
endlibrary
 
Removing the hero dies bonus trigger fixed it!

edit, I'm curious about this: was it single or multiplayer for your map?
The explanation of the period makes me question which range of period should be used. On one point it says it can go down to 0.00125 but in another that 0.02 is good for very fast speeds... though I dont think the period alone is the issue. I use this system myself with 0.00625 as a period without any (to my knowledge, issues) so Id be interested to know which period actually makes sense to use.
 
Last edited:
Top