• 🏆 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!

[System] Is Unit Moving

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
JASS:
library IsUnitMoving requires optional UnitIndexer, optional AIDS, Event
//===========================================================================
// IsUnitMoving
// ============ Created by Xiliger with some script improvements by Bribe
//
//
// This library provides a helpful function that can be used to detect when
// a unit is moving.
//
// API:
//
//     function IsUnitMoving takes unit u returns boolean
//     function GetMovingUnit takes nothing returns unit
//     function GetMovingUnitId takes nothing returns integer
//
// Struct API:
//
//     static constant Event MOVE
//     > Fires when a unit starts moving.
//
//     static constant Event STOP
//     > Fires when a unit stops moving.
//
//     readonly boolean moving
//     > Quick-check if a unit is moving.
//
//     readonly boolean allocated
//     > Was the unit allocated in the first place?
//
//     readonly real x
//     > Unit's last-checked x
//
//     readonly real y
//     > Unit's last-checked y
//
// Requires either:
//     UnitIndexer: hiveworkshop.com/forums/showthread.php?t=172090
// or:
//     AIDS:  thehelper.net/forums/showthread.php?t=130752
//     Event: thehelper.net/forums/showthread.php?t=126846
//
// Thanks to Jesus4Lyf for the extremely efficient Timer32 linked-list model
// and to Nestharus for ideas that give IsUnitMoving more power.
//
globals
    //-----------------------------------------------------------------------
    // Time between coordinate scans. You should increase it if you have move-
    // ment-oriented spells/systems with slower timers.
    //
    private constant real TIMEOUT = 0.03125

    private UnitMoving get = 0 // Reference
endglobals

private function GetUnit takes integer id returns unit
    static if LIBRARY_UnitIndexer then
        return GetUnitById(id)
    elseif LIBRARY_AIDS then
        return GetIndexUnit(id)
    endif
endfunction

private function GetNew takes nothing returns integer
    static if LIBRARY_UnitIndexer then
        return GetIndexedUnitId()
    elseif LIBRARY_AIDS then
        return AIDS_GetIndexOfEnteringUnit()
    endif
endfunction

private function GetOld takes nothing returns integer
    static if LIBRARY_UnitIndexer then
        return GetIndexedUnitId()
    elseif LIBRARY_AIDS then
        return AIDS_GetDecayingIndex()
    endif
endfunction

//***************************************************************************
//*
//*  Users' API
//*
//***************************************************************************

//===========================================================================
// Returns true when a unit is moving via orders or triggered actions, false
// if the unit is not moving.
//
function IsUnitMoving takes unit u returns boolean
    return UnitMoving(GetUnitUserData(u)).moving
endfunction

// Use within a registered event-response to get the moving or stopping unit.
function GetMovingUnit takes nothing returns unit
    return GetUnit(get)
endfunction

function GetMovingUnitId takes nothing returns UnitMoving
    return get
endfunction

//***************************************************************************
//*
//*  System Struct
//*
//***************************************************************************

private module Init
    private static method onInit takes nothing returns nothing
        set thistype.MOVE = Event.create()
        set thistype.STOP = Event.create()

        static if LIBRARY_UnitIndexer then
            call RegisterUnitIndexEvent(Filter(function thistype.index), UnitIndexer.INDEX)
            call RegisterUnitIndexEvent(Filter(function thistype.deindex), UnitIndexer.DEINDEX)
        elseif LIBRARY_AIDS then
            call AIDS_RegisterOnEnter(Filter(function thistype.index))
            call AIDS_RegisterOnDeallocate(Filter(function thistype.deindex))
        endif

        call TimerStart(CreateTimer(), TIMEOUT, true, function thistype.scan)
    endmethod
endmodule

