# [Trigger]Calculating Distance Traveled

Status
Not open for further replies.

#### Vegavak

Level 12

Is it possible to create a timer for distance traveled and convert (timer*movementspeed) into an integer?

Last edited:

#### aznricepuff

Level 11
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
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]
//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
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
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 )
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.

#### Vegavak

Level 12
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?

#### aznricepuff

Level 11
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
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]
//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 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
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
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
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
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
return ( data.destroyplease ) //will return true if it sucessfully sets up tracking to be destroyed
endfunction``````

#### Vegavak

Level 12
You are a saint. I will try this out and let you know how it performs.

Status
Not open for further replies.

Replies
24
Views
2K
Replies
3
Views
641
Replies
3
Views
644
Replies
5
Views
634
Replies
2
Views
746