Because there is no fast easy way to find out if a unit is moving, I decided to automate the process.
With this system, you add units to an array that has its units' positions updated periodically. Then all you have to do is use the IsUnitMoving() function to calculate whether or not it is moving.
Edit: I was playing around with this system and it seemed to have some major problems. I was hitting the op limit after a couple of minutes running the map. I've changed some stuff around and it seems to be working better now. I've also attached a new map. I made a hero with some skills that take advantage of the system.
------------
Skill 1: Flurry
Nothing original here. Its just a bladestorm with a lower duration. It does, however allow the hero to do damage while it is moving (unlike normal attacks) so it is an integral skill for this hero.
Skill 2: Meditation
This is a heal over time spell. You cast it once and it ticks every three seconds for 15 seconds. The amount of hitpoints healed each tick increases by 150% per tick if the hero is not moving. If the hero is moving the hp healed each tick does not increase.
Skill 3: Acrobatics (passive)
If the hero is moving and is damaged, the damage received will be reduced by a factor of how fast he is moving. If the hero is standing still and is damaged, he will take double damage. When the hero damages another unit he will deal bonus damage based on how much faster he is moving than the unit. So if you turn on Flurry and move around a stationary unit you will do much more damage than if you just stood and attacked it or didn't move when you Flurried. You'll take less damage too.
Skill 4: Retribution
Whenever you avoid damage because you are moving, that damage is stored, up to a limit of 2000. Retribution is essentially a blink strike that moves you instantly to a point and deals whatever damage you have currently stored to a small area around that point.
Edit 2: I made a few more changes. Last time I submitted I forgot to update the functions using custom values to store info. Also added a couple of functions.
Functions:
Hidden information:
JASS:
//functions that use custom value to store data for the systemfunctionIsUnitMovingCVtakesunitureturnsbooleanfunctionGetUnitMoveDistanceCVtakesunitureturnsrealfunctionRemoveMoveCheckFromUnitCVtakesunitureturnsnothingfunctionEnableUnitMovementCheckCVtakesunitureturnsnothingfunctionEnableUnitMovementCheckPermanentCVtakesunitureturnsnothingfunctionIUM_CreateUnitCVtakesplayerp, integerunitId, realx, realy, realface, booleanpermanentreturnsunit//functions that store data in gamecache insteadfunctionIsUnitMovingGCtakesunitureturnsbooleanfunctionGetUnitMoveDistanceGCtakesunitureturnsrealfunctionRemoveMoveCheckFromUnitGCtakesunitureturnsnothingfunctionEnableUnitMovementCheckGCtakesunitureturnsnothingfunctionEnableUnitMovementCheckPermanentGCtakesunitreturnsnothingfunctionIUM_CreateUnitGCtakesplayerp, integerunitId, realx, realy, realface, booleanpermanentreturnsunit
Here's the code for the system:
Hidden information:
//////////////////////////////////////////////////////////////////////////////////////
//""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""//
//" IsUnitMoving? "//
//" by wantok "//
//" "//
//" (Vexorian's JassHelper is required "//
//" as well as WEHelper or Grimoire) "//
//""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""//
//////////////////////////////////////////////////////////////////////////////////////
struct MovingData
unit master
real xnew
real xold
real ynew
real yold
trigger trig
triggeraction ac
endstruct
library a
//=======================================================================================
//These two constant functions determine how accurate the IsUnitMoving function is
constant function DistanceSensitivity takes nothing returns real
return 2. //determines how far the unit has to move in a certain amount of time to be considered moving.
endfunction
constant function TimeSensitivity takes nothing returns real
return .1 //determines how often a unit's x and y values are updated. Higher values mean less sensitivity.
endfunction
////////////////////////////////////////////////////////////////////////////////////////
// Selected Functions from Handle Vars by KaTTaNa //
// //
// (these may be removed if you are only going to use the custom value functions) //
// //
////////////////////////////////////////////////////////////////////////////////////////
function IUM_H2I takes handle h returns integer
return h
return 0
endfunction
function IUM_LocalVars takes nothing returns gamecache
return GameCache
endfunction
function IUM_SetHandleInt takes handle subject, string name, integer value returns nothing
if value==0 then
call FlushStoredInteger(IUM_LocalVars(),I2S(IUM_H2I(subject)),name)
else
call StoreInteger(IUM_LocalVars(), I2S(IUM_H2I(subject)), name, value)
endif
endfunction
function IUM_GetHandleInt takes handle subject, string name returns integer
return GetStoredInteger(IUM_LocalVars(), I2S(IUM_H2I(subject)), name)
endfunction
//======================================================================================
//This function works for both the gamecache and the custom value storage solutions.
//It periodically updates the x and y positions of every unit in the IsUnitMoving pool.
function MovingDataTimerCallback takes nothing returns nothing
local MovingData dat
local integer i = 0
loop
exitwhen i == MovingData_total
set dat = MovingData_ar[i]
if dat.master == null then
set MovingData_ar[i] = MovingData_ar[MovingData_total - 1]
set MovingData_total = MovingData_total - 1
call dat.destroy()
set i = i - 1
endif
set dat.xold = dat.xnew
set dat.yold = dat.ynew
set dat.xnew = GetUnitX(dat.master)
set dat.ynew = GetUnitY(dat.master)
set i = i + 1
endloop
if MovingData_total == 0 then
call PauseTimer(IsUnitMoving_Timer)
endif
endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
//The following functions use a unit's custom value to store information needed //
//to check if it is moving. If you need to use the custom value for some other //
//task, you must use the functions that follow this group instead. //
//The suffix 'CV' for Custom Value differentiates between the two sets of functions //
///////////////////////////////////////////////////////////////////////////////////////////////
//==================================================================================================
//This function returns whether or not the unit is moving. Changes to the DistanceSensitivity() function
//and the TimeSensitivity() function make this function more or less precise.
function IsUnitMovingCV takes unit u returns boolean
local MovingData dat
local integer i = GetUnitUserData(u)
if i > 0 then
set dat = i
else
return FALSE
endif
if SquareRoot((dat.xnew-dat.xold)*(dat.xnew-dat.xold)+(dat.ynew-dat.yold)*(dat.ynew-dat.yold)) > DistanceSensitivity() then
return TRUE
else
return FALSE
endif
endfunction
//=======================================================================================
//Returns the distance moved over a set period of time
function GetUnitMoveDistanceCV takes unit u returns real
local MovingData dat
local integer i = GetUnitUserData(u)
if i > 0 then
set dat = GetUnitUserData(u)
return SquareRoot((dat.xnew-dat.xold)*(dat.xnew-dat.xold)+(dat.ynew-dat.yold)*(dat.ynew-dat.yold))
else
return 0.
endif
endfunction
//====================================================================================
//This function is automatically called when a unit in the pool dies. It removes it from
//the pool and recycles the struct.
function RemoveMoveCheckFromDyingUnitCV takes nothing returns nothing
local MovingData dat
local unit u = GetTriggerUnit()
local trigger t = GetTriggeringTrigger()
call SetUnitUserData(u, -1)
set dat.master = null
call TriggerRemoveAction(t, dat.ac)
call DestroyTrigger(t)
set t = null
set u = null
endfunction
//=====================================================================================
//Use this function to manually remove a unit from the pool and recycle the struct.
function RemoveMoveCheckFromUnitCV takes unit u returns nothing
local MovingData dat = GetUnitUserData(u)
//======================================================================================
//Call the following function to add a unit to the IsUnitMoving pool. You cannot check if
//the unit is moving in the same trigger thread. The results of IsUnitMoving will not be
//accurate until the timer has expired at least once.
function EnableUnitMovementCheckCV takes unit u returns nothing
local MovingData dat = MovingData.create()
local trigger t = CreateTrigger()
local triggeraction ac
if MovingData_total == 0 then
call TimerStart(IsUnitMoving_Timer, TimeSensitivity(), TRUE, function MovingDataTimerCallback)
endif
set dat.master = u
set dat.xnew = GetUnitX(u)
set dat.xold = GetUnitX(u)
set dat.ynew = GetUnitY(u)
set dat.yold = GetUnitY(u)
call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_DEATH)
set ac = TriggerAddAction(t, function RemoveMoveCheckFromDyingUnitCV)
set dat.ac = ac
set dat.trig = t
call SetUnitUserData(u, dat)
set MovingData_ar[MovingData_total] = dat
set MovingData_total = MovingData_total + 1
set ac = null
set t = null
endfunction
//===========================================================================================
//Call this function if the unit you are adding it to is going to be ressurected. The struct
//will not be recycled when the unit dies unlike the function above. You will have to manually
//remove the unit from the pool using the RemoveMoveCheckFromUnitCV function.
function EnableUnitMovementCheckPermanentCV takes unit u returns nothing
local MovingData dat = MovingData.create()
if MovingData_total == 0 then
call TimerStart(IsUnitMoving_Timer, TimeSensitivity(), TRUE, function MovingDataTimerCallback)
endif
set dat.master = u
set dat.xnew = GetUnitX(u)
set dat.xold = GetUnitX(u)
set dat.ynew = GetUnitY(u)
set dat.yold = GetUnitY(u)
call SetUnitUserData(u, dat)
set MovingData_ar[MovingData_total] = dat
set MovingData_total = MovingData_total + 1
endfunction
//======================================================================================
//The following function creates a unit and adds it directly to the IsUnitMoving pool
function IUM_CreateUnitCV takes player p, integer unitId, real x, real y, real face, boolean permanent returns unit
local unit u = CreateUnit(p, unitId, x, y, face)
if permanent == TRUE then
call EnableUnitMovementCheckPermanentCV(u)
else
call EnableUnitMovementCheckCV(u)
endif
return u
endfunction
////////////////////////////////////////////////////////////////////////////////////////////
//The following functions use handle variables and gamecache to store the information //
//needed to check if a unit is moving. Use these functions if you are using a unit's //
//custom value for some other task. A 'GC' suffix is added to differentiate them from //
//their Custom Value counterparts. //
////////////////////////////////////////////////////////////////////////////////////////////
//=========================================================================================
//See matching functions above for explanation to the purpose of the following functions
function IsUnitMovingGC takes unit u returns boolean
local MovingData dat
local integer i = IUM_GetHandleInt(u, "MovingData")
if i > 0 then
set dat = i
if SquareRoot((dat.xnew-dat.xold)*(dat.xnew-dat.xold)+(dat.ynew-dat.yold)*(dat.ynew-dat.yold)) > DistanceSensitivity() then
return TRUE
else
return FALSE
endif
else
return FALSE
endif
endfunction
function GetUnitMoveDistanceGC takes unit u returns real
local MovingData dat
local integer i = IUM_GetHandleInt(u, "MovingData")
if i > 0 then
set dat = i
return SquareRoot((dat.xnew-dat.xold)*(dat.xnew-dat.xold)+(dat.ynew-dat.yold)*(dat.ynew-dat.yold))
else
return 0.
endif
endfunction
function RemoveMoveCheckFromDyingUnitGC takes nothing returns nothing
local MovingData dat
local unit u = GetTriggerUnit()
local trigger t = GetTriggeringTrigger()
set dat = IUM_GetHandleInt(u, "MovingData")
set dat.master = null
function EnableUnitMovementCheckGC takes unit u returns nothing
local MovingData dat = MovingData.create()
local trigger t = CreateTrigger()
if MovingData_total == 0 then
call TimerStart(IsUnitMoving_Timer, TimeSensitivity(), TRUE, function MovingDataTimerCallback)
endif
set dat.master = u
set dat.xnew = GetUnitX(u)
set dat.xold = GetUnitX(u)
set dat.ynew = GetUnitY(u)
set dat.yold = GetUnitY(u)
call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_DEATH)
set dat.ac = TriggerAddAction(t, function RemoveMoveCheckFromDyingUnitGC)
set dat.trig = t
call IUM_SetHandleInt(u, "MovingData", dat)
set MovingData_ar[MovingData_total] = dat
set MovingData_total = MovingData_total + 1
set t = null
endfunction
function EnableUnitMovementCheckPermanentGC takes unit u returns nothing
local MovingData dat = MovingData.create()
if MovingData_total == 0 then
call TimerStart(IsUnitMoving_Timer, TimeSensitivity(), TRUE, function MovingDataTimerCallback)
endif
set dat.master = u
set dat.xnew = GetUnitX(u)
set dat.xold = GetUnitX(u)
set dat.ynew = GetUnitY(u)
set dat.yold = GetUnitY(u)
call IUM_SetHandleInt(u, "MovingData", dat)
set MovingData_ar[MovingData_total] = dat
set MovingData_total = MovingData_total + 1
endfunction
function IUM_CreateUnitGC takes player p, integer unitId, real x, real y, real face, boolean permanent returns unit
local unit u = CreateUnit(p, unitId, x, y, face)
if permanent == TRUE then
call EnableUnitMovementCheckPermanentGC(u)
else
call EnableUnitMovementCheckGC(u)
endif
return u
endfunction
endlibrary
//============================================================================================
//This function initialized the gamecache at map initialization. It can be removed if you are
//only using the custom value functions.
function InitTrig_InitGameCache takes nothing returns nothing
call FlushGameCache(InitGameCache("cache.x"))
set GameCache=InitGameCache("cache.x")
endfunction
[/jass]
I'd appreciate any comments/criticism. I've also attached a simple map that makes use of the system.
I ran into a lot of problems with the system as it was. I was hitting the op limit on my timer callback function after a couple of minutes of running the map. I'm not exactly sure why, since all the function does is update unit x's and y's. I can only assume I was somehow continually adding structs for it to cycle through or not destroying them properly. I made some changes and I haven't hit the op limit now in my recent tests. Occasionally though, the map will come to a grinding halt for reasons I have yet to figure out.
Anyway, I also made a hero with a couple of skills that really take advantage of the system. See the original post for a description.
Very sweet job.
I agree, submit this, you will probally get a few complaints and critism on how to improve, improve and get it in the submissions section.
I did some more tweaking to the custom value functions and also added a new function that adds a unit to the pool permanently. (You can remove it manually still) The non permanent version automatically removes the unit when it dies. Use the permanant version on heros or other units that will be resurrected so you don't have to add the unit to the pool again. The CreateUnit function now lets you choose to add the unit permanently or not.
Updated the code in the original post, but not the map.
How is the lag on this system? I made one where there is a point variable assigned to each unit at all times, great performance but when I quit the map it takes 4-5 seconds before closing wc3.
I've always felt it's easier to store a unit's current position, run a timer for 0.01 seconds, check if the new position is different from the stored one, return as necessary. I think it'd be silly to have a rect per unit you want to check or whatever.