struct UnitMoving extends array

    readonly static Event MOVE = 0
    readonly static Event STOP = 0

    readonly real x
    readonly real y

    readonly boolean moving
    readonly boolean allocated

    readonly thistype next
    readonly thistype prev

    private static method index takes nothing returns boolean
        local thistype u = GetNew()
        if 0 != GetUnitAbilityLevel(GetUnit(u), 'Amov') then
            set thistype(0).next.prev = u
            set u.next = thistype(0).next
            set thistype(0).next = u
            set u.x = GetUnitX(GetUnit(u))
            set u.y = GetUnitY(GetUnit(u))
            set u.allocated = true
        endif
        return false
    endmethod

    private static method deindex takes nothing returns boolean
        local thistype u = GetOld()
        if u.allocated then
            set u.moving = false
            set u.allocated = false
            set u.prev.next = u.next
            set u.next.prev = u.prev
            set u.prev = 0
        endif
        return false
    endmethod

    private static method scan takes nothing returns nothing
        local thistype u = thistype(0).next
        local real x
        local real y
        loop
            exitwhen 0 == u
            set x = GetUnitX(GetUnit(u))
            set y = GetUnitY(GetUnit(u))
            if x != u.x or y != u.y then
                set u.x = x
                set u.y = y
                if not u.moving then
                    // The unit was stopped but is now moving.
                    set u.moving = true
                    set get = u
                    call thistype.MOVE.fire()
                endif
            elseif u.moving then
                // The unit was moving but is now stopped.
                set u.moving = false
                set get = u
                call thistype.STOP.fire()
            endif
            set u = u.next
        endloop
    endmethod

    implement Init

endstruct
endlibrary

Example usage:

JASS:
library Motion initializer init requires IsUnitMoving

    private function onMove takes nothing returns boolean
        call BJDebugMsg(GetUnitName(GetMovingUnit()) + " has started moving!")
        return false
    endfunction

    private function onMove takes nothing returns boolean
        call BJDebugMsg(GetUnitName(GetMovingUnit()) + " has stopped moving!")
        return false
    endfunction

    private function init takes nothing returns nothing
        call UnitMoving.MOVE.register(Filter(function onMove))
        call UnitMoving.STOP.register(Filter(function onStop))
    endfunction

endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
The whole concept was done by Xiliger, just did some work through the code a bit and made some support for unit-indexing libraries.

I like the idea of stop/move events, so I have applied that functionality. That also gives more purpose to this library, something I value.

Event, by Jesus4Lyf, is an optional requirement that enables the functions:

TriggerRegisterMoveEvent.
TriggerRegisterStopEvent.

Thanks for another cool idea :)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
No problem, thanks for the OK to upload it.

Nestharus, I got rid of the requirement for Event. I think it's more efficient and makes things a lot less complicated.

The functions:

TriggerRegisterMoveEvent takes trigger t
TriggerRegisterStopEvent takes trigger t

Have been replaced by:

AddOnMoveFilter takes boolexpr b
AddOnStopFilter takes boolexpr b
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
OK, I have added that mess to the API.

I think the old function names AddOnMove/StopFilter are easy to forget and easy to mistake for something they are not.

The names of the boolexpr registry functions are now OnUnitStop and OnUnitMove. Those are the permanent names and I refuse to change them again.

The original filters didn't have a way to retrieve the moving or stopped unit. I have added GetMovingUnit to retrieve the unit that moved.

IsMovementTrue detects if a unit is moving because it was issued an order to do so.
IsMovementFalse detects if the movement was triggered. You can't just do not IsMovementTrue because the unit may well be not moving at all.

I am not sure if it is faster to inline IsUnitMoving and not IsMovementTrue or to call IsMovementFalse, because the former calls GetUnitId twice while the latter calls it only once but without inlining the function itself.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Many things have been optimized and adjusted for accuracy.

When a unit is issued an order to some coordinates, whether it be a unit's coordinates or a simple point on the map, the flag "natural movement" is enabled. A few things cancel this "natural" flag:

Unit is order to stop or to hold position.
Unit starts channeling a spell (has to stand still to do this).

The events OnUnitMove and OnUnitStop are no longer triggered by the stop/move commands, because I don't think that provides accurate responses. If the unit is ordered to move and then stops before it could move, that would mean the move/stop events fired for nothing. The only thing that can trigger them now is discrepancies between periodic scans, as normal.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Thank you.

I wish I had a way to compile this so I could put it in a demo-map, but don't have the game installed. I just want to run this script:

JASS:
library test initializer init requires IsUnitMoving
    
    private function onMove takes nothing returns boolean
        call BJDebugMsg(GetUnitName(GetMovingUnit()) + " has started moving.")
        return false
    endfunction
    
    private function onStop takes nothing returns boolean
        call BJDebugMsg(GetUnitName(GetMovingUnit()) + " has stopped moving.")
        return false
    endfunction
    
    private function init takes nothing returns nothing
        call OnUnitMove(Filter(function onMove))
        call OnUnitStop(Filter(function onStop))
    endfunction
endlibrary

Unfortunately, as I am unable to compile it, I have no way of checking for syntax errors. My JASS career is slowly going down the drain.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
private unit array a_u // Only invoked if AutoIndex is used.

