1. The mythological era has spawned some interesting characters around. Check them out and be sure to vote for them in the 30th Poll of the Texturing Contest.
    Dismiss Notice
  2. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  3. Hivers united and created a bunch of 2v2 melee maps. Vote for the best in our Melee Mapping Contest #4 - Poll!
    Dismiss Notice
  4. Check out the Staff job openings thread.
    Dismiss Notice

[System] Is Unit Moving

Discussion in 'Graveyard' started by Bribe, Sep 9, 2010.

  1. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Code (vJASS):

    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:

    Code (vJASS):

    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: Jul 3, 2011
  2. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,149
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    Brilliantly done! I see absolutely nothing wrong with it o-o.

    I would do an optional add on for unit stop move and unit begin move =).
     
  3. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    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 :)
     
  4. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,149
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    Looks good : D

    except?
    Code (vJASS):

    call TriggerEvaluate(OnMoveTrig)
                    static if (LIBRARY_Event) then
                        call Event(OnMove).fire()
                    endif
     


    shouldn't that be either or? : P
     
  5. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Not sure how to play that, because the user could very well have Event in their map yet want to use the faster Booleans which this library provides.
     
  6. Freyleyes

    Freyleyes

    Joined:
    Jun 28, 2008
    Messages:
    766
    Resources:
    1
    Spells:
    1
    Resources:
    1
    Looks excellent, thanks for the help.
     
  7. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    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
     
  8. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,149
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    Another interesting idea... do movement controller checking to see if the unit is moving on its own or if it's being forcefully moved by some sort of spell or JASS code = ). There are many spells and effects that only effect a unit while that unit is moving on their own = P.
     
  9. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    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.
     
  10. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    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.
     
  11. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    I kind of like it. I vote for approving.
     
  12. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    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:

    Code (vJASS):

    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.
     
  13. Axarion

    Axarion

    Joined:
    Sep 30, 2009
    Messages:
    675
    Resources:
    1
    Spells:
    1
    Resources:
    1
    May be very useful in some cases. Thanks to you two :)
     
  14. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,149
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    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
    Code (vJASS):

    //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

    Code (vJASS):

    //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: Sep 11, 2010
  15. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    I think I have tested the following and it compiled appropriately:

    Code (vJASS):

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


    A unit-command tracking library? I think LastOrder on wc3c.net already did that.
     
  16. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,149
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    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..
    Code (vJASS):

    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
    Code (vJASS):

    library b uses optional a
        static if LIBRARY_a then
            globals
                a
            endglobals
        else
            globals
                integer a
            endglobals
        endif
    endlibrary
     
     
  17. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Updated with some slick changes.
     
  18. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,149
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    Code (vJASS):

        static if LIBRARY_AutoIndex then
            globals
                private unit array a_unit // AutoIndex fails at reverse-lookup...
            endglobals
        endif
     


    fix this or get rid of the static if. I told you 2x now that static ifs for globals always returns true...

    I love how I'm so totally ignored ; ).
     
    Last edited: Sep 14, 2010
  19. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    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 :)
     
  20. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,149
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    Looks good now = ).

    I'm not sure that some of these add ons should be add ons (some could run through IsUnitMoving as separate resources), but eh ;o. Coupling pretty much saves a trigger evaluation for the add ons, so I guess it's ok ; D.


    I shall bonk azlier on the head so that he comes o-o.