• 🏆 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!
  • ✅ The POLL for Hive's Texturing Contest #33 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!

[Trigger] Calculating Distance Traveled

Status
Not open for further replies.
Level 12
Joined
Mar 16, 2006
Messages
992
Is there any easy way about this?

Is it possible to create a timer for distance traveled and convert (timer*movementspeed) into an integer?
 
Last edited:
Level 11
Joined
Feb 22, 2006
Messages
752
Im assuming you are tracking distance traveled (not displacement) of a unit. Make a timer that expires every 0.05 seconds. Every time the timer expires, have it check the coordinates of the unit, calculate displacement from the last checked coordinates (0.05 seconds ago), add that displacement to a variable that stores your total distance, and save the coordinates somehow so you can check it when the timer expires again 0.05 seconds later. Anytime you want to check total distance traveled, just reference your total distance variable.

Something like this:

JASS:
function Displacement_Calculate takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = GetHandleUnit( t, "u" )
    local real x = GetHandleReal( t, "x" )
    local real y = GetHandleReal( t, "y" )
    local real newx = GetUnitX( u )
    local real newy = GetUnitY( u )
    local real dx = newx - x
    local real dy = newy - y
    set udg_distancetraveled = udg_distancetraveled + SquareRoot( dx * dx + dy * dy ) //or some other variable, could be attached with gamecache to timer or unit...w/e
    call SetHandleReal( t, "x", newx )
    call SetHandleReal( t, "y", newy )
    set t = null
    set u = null
endfunction

function Distance_Timer takes unit u returns nothing
    local timer t = CreateTimer()
    call TimerStart( t, 0.05, true, function Displacement_Calculate )
    call SetHandleHandle( t, "u", u )
    call SetHandleReal( t, "x", GetUnitX( u ) )
    call SetHandleReal( t, "y", GetUnitY( u ) )
    set t = null
endfunction


The problem is that it isn't very efficient, mostly because it uses gamecache to attach variables to the timer, and also because you create one timer that expires every 0.05 seconds for every unit you want to track. To fix these problems, you can use vJass (which requires you have Jasshelper; easiest way to get it is with Jass NewGen pack).

Then the script becomes something like:

JASS:
struct distancedata
    unit u
    real x
    real y
    real d = 0.00
    boolean destroyplease = false
endstruct

globals
    timer distancetimer = CreateTimer()
    distancedata array distancearray
    integer distancetotal = 0
endglobals

function Distance_Update takes nothing returns nothing
    local distancedata data
    local real newx
    local real newy
    local real dx
    local real dy
    local integer counter = 0
    loop
        exitwhen ( counter >= distancetotal )
        set data = distancearray[counter]
        if ( data.destroyplease ) then
            //struct (tracking) is slated for destruction, so rearrange array members so you don't get gaps in the array and then destroy struct
            set distancearray[counter] = distancearray[distancetotal - 1]
            set distancetotal = distancetotal - 1
            call data.destroy()
            set counter = counter - 1 //because of the way we rearranged the array members, we need to make sure ALL members get checked
        else
            set newx = GetUnitX( data.u )
            set newy = GetUnitY( data.u )
            set dx = newx - data.x
            set dy = newy - data.y
            set data.d = data.d + SquareRoot( dx * dx + dy * dy ) //update distance
            //update your unit coordinates
            set data.x = newx
            set data.y = newy
        endif
        set counter = counter + 1
    endloop
    if ( distancetotal == 0 ) then
        //if no units are being tracked, running timer is useless and wastes memory, so pause timer
        call PauseTimer( distancetimer )
    endif
endfunction

function Distance_AddUnitTracking takes unit u returns nothing //call this function to add unit tracking
    local distancedata data = distancedata.create()
    set data.u = u
    set data.x = GetUnitX( u )
    set data.y = GetUnitY( u )
    call SetUnitUserData( u, data ) //since structs are integer arrays, we can save them in unit's Custom Value
    if ( distancetotal == 0 ) then
        call TimerStart( distancetimer, 0.04, true, function Distance_Update )
        //if there were no other units being tracked (i.e. timer was paused), start the timer
    endif
    //update your array variables
    set distancearray[distancetotal] = data
    set distancetotal = distancetotal + 1
endfunction

function GetDistanceTraveled takes unit u returns real //use this function to get the distance traveled
    local distancedata data = GetUnitUserData( u )
    return data.d
endfunction

function Distance_ResetTracking takes unit u returns boolean //use this function to reset a unit's tracking (setting distance to 0); you can achieve same effect by destroying tracking and adding another one but this way is more efficient
    local distancedata data = GetUnitUserData( u )
    set data.d = 0.00
    return ( data.d == 0.00 ) //will return true if it successfully resets distance
endfunction

function Distance_DestroyTracking takes unit u returns boolean //call this if you want to disable tracking, this is strongly recommended as it will free up memory
    local distancedata data = GetUnitUserData( u )
    set data.destroyplease = true
    return data.destroyplease //will return true if it sucessfully sets up tracking to be destroyed
endfunction

It looks longer and more complex, but in reality most of the extra stuff is just setup (defining struct and globals) and array management.

The above system would require you not save anything else to units' Custom Datas. The advantage is that it is faster, more efficient, and also has the potential to be more accurate since we can use lower expiration times for the timer without having to worry as much about lag.
 