That should be in a static if. All of the code that is only used with specific libs or w/e should be in static ifs so that they are only there if the system is present : |. This means that you need to put all globals that are only present if a library exists into structs because otherwise they will always be put there.

Example
JASS:
//library a
//endlibrary

library b uses optional a
    static if LIBRARY_a then
        globals
            integer a
        endglobals
    endif
    
    globals
        integer a
    endglobals
endlibrary

throws a syntax error as both integer a's are put in

This does not

JASS:
//library a
//endlibrary

library b uses optional a
    struct bleh extends array
        static if LIBRARY_a then
            integer a
        endif
        integer a
    endstruct
endlibrary


edit
Make a unit command tracking library ^>^.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
A unit-command tracking library? I think LastOrder on wc3c.net already did that.

If it's a script from wc3c, 99% chance it's garbage. Almost every script I read there was really bad.. : |

Hell, I even used one once and it had memory leaks all over.. I posted a reply and after one month of being ignored, I redid the damn thing.

And your example is bad..
JASS:
library b uses optional a
    static if LIBRARY_a then
        globals
            integer array a
        endglobals
    else
        globals
            integer a
        endglobals
    endif
endlibrary

Always returns true for globals, so ofc it compiles...... even if it returned false, your example would always compile. Mine didn't compile because it always returned true, even when it was actually false, meaning that both integer a were there.

Try this without library a being present
JASS:
library b uses optional a
    static if LIBRARY_a then
        globals
            a
        endglobals
    else
        globals
            integer a
        endglobals
    endif
endlibrary
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Ok, ok. It's done.

I also fixed the order-detection library so it now properly detects any type of order. From what I have seen in other libraries, EVENT_PLAYER_UNIT_ISSUED_ORDER seems to only detects immediate orders, so I have split it into three seperate event registries.

The user needs only include any of the addons in their own map to receive any of the benefits they have to offer. In the previous release, if the user wanted to include UnitMovementEvent, they would have to require it as well as IsUnitMoving for it to function. Now, the user need only require IsUnitMoving.

I hope this system sees the light of day soon :)
 
Level 7
Joined
Oct 11, 2008
Messages
304
Justing reporting some bug to you know with JH and your lib...

Your Library

JASS:
    private function init takes nothing returns nothing
        call TimerStart(CreateTimer(), TIMEOUT, true, function scan)
    endfunction

JASS:
    private struct init extends array
        implement initmodule
    endstruct


JassHelper

Scope symbol redeclared: init
\--- (previously declared here)


and before someone ask me, yes, i'm using JassHelper 0.A.2.B


No one try a test with JH?

EDIT:

JASS:
    private struct data extends array
        static if LIBRARY_AutoIndex then
            unit array units // AutoIndex does not have a reverse-lookup call
        endif
    endstruct

JH:

Array structs cannot have array members yet, use a dynamic array instead, if necessary.

I think someone forget to check the limitation of vJass :)
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
JASS:
private struct data extends array
    static if LIBRARY_AutoIndex then
        static unit array units // AutoIndex does not have a reverse-lookup call
    endif
endstruct

and rename the struct or function
JASS:
private struct pookybear extends array
    implement initmodule
endstruct

I haven't seen Bribe around, so those are your fixes until Bribe updates code : P.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
@Nesth

i know how to fix it lol

in my case... i just change the struct name to Init and commented the struct (since i don't use AI, just AIDS)

anyway :) bugs reported

kk, good to know ^_^. This comment just threw me off ; D

and before someone ask me, yes, i'm using JassHelper 0.A.2.B

Oh well, good luck with your map ; ).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Thanks for the bug reports o_O

I wouldn't have thought of the array/static array difference without that help. See, I can't compile the script so I can only rely on my expertise.

Also, structs sharing the same namespace as a function, I can see why Vexorian would do that for typecasting. Nevertheless, vJass really could improve on its scoping protocol.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Have to ask someone to do that for me. I can't compile vJass.