Level 12
Joined
Mar 16, 2006
Messages
992
Wow, thank you.

Could this be turned on/off or used for multiple players? And what would I need to get the totaldistance value/clear it?

Would I be able to assign this to an individual unit? If so, how?
 
Level 11
Joined
Feb 22, 2006
Messages
752
There is no need to manually turn the system on or off. If you have no unit tracking, the timer stays paused and uses up minimal memory (the only memory required is to store the timer itself). The moment you add one unit tracking, timer switches on and does its stuff. The moment all tracking is destroyed, the timer pauses itself again.

Yes it can be used on multiple players; this is fully MUI.

To get the total distance value, use the function GetDistanceTraveled(). To destroy the tracking and free up memory call Distance_DestroyTracking(). To reset distance traveled to 0, call Distance_ResetTracking() - this will reset total distance traveled for that unit to 0, but the tracking will still be there and will still update.

This system ONLY works on individual units, because that is how i set it up. If you wanted to add tracking to every unit in your map, you would have to assign tracking individually to every unit (though I discourage this because it gets laggy if it tracks too many units). The function Distance_AddUnitTracking() takes a unit as an argument and once you call it the unit you specify will have its distance moved tracked. It will continue to have the distance tracked until you call Distance_DestroyTracking() on that unit.

I've made some updates to the script that I probably should have added in the first place. Read the comments in the script - they give you information as to what is being done.

JASS:
struct distancedata
    unit u
    real x
    real y
    real d = 0.00
    real destroydelay = 0.00
    boolean dead = false
    boolean destroyplease = false
    method onDestroy takes nothing returns nothing
        call SetUnitUserData( .u, 0 )
    endmethod
endstruct
globals
    timer distancetimer = CreateTimer()
    distancedata array distancearray
    integer distancetotal = 0
endglobals
function Distance_Update takes nothing returns nothing
    local distancedata data
    local real newx
    local real newy
    local real dx
    local real dy
    local integer counter = 0
    loop
        exitwhen ( counter >= distancetotal )
        set data = distancearray[counter]
        if ( data.destroyplease ) then
            //struct (tracking) is slated for destruction, so rearrange array members so you don't get gaps in the array and then destroy struct
            set distancearray[counter] = distancearray[distancetotal - 1]
            set distancetotal = distancetotal - 1
            call data.destroy()
            set counter = counter - 1 //because of the way we rearranged the array members, we need to make sure ALL members get checked
        else
            if ( data.dead ) then
                //if unit is dead, we check to see if delay time is 0; if it is we slate struct for destruction, else we countdown the delay
                if ( data.destroydelay <= 0.00 ) then
                    set data.destroyplease = true
                else
                    set data.destroydelay = data.destroydelay - 0.04
                endif
            else
                set newx = GetUnitX( data.u )
                set newy = GetUnitY( data.u )
                set dx = newx - data.x
                set dy = newy - data.y
                set data.d = data.d + SquareRoot( dx * dx + dy * dy ) //update distance
                //update your unit coordinates
                set data.x = newx
                set data.y = newy
                if ( GetWidgetLife( data.u ) < 0.405 ) then
                    //if unit is dead, it obviously can't move anymore, so we slate the struct (tracking) for destruction after the delay time
                    set data.dead = true
                endif
            endif
        endif
        set counter = counter + 1
    endloop
    if ( distancetotal == 0 ) then
        //if no units are being tracked, running timer is useless and wastes memory, so pause timer
        call PauseTimer( distancetimer )
    endif
endfunction
function Distance_AddUnitTracking takes unit u, real destroydelay returns nothing //call this function to add unit tracking
    //unit u is the unit to add the tracking to; real destroydelay is the time (in seconds) after the unit dies that the tracking is automatically destroyed
    local distancedata data = distancedata.create()
    set data.u = u
    set data.x = GetUnitX( u )
    set data.y = GetUnitY( u )
    set data.destroydelay = destroydelay
    call SetUnitUserData( u, data ) //since structs are integer arrays, we can save them in unit's Custom Value
    if ( distancetotal == 0 ) then
        call TimerStart( distancetimer, 0.04, true, function Distance_Update )
        //if there were no other units being tracked (i.e. timer was paused), start the timer
    endif
    //update your array variables
    set distancearray[distancetotal] = data
    set distancetotal = distancetotal + 1
endfunction
function GetDistanceTraveled takes unit u returns real //use this function to get the distance traveled
    local distancedata data = GetUnitUserData( u )
    return data.d
endfunction
function Distance_ResetTracking takes unit u returns boolean //use this function to reset a unit's tracking (setting distance to 0); you can achieve same effect by destroying tracking and adding another one but this way is more efficient
    local distancedata data = GetUnitUserData( u )
    if ( data == 0 ) then
        return false //if no tracking exists on that unit, return false
    endif
    set data.d = 0.00
    return ( data.d == 0.00 ) //will return true if it successfully resets distance
endfunction
function Distance_DestroyTracking takes unit u returns boolean //call this if you want to disable tracking, this is strongly recommended as it will free up memory
    local distancedata data = GetUnitUserData( u )
    if ( data == 0 ) then
        return false //if no tracking exists on that unit, return false
    endif
    set data.destroyplease = true
    return ( data.destroyplease ) //will return true if it sucessfully sets up tracking to be destroyed
endfunction
 
Status
Not open for further replies.
Top