Also, which unit indexing system are you using? Are you using any of the modules? Details will help me provide the solution (specific compile-time errors you've experienced would help).

Also, the version of JassHelper you have will influence how it compiles. You need 0.A.2.B as far as I know.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Added a couple of textmacros and some "get" functions for users' convenience.

JASS:
struct data
    
    static method myFunc takes integer i, unit u returns nothing
        call BJDebugMsg(GetUnitName(u))
    endmethod
    
    //! runtextmacro ForIndexedUnits("myFunc")
    
    static method myFunc2 takes integer i, unit u returns nothing
        call BJDebugMsg(GetUnitName(u) + " is moving!")
    endmethod
    
    //! runtextmacro ForMovingUnits("myFunc2")
    
    static method DisplayIndexes takes nothing returns nothing
        call forIndexedUnits() // Calls "myFunc" for all indexed units.
        call forMovingUnits()  // Calls "myFunc2" for all moving units.
    endmethod
    
endstruct
    
// New functions:
    
function GetNextUnitIndex takes integer i returns integer
    
function IsUnitMovingById takes integer i returns boolean
    
function IsMovementTrueById takes integer i returns boolean
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Since your event registration functions are just wrappers, just tell people to use the Event API to register their stuff and provide readonly Event fields.

From
JASS:
//===========================================================================
// call OnUnitMove(Condition(function Moved)) during map initialization to
// register a function to be evaluated each time a unit begins moving.
//
function OnUnitMove takes boolexpr c returns nothing
    call RegisterEvent(c, s_move)
endfunction

//===========================================================================
// Or...
//
function TriggerRegisterUnitMoveEvent takes trigger t returns nothing
    call TriggerRegisterEvent(t, s_move)
endfunction

//===========================================================================
// call OnUnitStop(Condition(function Stopped)) during map initialization to
// register a function to be evaluated each time a unit stops moving.
//
function OnUnitStop takes boolexpr c returns nothing
    call RegisterEvent(c, s_stop)
endfunction

//===========================================================================
// Or...
//
function TriggerRegisterUnitStopEvent takes trigger t returns nothing
    call TriggerRegisterEvent(t, s_stop)
endfunction

to just like
JASS:
static readonly UnitMove.MOVE
static readonly UnitMove.STOP

Or something

This way people can do like
call TriggerRegisterEvent(t, UnitMove.MOVE)

or
call UnitMove.registerTrigger(t)

That'll make it so you have 4 less functions in your lib ; P.


Also, what's missing in this line? : P
library IsUnitMoving requires UnitIndexer, optional IsNativeMovement
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
What's missing? UnitIndexer requires Event already, so nothing is missing ;)

The other changes have been made. Make sure to update the changes to your libraries that depend on this to use RegisterEvent or TriggerRegisterEvent with the new readonly vars UnitMoving.MOVE_EVENT and UnitMoving.STOP_EVENT.

Also, added a filter so that units without the move ability don't uselessly get allocated.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
This should be static
JASS:
    method filter takes unit u returns boolean
        return GetUnitAbilityLevel(u, 'Amov') != 0
    endmethod

As it stands, it uselessly declares a local =P.

Also, good avoidance of extra triggers by avoiding the use of the unit method operator and using GetIndexedUnit instead ;D.

JASS:
    private method index takes nothing returns nothing
        set a_prev[a_next[0]] = this
        set a_next[this] = a_next[0]
        set a_next[0] = this
        set a_prev[this] = 0
        set a_x[this] = GetUnitX(GetIndexedUnit())
        set a_y[this] = GetUnitY(GetIndexedUnit())
    endmethod

/happy face

It would be nice with a list of functions like this though
JASS:
function IsUnitMoving takes unit u returns boolean
function IsUnitMovingById takes integer i returns boolean
function GetMovingUnit takes nothing returns unit
function GetNextUnitIndex takes integer i returns integer

static constant Event MOVE_EVENT
static constant Event STOP_EVENT

Also you should really follow naming convention in those events: EVENT_MOVE and EVENT_STOP.

Given that it's somewhat obvious, you could also just do MOVE and STOP.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
No simple API list above system code? : (

Going through your system to find the functions is annoying /cry

JASS:
library IsNativeMovement initializer onInit requires UnitIndexer

That one is missing list =p

edit
Is there a reason this is public? static method filter takes unit u returns boolean
 
Last edited:
Level 22
Joined
Nov 14, 2008
Messages
3,256
JASS:
//     readonly real x
//     > Unit's last-checked x
//
//     readonly real y
//     > Unit's last-checked y

Does not exist in the script.

JASS:
if x == u.x and y == u.y) then

The ) must be removed.

Also gaining this error

deindex is not an static method of UnitMoving that takes nothing
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Right with the constant function, I should just avoid the habit of wanting to add constant to every function (GetUnitUserData is not a constant native, for example). Didn't realize that Nestharus didn't include the constant keyword with that GetUnitById function.

The index errors... hmm, that's weird. I'll look into it when I am home.
 
Top