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

SmartTrack v1.05.1

  • Like
Reactions: tyya


JASS:
library SmartTrack requires RegisterPlayerUnitEvent optional TimerUtils

/*
    SmartTrack v1.05.1
    By Spellbound
   
   
    ==== Description ====
   
    SmartTrack will track when a unit is ordered to right-click on another unit. For this to
    happen, the 'Tracker' will have to first be identified with CreateSmartOrderTracker, which
    takes the unit itself (usually a building) and the range with which it will detect a unit
    that right-clicked it.
   
    SmartTrack can thus be used in conjunction with other systems like a custom waygate system
    or docking system.
   
   
    ==== Requirements ====
   
    RegisterPlayerUnitEvent (by Bannar)
    https://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
   
    Any custom value unit indexer such as:
    Unit Event: https://www.hiveworkshop.com/threads/gui-unit-event-v2-5-0-0.201641/ or
    UnitDex: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
    Optionally uses TimerUtils for the SmartTrack.stop for the interruption boolean.
   
    ==== API ====
   
    function GetSmartUnit() returns the unit that did the right-clicking
   
    function GetTracker() is the building/unit that tracks right-clickers.
   
    function CreateSmartOrderTracker takes unit source, real range returns nothing
        ^ this sets up a unit as a Tracker that will fire an event whenever another unit that
        has right-clicked on it comes into range.
   
    function CancelSmartOrderTracker takes unit source returns nothing
        ^ this undoes the setup. Call this function when the unit dies or is de-indexed.
   
    function RegisterNativeEvent takes whichEvent, function code returns nothing
        ^ this uses RegisterNativeEvent's event-creation to register SmartTrack's many events. They
        are as followed:
        EVENT_SMART_TRACK_IN_RANGE ---- will detect when a unit is in range. Range is determined by
                                        the function CreateSmartOrderTracker()
        EVENT_SMART_TRACK_STARTED ----- will fire when tracking has begun. If a unit is already in 
                                        range it will fire first, then EVENT_SMART_TRACK_IN_RANGE will fire.
        EVENT_SMART_TRACK_TERMINATED -- will fire when tracking has ended, either from cancellation 
                                        or a SmartUnit has come within range of its Tracker.
                               
    function RegisterIndexNativeEvent takes integer playerNumber, integer whichEvent, function code returns nothing
        ^ Same as above, but per-player. If you wish SmartTrack to only work for specific players,
        use this instead.
       
    SmartTrack.stop( takes unit smarty, boolean interruptOrders )
        ^ call this method when you want to prevent units from carrying out the right-click
        on EVENT_SMART_TRACK_STARTED event. This is useful for when you want to filter out units for
        whatever reasons. For example, if you are using DockingSystem you'll want to prevent
        some units from docking into the building. You can prevent that from happening by
        calling the above method.
       
        smarty is the unit you wish to interrupt
        interruptOrders will tell the system whether you want the unit to stop moving.
   
*/

globals
    constant integer ORDER_SMART = 851971
   
    private unit eventSmarty = null
    private unit eventTracker = null
    private boolean array stopTracking
    public boolean ignoreOrders = false // This boolean is used by other libraries that have SmartTrack as a dependency.
   
    integer EVENT_SMART_TRACK_IN_RANGE
    integer EVENT_SMART_TRACK_STARTED
    integer EVENT_SMART_TRACK_TERMINATED
   
    private hashtable TRIGGER_TRACK = InitHashtable()
    private hashtable TimerHash
    private trigger SmartyTrack = CreateTrigger()
    private integer TrackerCount = 0
    private integer MaxSmarties = 0
    private integer array SmartySlot
    private unit array Smarty
    private unit array Tracker
    private real array TrackerRange
    private trigger array TrackerTrig
    private trigger array EventTrig
    public boolean array IsATracker
endglobals

native UnitAlive takes unit u returns boolean

// Getter function
function GetSmartUnit takes nothing returns unit
    return eventSmarty
endfunction

function GetTracker takes nothing returns unit
    return eventTracker
endfunction

// This is the function that controls what trigger is run.
private function FireEvent takes unit tracker, unit u, integer ev returns nothing
    local integer playerId = GetPlayerId(GetOwningPlayer(u))
    local integer id = GetUnitUserData(u)
    local unit prevSmarty = eventSmarty
    local unit prevTracker = eventTracker
   
    // If SmartTrack.stop is called, stopTracking[id] will be set to true to prevent custom events from firing.
    if not stopTracking[id] then
        set eventSmarty = u
        set eventTracker = tracker
        call TriggerEvaluate(GetNativeEventTrigger(ev))
        if IsNativeEventRegistered(playerId, ev) then
            call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, ev))
        endif
        set eventSmarty = prevSmarty
        set eventTracker = prevTracker
    endif
   
    set prevSmarty = null
    set prevTracker = null
endfunction


// This function handles removing right-clickers from the Smarty[] list. It checks where the
// right-clicker is on the list and then move all elements above it in the list down by 1.
private function RemoveSmarty takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    local integer i = SmartySlot[id]
    local unit tracker = Tracker[id]
   
    if i != 0 then
        set Tracker[id] = null
        set SmartySlot[id] = 0
        loop
            set Smarty[i] = Smarty[i + 1]
            set SmartySlot[GetUnitUserData(Smarty[i])] = i
            set i = i + 1
            exitwhen i > MaxSmarties
        endloop
        set MaxSmarties = MaxSmarties - 1
        call FireEvent(tracker, u, EVENT_SMART_TRACK_TERMINATED)
    endif
   
    set tracker = null
endfunction


// This function fires when a unit comes in range of a tracker. It goes through the Smart[] list
// and if the unit correspond to a Smarty in the list as well as if the tracker is the one the
// unit had right-clicked on, a EVENT_SMART_TRACK_IN_RANGE event will fire.
private function FilterSmarties takes nothing returns boolean
    local unit tracker = LoadUnitHandle(TRIGGER_TRACK, GetHandleId(GetTriggeringTrigger()), 1)
    local unit u = GetTriggerUnit()
    local integer idU = GetUnitUserData(u)
    local integer i = 0
   
    loop
        set i = i + 1
        exitwhen i > MaxSmarties
        if u == Smarty[i] and Tracker[idU] == tracker then
            // Events
            call FireEvent(tracker, u, EVENT_SMART_TRACK_IN_RANGE)
            call RemoveSmarty(u)
        endif
    endloop
   
    set u = null
    set tracker = null
   
    return false
endfunction


// This is the main function that tracks all right-clicks on trackers.
private function TrackOrders takes nothing returns boolean
    local unit smarty = GetTriggerUnit()
    local unit tracker = GetOrderTargetUnit()
    local integer idSmarty = GetUnitUserData(smarty)
    local integer idTracker = GetUnitUserData(tracker)
   
    if not ignoreOrders then
        // if tracker is not null that means you have right-clicked on a unit.
        if tracker != null and GetIssuedOrderId() == ORDER_SMART then
            if IsATracker[idTracker] then
                if Tracker[idSmarty] == null then // If the right-clicker has no tracker identified...
                    if IsUnitInRange(smarty, tracker, TrackerRange[idTracker]) then
                        // Events
                        call FireEvent(tracker, smarty, EVENT_SMART_TRACK_STARTED)
                        call FireEvent(tracker, smarty, EVENT_SMART_TRACK_IN_RANGE)
                        call FireEvent(tracker, smarty, EVENT_SMART_TRACK_TERMINATED)
                    else
                        // If not in range, add right-clicker to the Smarty[] list.
                        set MaxSmarties = MaxSmarties + 1
                        set Smarty[MaxSmarties] = smarty
                        set SmartySlot[idSmarty] = MaxSmarties
                        set Tracker[idSmarty] = tracker
                        call FireEvent(tracker, smarty, EVENT_SMART_TRACK_STARTED)
                    endif
                else
                    // If the right-clicker was already being tracked by another tracker buy the
                    // right-clicked on another tracker, check if that new tracker is in range.
                    if IsUnitInRange(smarty, tracker, TrackerRange[idTracker]) then
                        call RemoveSmarty(smarty)
                        // Events
                        call FireEvent(tracker, smarty, EVENT_SMART_TRACK_STARTED)
                        call FireEvent(tracker, smarty, EVENT_SMART_TRACK_IN_RANGE)
                        call FireEvent(tracker, smarty, EVENT_SMART_TRACK_TERMINATED)
                    else
                        set Tracker[idSmarty] = tracker
                        call FireEvent(tracker, smarty, EVENT_SMART_TRACK_STARTED)
                    endif
                endif
            endif
        else
            // if you had a tracker but did not right-click on a unit, remove from Smarty[] list.
            if Tracker[idSmarty] != null then
                call RemoveSmarty(smarty)
            endif
        endif
    endif
   
    set smarty = null
    set tracker = null
    set stopTracking[idSmarty] = false
   
    return false
endfunction


// This function removes tracking from a unit so that right-clicking on it will not trigger any
// custom events from this library.
function CancelSmartOrderTracker takes unit source returns nothing
    local integer id = GetUnitUserData(source)
    local integer i
   
    call FlushChildHashtable(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]))
    call DestroyTrigger(TrackerTrig[id])
   
    set TrackerTrig[id] = null
    set IsATracker[id] = false
    set TrackerCount = TrackerCount - 1
   
    if TrackerCount < 1 then
        call DisableTrigger(SmartyTrack)
        set i = 0
        loop
            set i = i + 1
            exitwhen i > MaxSmarties
            set Tracker[GetUnitUserData(Smarty[i])] = null
            set Smarty[i] = null
        endloop
        set MaxSmarties = 0
    endif
endfunction


// This function will identify a unit as a tracker as well as at what range will the custom
// EVENT_SMART_TRACK_IN_RANGE event will trigger.
function CreateSmartOrderTracker takes unit source, real range returns nothing
    local integer id = GetUnitUserData(source)
   
    if TrackerTrig[id] == null then
        set TrackerTrig[id] = CreateTrigger()
    else
        // If TrackerTrig[id] already exists, it is destroyed and re-created rather than ignored
        // to overwrite the old tracking. This is useful in the event that you wish to modify
        // The range at which a unit tracks.
        call DestroyTrigger(TrackerTrig[id])
        set TrackerTrig[id] = CreateTrigger()
    endif
   
    call TriggerRegisterUnitInRange(TrackerTrig[id], source, range, null)
    call TriggerAddCondition(TrackerTrig[id], function FilterSmarties)
    call SaveUnitHandle(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]), 1, source)
    set TrackerCount = TrackerCount + 1
    set TrackerRange[id] = range //necessary for units that right-click inside the tracker's range.
    set IsATracker[id] = true
   
    if not IsTriggerEnabled(SmartyTrack) then
        call EnableTrigger(SmartyTrack)
    endif
endfunction


// This struct exists solely for prevent a tracking from occuring. It comes with the option to
// force a unit to stop. It orders the unit in question to stop after a zero-second timer, a
// necessary workaround because issuing a stop or stunned command in a function triggered by
// an order event will not work.
struct SmartTrack
   
    unit u
   
    private static method zeroTimerStun takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local thistype this
       
        static if LIBRARY_TimerUtils then
            set this = GetTimerData(t)
            call IssueImmediateOrderById(this.u, 851973) // order stunned
            set this.u = null
            call ReleaseTimer(t)
            call this.deallocate()
        else
            call IssueImmediateOrderById(LoadUnitHandle(TimerHash, GetHandleId(t), 1), 851973) // order stunned
            call FlushChildHashtable(TimerHash, GetHandleId(t))
            call DestroyTimer(t)
        endif
   
        set t = null
    endmethod
   
    static method stop takes unit smarty, boolean interruptOrders returns nothing
        local thistype this
        local timer t
       
        call RemoveSmarty(smarty)
        set stopTracking[GetUnitUserData(smarty)] = true
       
        if interruptOrders then
            static if LIBRARY_TimerUtils then
                set this = allocate()
                set this.u = smarty
                call TimerStart(NewTimerEx(this), 0., false, function thistype.zeroTimerStun)
            else
                set t = CreateTimer()
                call SaveUnitHandle(TimerHash, GetHandleId(t), 1, smarty)
                call TimerStart(t, 0., false, function thistype.zeroTimerStun)
                set t = null
            endif
        endif
    endmethod
   
endstruct


// This module sets up the various events the library uses. Uses RegisterEvent
// that fire when a unit comes within range of a tracker, or tracking has started or ended.
private module Init
    private static method onInit takes nothing returns nothing
   
        static if not LIBRARY_TimerUtils then
            set TimerHash = InitHashtable()
        endif
       
        set EVENT_SMART_TRACK_IN_RANGE = CreateNativeEvent()
        set EVENT_SMART_TRACK_STARTED = CreateNativeEvent()
        set EVENT_SMART_TRACK_TERMINATED = CreateNativeEvent()
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function TrackOrders)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function TrackOrders)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function TrackOrders)
       
    endmethod
endmodule

private struct init
    implement Init
endstruct

endlibrary

JASS:
scope onSmartTrack initializer init

    /*
        GetSmartUnit() == Unit that enters within range of the tracker
        GetTracker() == The tracker
    */
  
    private function Actions_inRange takes nothing returns boolean
      
        local unit smarty = GetSmartUnit()
        local unit tracker = GetTracker()
      
        local real life = GetUnitState(smarty, UNIT_STATE_LIFE)
      
        //Hunter's Hall
        if GetUnitTypeId(tracker) == 'edob' then
            if GetUnitTypeId(smarty) == 'earc' then //Archers
                //Do a passive transformation
                call UnitAddAbility(smarty, 'pt00')
                call UnitRemoveAbility(smarty, 'pt00')
            elseif GetUnitTypeId(smarty) == 'esen' then //Huntresses
                //Do a passive transformation
                call UnitAddAbility(smarty, 'pt01')
                call UnitRemoveAbility(smarty, 'pt01')
            endif
      
        //Arcane Tower
        elseif GetUnitTypeId(tracker) == 'hatw' then
            if life < GetUnitState(smarty, UNIT_STATE_MAX_LIFE) then
                call SetUnitState(smarty, UNIT_STATE_LIFE, GetUnitState(smarty, UNIT_STATE_LIFE) + 50.)
                call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\HealingSpray\\HealBottleMissile.mdl", smarty, "chest"))
            else
                call BJDebugMsg("This unit is already at full health")
            endif
          
        //Orc Burrow
        elseif GetUnitTypeId(tracker) == 'otrb' then
            call SetUnitState(smarty, UNIT_STATE_LIFE, life - 50.)
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl", smarty, "chest"))
          
        endif
      
        set smarty = null
        set tracker = null
      
        return false
    endfunction
  
    private function Actions_started takes nothing returns boolean
      
        local unit smarty = GetSmartUnit()
        local unit tracker = GetTracker()
      
        //Hunter's Hall - filter example
        if GetUnitTypeId(tracker) == 'edob' then
            if not (GetUnitTypeId(smarty) == 'earc' or GetUnitTypeId(smarty) == 'esen') then //Archers or Huntresses
                call SmartTrack.stop(smarty, true)
                call BJDebugMsg("Only Archers or Huntresses allowed")
            endif
        endif
      
        set smarty = null
        set tracker = null
      
        return false
    endfunction
  
    private function Actions_terminated takes nothing returns boolean
        call BJDebugMsg("Tracking terminated")
        return false
    endfunction
  
    private function init takes nothing returns nothing
        call RegisterNativeEvent(EVENT_SMART_TRACK_IN_RANGE, function Actions_inRange)
        call RegisterNativeEvent(EVENT_SMART_TRACK_STARTED, function Actions_started)
        call RegisterNativeEvent(EVENT_SMART_TRACK_TERMINATED, function Actions_terminated)
    endfunction

endscope

- v1.05.1 fixed an initialization issue.
- v1.05 updated to now use RegisterPlayerUnitEvent/RegisterNativeEvent as dependencies because Bannar happened and is the only one updating his resources. Added demo.
- v1.04 code is now recursion-safe.
- v1.03 Events now use GetSmartUnit() and GetTracker() to return the unit that did the right-clicking and the right-click-tracker respectively. ST_EVENT_UNIT and ST_EVENT_TRACKER have been made private. Also updated Unit Event to 2.5.0.0 for the demo.
- v1.02.1 fixed an issue with TrackOrders not returning a boolean, which was causing a compile error for some.
- v1.02 cleaned a local variable that was leaking as well as a global trigger variable that wasn't being nulled. Added comments to explain the code as well as expanded the demo map to include more significant demonstration of SmartTrack's capabilities.
- v1.01 cleaned up two local variables that were leaking and added a method for interrupting tracking, designed specifically for creating filters for dependent libraries.
- v1.00 released
Contents

SmartTrack v1.05.1 (Map)

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
Required for Approval:
  • Fix all leaks. The function FilterSmarties can potentially cause a unit handle leak due to the local declared local handle variable reference counter leak on return bug.

    Additionally any handle type array variable that is used to map data to units by a unit indexer must also be nulled after the mapping is no longer required. This is to allow timely recycling of the handle indices used as they will not be recycled as long as at least 1 reference to them still exists. An example of this is TrackerTrig, which trigger can be destroyed but the index might never be reused so the trigger handle might never be recycled.
Feedback and Suggestions:
  • It appears that the private trigger array SmartyTrig is never used. Probably was left in by mistake.
  • Code could do with more descriptive comments. Does not have to be over done but explaining what a private function does or the purpose of a block of code can be useful for future maintenance and for others to learn from.
  • Maybe expand the test map to show off what the system can do. Printing messages on the screen might seem like not much to some people.
 
local declared local handle variable reference counter leak on return bug
Counter leak? I'm not familiar with that - the only bug I know on return is when you return a local unit (for example), which takes away the change to null it. I'll be nulling tracker for the next version. Thanks for spotting that.

Additionally any handle type array variable that is used to map data to units by a unit indexer must also be nulled after the mapping is no longer required. This is to allow timely recycling of the handle indices used as they will not be recycled as long as at least 1 reference to them still exists. An example of this is TrackerTrig, which trigger can be destroyed but the index might never be reused so the trigger handle might never be recycled.
I don't think I using any handle variable besides TrackerTrig, which I will be nulling for the next version.

Thanks for the review. I'll expand the test map and comment some of my functions for next version.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
Counter leak? I'm not familiar with that - the only bug I know on return is when you return a local unit (for example), which takes away the change to null it. I'll be nulling tracker for the next version. Thanks for spotting that.
The bug is the entire reason why you have to null local units.

Handle IDs (you can see these with GetHandleId()) are a finite resource. The Handle IDs are recycled ever few game frames if the object assigned to them has been destroyed and only if there are no existing JASS references to them. This behaviour is needed for safety reasons as otherwise a reference to a destroyed object might change into a reference to a living object of the wrong type.

References to a handle ID are tracked using a reference counter. Every time a global or local handle variable is assigned to a handle ID it increments that handle ID's reference counter by 1. Every time such variables are assigned to a different handle ID it decrements the original handle ID's reference counter by 1. This behaviour only applies to some handle types, mostly those that represent objects with a finite life cycle.

The bug is that assigning a local declared local handle variable a handle ID will not automatically decrement the handle ID's reference counter by 1 on function return. This causes a reference counter leak and hence prevents the handle ID from ever being recycled. The solution is to explicitly decrement the handle ID's reference counter by 1 before function return by assigning the variable a null value.
 
Okay, updated per your requirements and suggestions. I do hope I got rid of all the leaks.

- v1.02 cleaned a local variable that was leaking as well as a global trigger variable that wasn't being nulled. Added comments to explain the code as well as expanded the demo map to include more significant demonstration of SmartTrack's capabilities.
 
Level 6
Joined
Oct 31, 2015
Messages
95
Hey guys could you give me some help pls? I'm getting this error message from JassHelper when trying to save maps with SmartTrack (even the demo map):

Jass Helper - "Line 301: Functions passed to Filter or Conditions must return a boolean."
in this line
Code:
call TriggerAddCondition(SmartTrack___SmartyTrack, Condition(function SmartTrack___TrackOrders))

I'm going to attach a picture of the error in the bottom in any case.

I'm using Warcraft patch 1.26.0.64. Do I need to use a newer version to be able to save SmartTrack's maps?
 

Attachments

  • SmartTrack Error.png
    SmartTrack Error.png
    322.9 KB · Views: 158
Level 6
Joined
Oct 31, 2015
Messages
95
Understood. I've downloaded the newer version and Wex but it still doesn't work to me. I will try to figure out what is causing the problem. Thanks for replying.
 
Actually, it occurs to me that TrackOrders is not returning a boolean, which it should, but WEX allowed it anyway...

@Dr Super Good I do have a question concerning that. My first priority is usually performance and I've been told that TriggerAddCondition is faster than TriggerAddAction. But then, there's this: call TriggerAddCondition(SmartyTrack, Condition(function TrackOrders)). Is that faster than call TriggerAddCondition(SmartyTrack, function TrackOrders)?

Also I guess I should update TrackOrders to return a boolean.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
Actually, it occurs to me that TrackOrders is not returning a boolean, which it should, but WEX allowed it anyway...
And it seemed to run. Does Warcraft III require that it return a boolean anymore?
call TriggerAddCondition(SmartyTrack, Condition(function TrackOrders))
. Is that faster than
call TriggerAddCondition(SmartyTrack, function TrackOrders)
?
The one on the right is not valid JASS. Hence one can only use the one on the left.

function TrackOrders -> code statement
Condition(function TrackOrders)) -> filter/boolexpr statement
 

Nitpicks:

  • Describing what the private functions do would help, even if only a bit.

Notes:

  • Just in case, in FireEvent, the variables could be made recursion-safe by hashing them into locals that store their previous values.

JASS:
local unit prevSmart = ST_UNIT
local unit prevTracker = ST_UNIT_TRACKER

ST_UNIT = newVal
ST_UNIT_TRACKER = newVal2

// fire...

ST_UNIT = prevSmart
ST_UNIT_TRACKER = prevTracker

// nullify...

Resolved.

Status:


Approved
 
Last edited:
I did describe the private functions though?

As for FireEvent, is this good? Idk what recursion-safe means :p
JASS:
    // This is the function that controls what trigger is run.
    private function FireEvent takes unit tracker, unit u, integer ev returns nothing
        local integer id = GetUnitUserData(u)
        local unit prevSmarty = ST_EVENT_UNIT
        local unit prevTracker = ST_EVENT_TRACKER
        // If SmartTrack.stop is called, stopTracking[id] will be set to true to prevent custom events from firing.
        if not stopTracking[id] then
            set ST_EVENT_UNIT = u
            set ST_EVENT_TRACKER = tracker
            call TriggerEvaluate(EventTrig[ev])
            set ST_EVENT_UNIT = prevSmarty
            set ST_EVENT_TRACKER = prevTracker
        endif
        set prevSmarty = null
        set prevTracker = null
    endfunction
 
My reason for that is an impractical one, but one that wouldn't hurt to prevent.

Consider an event that fires, and an action that somehow fires that event. If the former goes, we would lose our reference to the previous event.

For example:

Event unit: Death Knight
Death Knight somehow fires the event again.
Event unit 2: Death Knight
Event unit 2: null -> but Event unit is scalar.
Event unit: null before nulling :(

EDIT:

@Spellbound said:
I did describe the private functions though?

You did? I must have missed that.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
I don't understand why people stray away from requirements. Requirements make your code more coherent, prevent code duplication and such.

This library is currently incompatible with most vJass libraries that would require it.
- initializer init should be replaced with module initializer
Currently, if I call RegisterSmartTrackEvent in my library that requires yours, event triggers would be uninitialized (point to null rather than valid handle), thus registration would fail and I would not have been able to track anything.

- fix indentation, drop 4 spaces for all code lines:
JASS:
library (...)

    globals (...)

->>>
library (...)

globals (...)
- append newlines in code bloats segments e.g. between locals declaration and 'if' statement. Your init function indentation is superior to most of your other code parts
- event globals should be named using camel case rather than constant case - those are not constant, they change their value between FireEvent invocations

- should make use of RegisterEvent pack to reduce code size and streamline it with other custom-event exposing libraries
Create events off CreateNativeEvent and let user register to them via RegisterNativeEvent or RegisterIndexNativeEvent (e.g. for Player(0)), thus you do not have to account for player-specific event registration. Leave that to RegisterNativeEvent api.
 
- initializer init should be replaced with module initializer
Never used modules before so you'll have to show me how :p

Currently, if I call RegisterSmartTrackEvent in my library that requires yours, event triggers would be uninitialized (point to null rather than valid handle), thus registration would fail and I would not have been able to track anything.
How so? I used SmartTrack as a requirement in my other libraries without any issue.

- fix indentation, drop 4 spaces for all code lines:
Is this a vJASS standard?

- append newlines in code bloats segments e.g. between locals declaration and 'if' statement. Your init function indentation is superior to most of your other code parts
Hmm, I tend to avoid lines breaks inside of small functions so that each function appears clearly separate from one another. Is there a coding standard I can look at that will clear this up?

- event globals should be named using camel case rather than constant case - those are not constant, they change their value between FireEvent invocations
Yeah, I guess. What would that look like though? Also, any thoughts on naming a variable my_var as opposed to myVar?

- should make use of RegisterEvent pack to reduce code size and streamline it with other custom-event exposing libraries
Create events off CreateNativeEvent and let user register to them via RegisterNativeEvent or RegisterIndexNativeEvent (e.g. for Player(0)), thus you do not have to account for player-specific event registration. Leave that to RegisterNativeEvent api.
I mean, I probably would but I have no idea how to use RegisterNativeEvent.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Never used modules before so you'll have to show me how :p
You can checkout any of my resources in Jass section to see how module initializer looks like and how to implement one for yourself.

How so? I used SmartTrack as a requirement in my other libraries without any issue.
Both of your requirements unit function initializer, thus those are also prone to failure by the exact same issue.
If I do:
library MyLibrary requires SmarkTrack // notice the last of function initializer and then use module initializer for my structs, I'll experience undesired behavior as stated in previous post.

Is this a vJASS standard?
Of course it is. You start indentation from 0 space. Keyword "library" does not force you to start indentation from 4 spaces.

Hmm, I tend to avoid lines breaks inside of small functions so that each function appears clearly separate from one another. Is there a coding standard I can look at that will clear this up?
Coding standard states: code should be readable. Your init function is readable. Plenty of others are not. Newlines guarantee one can easier navigate between code segments within given function, helping him in understanding it quickly.

Yeah, I guess. What would that look like though? Also, any thoughts on naming a variable my_var as opposed to myVar?
This is not C. Use eventUnit instead of ST_EVENT_UNIT.

I mean, I probably would but I have no idea how to use RegisterNativeEvent.
Again, checkout any of custom-event exposing library or snippet, example: InventoryEvent. The debug messages for obsolete methods will tell you what to use instead. See demo code for usage.
 
Last edited:
You can checkout any of my resources in Jass section to see how module initializer looks like and how to implement one for yourself.
I am. Still doesn't make sense to me. I mean, ok, onInit will initialize the method but will it still do so within a module? Do I have to implement the module for it to work? These things may be obvious to you but they're not to me.

Also:
JASS:
private module ConstructEventInit
    private static method onInit takes nothing returns nothing
        set EVENT_UNIT_CONSTRUCTION_START = CreateNativeEvent()
        set EVENT_UNIT_CONSTRUCTION_CANCEL = CreateNativeEvent()
        set EVENT_UNIT_CONSTRUCTION_FINISH = CreateNativeEvent()
        set EVENT_UNIT_CONSTRUCTION_INTERRUPT = CreateNativeEvent()
        set START = EVENT_UNIT_CONSTRUCTION_START
        set CANCEL = EVENT_UNIT_CONSTRUCTION_CANCEL
        set FINISH = EVENT_UNIT_CONSTRUCTION_FINISH
        set INTERRUPT = EVENT_UNIT_CONSTRUCTION_INTERRUPT

        set ongoing = IntegerList.create()

        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function OnNonPointOrder)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function OnPointOrder)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function OnNonPointOrder)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, function OnConstructCancel)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function OnConstructFinish)

        call RegisterUnitIndexEvent(Condition(function OnIndex), EVENT_UNIT_INDEX)
        call RegisterUnitIndexEvent(Condition(function OnDeindex), EVENT_UNIT_DEINDEX)
    endmethod
endmodule

struct ConstructEvent
    readonly static integer START
    readonly static integer CANCEL
    readonly static integer FINISH
    readonly static integer INTERRUPT

    implement ConstructEventInit
endstruct

Is the ConstructEvent struct for backward compatibility?

Both of your requirements unit function initializer, thus those are also prone to failure by the exact same issue.
If I do:
library MyLibrary requires SmarkTrack // notice the last of function initializer
and then use module initializer for my structs, I'll experience undesired behavior as stated in previous post.
So a library/scope initializer and module initializer run at different times or something?

Of course it is. You can identation from 0 space. Keyword "library" does not force you to start indentation from 4 spaces.
To quote myself above, "These things may be obvious to you but they're not to me." Indenting 4 spaces after "library" makes sense to me because those functions are seen as part of said library.

EDIT:

How's that?

JASS:
library SmartTrack requires RegisterNativeEvent optional TimerUtils

/*
    SmartTrack v1.05
    By Spellbound
   
   
    ==== Description ====
   
    SmartTrack will track when a unit is ordered to right-click on another unit. For this to
    happen, the 'Tracker' will have to first be identified with CreateSmartOrderTracker, which
    takes the unit itself (usually a building) and the range with which it will detect a unit
    that right-clicked it.
   
    SmartTrack can thus be used in conjunction with other systems like a custom waygate system
    or docking system.
   
   
    ==== Requirements ====
   
    Any custom value unit indexer such as:
    Unit Event: https://www.hiveworkshop.com/threads/gui-unit-event-v2-5-0-0.201641/ or
    UnitDex: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
    Optionally uses TimerUtils for the SmartTrack.stop for the interruption boolean.
   
    ==== API ====
   
    function GetSmartUnit() returns the unit that did the right-clicking
   
    function GetTracker() is the building/unit that tracks right-clickers.
   
    function CreateSmartOrderTracker takes unit source, real range returns nothing
        ^ this sets up a unit as a Tracker that will fire an event whenever another unit that
        has right-clicked on it comes into range.
   
    function CancelSmartOrderTracker takes unit source returns nothing
        ^ this undoes the setup. Call this function when the unit dies or is de-indexed.
       
    SmartTrack.stop( takes unit smarty, boolean interruptOrders )
        ^ call this method when you want to prevent units from carrying out the right-click
        on ST_TRACKING_STARTED event. This is useful for when you want to filter out units
        for whatever reasons. For example, a if you're uding DockingSystem, you'll want to
        prevent some units from docking into the building. You can prevent that from happening
        by calling the above method.
       
        smarty is the unit you wish to interrupt
        interruptOrders will tell the system whether you want the unit to stop moving.
   
*/

globals
    constant integer ORDER_SMART = 851971
   
    private unit eventSmarty = null
    private unit eventTracker = null
    private boolean array stopTracking
    public boolean ignoreOrders = false // This boolean is used by other libraries that have SmartTrack as a dependency.
   
    constant integer ST_UNIT_IN_RANGE = 1
    constant integer ST_TRACKING_STARTED = 2
    constant integer ST_TRACKING_TERMINATED = 3
   
    private hashtable TRIGGER_TRACK = InitHashtable()
    private hashtable TimerHash
    private trigger SmartyTrack = CreateTrigger()
    private integer TrackerCount = 0
    private integer MaxSmarties = 0
    private integer array SmartySlot
    private unit array Smarty
    private unit array Tracker
    private real array TrackerRange
    private trigger array TrackerTrig
    private trigger array EventTrig
    public boolean array IsATracker
endglobals

native UnitAlive takes unit u returns boolean

// Getter function
function GetSmartUnit takes nothing returns unit
    return eventSmarty
endfunction

function GetTracker takes nothing returns unit
    return eventTracker
endfunction

// This is the function that controls what trigger is run.
private function FireEvent takes unit tracker, unit u, integer ev returns nothing
    local integer id = GetUnitUserData(u)
    local unit prevSmarty = eventSmarty
    local unit prevTracker = eventTracker
   
    // If SmartTrack.stop is called, stopTracking[id] will be set to true to prevent custom events from firing.
    if not stopTracking[id] then
        set eventSmarty = u
        set eventTracker = tracker
        call TriggerEvaluate(EventTrig[ev])
        set eventSmarty = prevSmarty
        set eventTracker = prevTracker
    endif
   
    set prevSmarty = null
    set prevTracker = null
endfunction


// This function registers the various custom events provided by SmartTrack.
function RegisterSmartTrackEvent takes integer ev, code c returns nothing
    call TriggerAddCondition(EventTrig[ev], Condition(c))
endfunction


// This function handles removing right-clickers from the Smarty[] list. It checks where the
// right-clicker is on the list and then move all elements above it in the list down by 1.
private function RemoveSmarty takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    local integer i = SmartySlot[id]
    local unit tracker = Tracker[id]
   
    if i != 0 then
        set Tracker[id] = null
        set SmartySlot[id] = 0
        loop
            set Smarty[i] = Smarty[i + 1]
            set SmartySlot[GetUnitUserData(Smarty[i])] = i
            set i = i + 1
            exitwhen i > MaxSmarties
        endloop
        set MaxSmarties = MaxSmarties - 1
        call FireEvent(tracker, u, ST_TRACKING_TERMINATED)
    endif
   
    set tracker = null
endfunction


// This function fires when a unit comes in range of a tracker. It goes through the Smart[] list
// and if the unit correspond to a Smarty in the list as well as if the tracker is the one the
// unit had right-clicked on, a ST_UNIT_IN_RANGE event will fire.
private function FilterSmarties takes nothing returns boolean
    local unit tracker = LoadUnitHandle(TRIGGER_TRACK, GetHandleId(GetTriggeringTrigger()), 1)
    local unit u = GetTriggerUnit()
    local integer idU = GetUnitUserData(u)
    local integer i = 0
   
    loop
        set i = i + 1
        exitwhen i > MaxSmarties
        if u == Smarty[i] and Tracker[idU] == tracker then
            // Events
            call FireEvent(tracker, u, ST_UNIT_IN_RANGE)
            call RemoveSmarty(u)
        endif
    endloop
   
    set u = null
    set tracker = null
   
    return false
endfunction


// This is the main function that tracks all right-clicks on trackers.
private function TrackOrders takes nothing returns boolean
    local unit smarty = GetTriggerUnit()
    local unit tracker = GetOrderTargetUnit()
    local integer idSmarty = GetUnitUserData(smarty)
    local integer idTracker = GetUnitUserData(tracker)
   
    if not ignoreOrders then
        // if tracker is not null that means you have right-clicked on a unit.
        if tracker != null and GetIssuedOrderId() == ORDER_SMART then
            if IsATracker[idTracker] then
                if Tracker[idSmarty] == null then // If the right-clicker has no tracker identified...
                    if IsUnitInRange(smarty, tracker, TrackerRange[idTracker]) then
                        // Events
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                        call FireEvent(tracker, smarty, ST_UNIT_IN_RANGE)
                        call FireEvent(tracker, smarty, ST_TRACKING_TERMINATED)
                    else
                        // If not in range, add right-clicker to the Smarty[] list.
                        set MaxSmarties = MaxSmarties + 1
                        set Smarty[MaxSmarties] = smarty
                        set SmartySlot[idSmarty] = MaxSmarties
                        set Tracker[idSmarty] = tracker
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                    endif
                else
                    // If the right-clicker was already being tracked by another tracker buy the
                    // right-clicked on another tracker, check if that new tracker is in range.
                    if IsUnitInRange(smarty, tracker, TrackerRange[idTracker]) then
                        call RemoveSmarty(smarty)
                        // Events
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                        call FireEvent(tracker, smarty, ST_UNIT_IN_RANGE)
                        call FireEvent(tracker, smarty, ST_TRACKING_TERMINATED)
                    else
                        set Tracker[idSmarty] = tracker
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                    endif
                endif
            endif
        else
            // if you had a tracker but did not right-click on a unit, remove from Smarty[] list.
            if Tracker[idSmarty] != null then
                call RemoveSmarty(smarty)
            endif
        endif
    endif
   
    set smarty = null
    set tracker = null
    set stopTracking[idSmarty] = false
   
    return false
endfunction


// This function removes tracking from a unit so that right-clicking on it will not trigger any
// custom events from this library.
function CancelSmartOrderTracker takes unit source returns nothing
    local integer id = GetUnitUserData(source)
    local integer i
   
    call FlushChildHashtable(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]))
    call DestroyTrigger(TrackerTrig[id])
   
    set TrackerTrig[id] = null
    set IsATracker[id] = false
    set TrackerCount = TrackerCount - 1
   
    if TrackerCount < 1 then
        call DisableTrigger(SmartyTrack)
        set i = 0
        loop
            set i = i + 1
            exitwhen i > MaxSmarties
            set Tracker[GetUnitUserData(Smarty[i])] = null
            set Smarty[i] = null
        endloop
        set MaxSmarties = 0
    endif
endfunction


// This function will identify a unit as a tracker as well as at what range will the custom
// ST_UNIT_IN_RANGE event will trigger.
function CreateSmartOrderTracker takes unit source, real range returns nothing
    local integer id = GetUnitUserData(source)
   
    if TrackerTrig[id] == null then
        set TrackerTrig[id] = CreateTrigger()
    else
        // If TrackerTrig[id] already exists, it is destroyed and re-created rather than ignored
        // to overwrite the old tracking. This is useful in the event that you wish to modify
        // The range at which a unit tracks.
        call DestroyTrigger(TrackerTrig[id])
        set TrackerTrig[id] = CreateTrigger()
    endif
   
    call TriggerRegisterUnitInRange(TrackerTrig[id], source, range, null)
    call TriggerAddCondition(TrackerTrig[id], function FilterSmarties)
    call SaveUnitHandle(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]), 1, source)
    set TrackerCount = TrackerCount + 1
    set TrackerRange[id] = range //necessary for units that right-click inside the tracker's range.
    set IsATracker[id] = true
   
    if not IsTriggerEnabled(SmartyTrack) then
        call EnableTrigger(SmartyTrack)
    endif
endfunction


// This function sets up the various events the library uses. EventTrig[ST_...] are custom events
// that fire when a unit comes within range of a tracker, or tracking has started or ended.
private module SmartTrackInit
    private static method onInit takes nothing returns nothing
        static if not LIBRARY_TimerUtils then
            set TimerHash = InitHashtable()
        endif
       
        set EventTrig[ST_UNIT_IN_RANGE] = CreateTrigger()
        set EventTrig[ST_TRACKING_STARTED] = CreateTrigger()
        set EventTrig[ST_TRACKING_TERMINATED] = CreateTrigger()
       
        call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
        call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        call TriggerAddCondition(SmartyTrack, Condition(function TrackOrders))
       
        call DisableTrigger(SmartyTrack) // No point in enabling the trigger if no trackers have been assigned yet.
    endmethod
endmodule


// This struct exists solely for prevent a tracking from occuring. It comes with the option to
// force a unit to stop. It orders the unit in question to stop after a zero-second timer, a
// necessary workaround because issuing a stop or stunned command in a function triggered by
// an order event will not work.
struct SmartTrack
   
    unit u
   
    private static method zeroTimerStun takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local thistype this
       
        static if LIBRARY_TimerUtils then
            set this = GetTimerData(t)
            call IssueImmediateOrderById(this.u, 851973) // order stunned
            set this.u = null
            call ReleaseTimer(t)
            call this.deallocate()
        else
            call IssueImmediateOrderById(LoadUnitHandle(TimerHash, GetHandleId(t), 1), 851973) // order stunned
            call FlushChildHashtable(TimerHash, GetHandleId(t))
            call DestroyTimer(t)
        endif
   
        set t = null
    endmethod
   
    static method stop takes unit smarty, boolean interruptOrders returns nothing
        local thistype this
        local timer t
       
        call RemoveSmarty(smarty)
        set stopTracking[GetUnitUserData(smarty)] = true
       
        if interruptOrders then
            static if LIBRARY_TimerUtils then
                set this = allocate()
                set this.u = smarty
                call TimerStart(NewTimerEx(this), 0., false, function thistype.zeroTimerStun)
            else
                set t = CreateTimer()
                call SaveUnitHandle(TimerHash, GetHandleId(t), 1, smarty)
                call TimerStart(t, 0., false, function thistype.zeroTimerStun)
                set t = null
            endif
        endif
    endmethod
   
    implement SmartTrackInit
   
endstruct

endlibrary
 
Last edited:
Initialization usually goes like this, in vJASS:

  1. Module initializers, (private onInit in modules)
  2. Struct initializers, (public onInit in modules)
  3. Library initializers
  4. Scope Initializers
  5. InitTrig initializers
  6. Map initialization initializers
  7. Zero-Second timer
As for the requirements, I find myself at a dilemma here.

On one hand, it streamlines the flow of code, allowing one to focus on implementing his/her spell/system. It also increases the likelihood of it being approved if the requirements are tested and proven to be of good quality (ergo, Approved)

On the other hand, I interpret having additional requirements as a burden on the part of those who download and import the spell/system in question. In addition, having too many requirements may actually become detrimental to your resource. (To wit, I must emphasize that these are my thoughts on the matter)

With that being said, I would suggest taking into consideration the extent to which you will need requirements. As all things are, keep them in moderation.
 
Last edited:
Ok, I think I got how RegisterNativeEvent works. I think.
Now about RegisterPlayerUnitEvent:

What is the difference between:
call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
and
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function SmartyTrack)

What I'm doing with SmartTrack right now is that if there are no units that are assigned as Trackers, I disable the trigger SmartyTrack so that these events don't fire. Would I be able to do the same with RegisterAnyPlayerUnitEvent()?
 
If I recall my precious examination of the resource above, you would have to update the handler values first before firing SmartyTrack. In essence, SmartyTrack will only fire when you want it to fire on the specified events, which means that it will no longer be enabled and disabled as often, and it frees up event handles, which get eaten up all too quickly.
 
Okay, I went ahead and tried to make use of RegisterNativeEvent and RegisterPlayerUnitEvent as optional, which might defeat the purpose of having libraries that are supposed to shorten code, but w/e, I still haven't decided:
JASS:
library SmartTrack requires optional RegisterPlayerUnitEvent optional TimerUtils

/*
    SmartTrack v1.05
    By Spellbound
   
   
    ==== Description ====
   
    SmartTrack will track when a unit is ordered to right-click on another unit. For this to
    happen, the 'Tracker' will have to first be identified with CreateSmartOrderTracker, which
    takes the unit itself (usually a building) and the range with which it will detect a unit
    that right-clicked it.
   
    SmartTrack can thus be used in conjunction with other systems like a custom waygate system
    or docking system.
   
   
    ==== Requirements ====
   
    Any custom value unit indexer such as:
    Unit Event: https://www.hiveworkshop.com/threads/gui-unit-event-v2-5-0-0.201641/ or
    UnitDex: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
    Optionally uses TimerUtils for the SmartTrack.stop for the interruption boolean.
   
    ==== API ====
   
    function GetSmartUnit() returns the unit that did the right-clicking
   
    function GetTracker() is the building/unit that tracks right-clickers.
   
    function CreateSmartOrderTracker takes unit source, real range returns nothing
        ^ this sets up a unit as a Tracker that will fire an event whenever another unit that
        has right-clicked on it comes into range.
   
    function CancelSmartOrderTracker takes unit source returns nothing
        ^ this undoes the setup. Call this function when the unit dies or is de-indexed.
       
    SmartTrack.stop( takes unit smarty, boolean interruptOrders )
        ^ call this method when you want to prevent units from carrying out the right-click
        on ST_TRACKING_STARTED event. This is useful for when you want to filter out units
        for whatever reasons. For example, a if you're uding DockingSystem, you'll want to
        prevent some units from docking into the building. You can prevent that from happening
        by calling the above method.
       
        smarty is the unit you wish to interrupt
        interruptOrders will tell the system whether you want the unit to stop moving.
   
*/

globals
    constant integer ORDER_SMART = 851971
   
    private unit eventSmarty = null
    private unit eventTracker = null
    private boolean array stopTracking
    public boolean ignoreOrders = false // This boolean is used by other libraries that have SmartTrack as a dependency.
   
    integer ST_UNIT_IN_RANGE
    integer ST_TRACKING_STARTED
    integer ST_TRACKING_TERMINATED
   
    private hashtable TRIGGER_TRACK = InitHashtable()
    private hashtable TimerHash
    private trigger SmartyTrack = CreateTrigger()
    private integer TrackerCount = 0
    private integer MaxSmarties = 0
    private integer array SmartySlot
    private unit array Smarty
    private unit array Tracker
    private real array TrackerRange
    private trigger array TrackerTrig
    private trigger array EventTrig
    public boolean array IsATracker
endglobals

native UnitAlive takes unit u returns boolean

// Getter function
function GetSmartUnit takes nothing returns unit
    return eventSmarty
endfunction

function GetTracker takes nothing returns unit
    return eventTracker
endfunction

// This is the function that controls what trigger is run.
private function FireEvent takes unit tracker, unit u, integer ev returns nothing
    local integer playerId = GetPlayerId(GetOwningPlayer(u))
    local integer id = GetUnitUserData(u)
    local unit prevSmarty = eventSmarty
    local unit prevTracker = eventTracker
   
    // If SmartTrack.stop is called, stopTracking[id] will be set to true to prevent custom events from firing.
    if not stopTracking[id] then
        set eventSmarty = u
        set eventTracker = tracker
        static if LIBRARY_RegisterPlayerUnitEvent then
            call TriggerEvaluate(GetNativeEventTrigger(ev))
            if IsNativeEventRegistered(playerId, ev) then
                call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, ev))
            endif
        else
            call TriggerEvaluate(EventTrig[ev])
        endif
        set eventSmarty = prevSmarty
        set eventTracker = prevTracker
    endif
   
    set prevSmarty = null
    set prevTracker = null
endfunction


// This function registers the various custom events provided by SmartTrack.
function RegisterSmartTrackEvent takes integer ev, code c returns nothing
    static if LIBRARY_RegisterPlayerUnitEvent then
        call RegisterNativeEvent(ev, c)
    else
        call TriggerAddCondition(EventTrig[ev], Condition(c))
    endif
endfunction


// This function handles removing right-clickers from the Smarty[] list. It checks where the
// right-clicker is on the list and then move all elements above it in the list down by 1.
private function RemoveSmarty takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    local integer i = SmartySlot[id]
    local unit tracker = Tracker[id]
   
    if i != 0 then
        set Tracker[id] = null
        set SmartySlot[id] = 0
        loop
            set Smarty[i] = Smarty[i + 1]
            set SmartySlot[GetUnitUserData(Smarty[i])] = i
            set i = i + 1
            exitwhen i > MaxSmarties
        endloop
        set MaxSmarties = MaxSmarties - 1
        call FireEvent(tracker, u, ST_TRACKING_TERMINATED)
    endif
   
    set tracker = null
endfunction


// This function fires when a unit comes in range of a tracker. It goes through the Smart[] list
// and if the unit correspond to a Smarty in the list as well as if the tracker is the one the
// unit had right-clicked on, a ST_UNIT_IN_RANGE event will fire.
private function FilterSmarties takes nothing returns boolean
    local unit tracker = LoadUnitHandle(TRIGGER_TRACK, GetHandleId(GetTriggeringTrigger()), 1)
    local unit u = GetTriggerUnit()
    local integer idU = GetUnitUserData(u)
    local integer i = 0
   
    loop
        set i = i + 1
        exitwhen i > MaxSmarties
        if u == Smarty[i] and Tracker[idU] == tracker then
            // Events
            call FireEvent(tracker, u, ST_UNIT_IN_RANGE)
            call RemoveSmarty(u)
        endif
    endloop
   
    set u = null
    set tracker = null
   
    return false
endfunction


// This is the main function that tracks all right-clicks on trackers.
private function TrackOrders takes nothing returns boolean
    local unit smarty = GetTriggerUnit()
    local unit tracker = GetOrderTargetUnit()
    local integer idSmarty = GetUnitUserData(smarty)
    local integer idTracker = GetUnitUserData(tracker)
   
    if not ignoreOrders then
        // if tracker is not null that means you have right-clicked on a unit.
        if tracker != null and GetIssuedOrderId() == ORDER_SMART then
            if IsATracker[idTracker] then
                if Tracker[idSmarty] == null then // If the right-clicker has no tracker identified...
                    if IsUnitInRange(smarty, tracker, TrackerRange[idTracker]) then
                        // Events
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                        call FireEvent(tracker, smarty, ST_UNIT_IN_RANGE)
                        call FireEvent(tracker, smarty, ST_TRACKING_TERMINATED)
                    else
                        // If not in range, add right-clicker to the Smarty[] list.
                        set MaxSmarties = MaxSmarties + 1
                        set Smarty[MaxSmarties] = smarty
                        set SmartySlot[idSmarty] = MaxSmarties
                        set Tracker[idSmarty] = tracker
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                    endif
                else
                    // If the right-clicker was already being tracked by another tracker buy the
                    // right-clicked on another tracker, check if that new tracker is in range.
                    if IsUnitInRange(smarty, tracker, TrackerRange[idTracker]) then
                        call RemoveSmarty(smarty)
                        // Events
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                        call FireEvent(tracker, smarty, ST_UNIT_IN_RANGE)
                        call FireEvent(tracker, smarty, ST_TRACKING_TERMINATED)
                    else
                        set Tracker[idSmarty] = tracker
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                    endif
                endif
            endif
        else
            // if you had a tracker but did not right-click on a unit, remove from Smarty[] list.
            if Tracker[idSmarty] != null then
                call RemoveSmarty(smarty)
            endif
        endif
    endif
   
    set smarty = null
    set tracker = null
    set stopTracking[idSmarty] = false
   
    return false
endfunction


// This function removes tracking from a unit so that right-clicking on it will not trigger any
// custom events from this library.
function CancelSmartOrderTracker takes unit source returns nothing
    local integer id = GetUnitUserData(source)
    local integer i
   
    call FlushChildHashtable(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]))
    call DestroyTrigger(TrackerTrig[id])
   
    set TrackerTrig[id] = null
    set IsATracker[id] = false
    set TrackerCount = TrackerCount - 1
   
    if TrackerCount < 1 then
        call DisableTrigger(SmartyTrack)
        set i = 0
        loop
            set i = i + 1
            exitwhen i > MaxSmarties
            set Tracker[GetUnitUserData(Smarty[i])] = null
            set Smarty[i] = null
        endloop
        set MaxSmarties = 0
    endif
endfunction


// This function will identify a unit as a tracker as well as at what range will the custom
// ST_UNIT_IN_RANGE event will trigger.
function CreateSmartOrderTracker takes unit source, real range returns nothing
    local integer id = GetUnitUserData(source)
   
    if TrackerTrig[id] == null then
        set TrackerTrig[id] = CreateTrigger()
    else
        // If TrackerTrig[id] already exists, it is destroyed and re-created rather than ignored
        // to overwrite the old tracking. This is useful in the event that you wish to modify
        // The range at which a unit tracks.
        call DestroyTrigger(TrackerTrig[id])
        set TrackerTrig[id] = CreateTrigger()
    endif
   
    call TriggerRegisterUnitInRange(TrackerTrig[id], source, range, null)
    call TriggerAddCondition(TrackerTrig[id], function FilterSmarties)
    call SaveUnitHandle(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]), 1, source)
    set TrackerCount = TrackerCount + 1
    set TrackerRange[id] = range //necessary for units that right-click inside the tracker's range.
    set IsATracker[id] = true
   
    if not IsTriggerEnabled(SmartyTrack) then
        call EnableTrigger(SmartyTrack)
    endif
endfunction


// This function sets up the various events the library uses. EventTrig[ST_...] are custom events
// that fire when a unit comes within range of a tracker, or tracking has started or ended.
private module SmartTrackInit
    private static method onInit takes nothing returns nothing
        static if not LIBRARY_TimerUtils then
            set TimerHash = InitHashtable()
        endif
       
        static if LIBRARY_RegisterPlayerUnitEvent then
            set ST_UNIT_IN_RANGE = CreateNativeEvent()
            set ST_TRACKING_STARTED = CreateNativeEvent()
            set ST_TRACKING_TERMINATED = CreateNativeEvent()
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function TrackOrders)
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function TrackOrders)
            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function TrackOrders)
        else
            set ST_UNIT_IN_RANGE = 1
            set ST_TRACKING_STARTED = 2
            set ST_TRACKING_TERMINATED = 3
           
            set EventTrig[ST_UNIT_IN_RANGE] = CreateTrigger()
            set EventTrig[ST_TRACKING_STARTED] = CreateTrigger()
            set EventTrig[ST_TRACKING_TERMINATED] = CreateTrigger()
       
            call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
            call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
            call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_ORDER)
            call TriggerAddCondition(SmartyTrack, Condition(function TrackOrders))
           
            call DisableTrigger(SmartyTrack) // No point in enabling the trigger if no trackers have been assigned yet.
        endif
    endmethod
endmodule


// This struct exists solely for prevent a tracking from occuring. It comes with the option to
// force a unit to stop. It orders the unit in question to stop after a zero-second timer, a
// necessary workaround because issuing a stop or stunned command in a function triggered by
// an order event will not work.
struct SmartTrack
   
    unit u
   
    private static method zeroTimerStun takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local thistype this
       
        static if LIBRARY_TimerUtils then
            set this = GetTimerData(t)
            call IssueImmediateOrderById(this.u, 851973) // order stunned
            set this.u = null
            call ReleaseTimer(t)
            call this.deallocate()
        else
            call IssueImmediateOrderById(LoadUnitHandle(TimerHash, GetHandleId(t), 1), 851973) // order stunned
            call FlushChildHashtable(TimerHash, GetHandleId(t))
            call DestroyTimer(t)
        endif
   
        set t = null
    endmethod
   
    static method stop takes unit smarty, boolean interruptOrders returns nothing
        local thistype this
        local timer t
       
        call RemoveSmarty(smarty)
        set stopTracking[GetUnitUserData(smarty)] = true
       
        if interruptOrders then
            static if LIBRARY_TimerUtils then
                set this = allocate()
                set this.u = smarty
                call TimerStart(NewTimerEx(this), 0., false, function thistype.zeroTimerStun)
            else
                set t = CreateTimer()
                call SaveUnitHandle(TimerHash, GetHandleId(t), 1, smarty)
                call TimerStart(t, 0., false, function thistype.zeroTimerStun)
                set t = null
            endif
        endif
    endmethod
   
    implement SmartTrackInit
   
endstruct

endlibrary
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Is the ConstructEvent struct for backward compatibility?
Yeah, that struct will be privatized once deprecation period ends and thus becoming init-struct only.

RegisterPlayerUnitEvent is one of the most commonly used resources. You should not worry about making it optional, just use it directly.
In wurst, you don't care about requirements at all. You just create new project and immediately 32903942 packages are added as dependancy for you.

4-5 requirements for vJass snippet? Comparison of those numbers speaks for itself..
If one uses VSCode + git this becomes even more trivial since your vJass code can be updated just like any other code repository. I've touched on this subject in my tutorial.

JASS:
function RegisterSmartTrackEvent takes integer ev, code c returns nothing
    static if LIBRARY_RegisterPlayerUnitEvent then
        call RegisterNativeEvent(ev, c)
    else
        call TriggerAddCondition(EventTrig[ev], Condition(c))
    endif
endfunction
You don't need that. RegisterNativeEvent does that for you.

RegisterNativeEvent(YOUR_EVENT) or RegisterIndexNativeEvent(GetPlayerId(p), YOUR_EVENT) is what user will be using. Look how coherent this becomes. No matter which event, which library we talk about, always the same two methods take care of event registration. Currently you do not declare player-specific register variant. With RegisterNativeEvent you don't have to, such api is already included.
What's also a nice addition is that you can rely on event-values as unique values for your tables or arrays. 1, 2, 3 does not seem unique to me, does it?

And last, you do not define trigger retrieval methods.
What if I want to disable smart track event firing to ensure my code behaves exactly the way I want it to with no side effects? Or perhaps one needs to prevent infinite recursion from happening (like we do for _ISSUED<>_ORDER playerunit events)? Again, RegisterNativeEvent exposes such functionality for you.

Consider making it a must. It's not 2010 anymore, we live in 2018.
 
Looking at your FireEvent trigger in ConstructEvent, I see this:
JASS:
    call TriggerEvaluate(GetNativeEventTrigger(evt))
    if IsNativeEventRegistered(playerId, evt) then
        call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, evt))
    endif

What is the purpose of this part?
JASS:
    if IsNativeEventRegistered(playerId, evt) then
        call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, evt))
    endif
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
RegisterNativeEvent(YOUR_EVENT) or RegisterIndexNativeEvent(GetPlayerId(p), YOUR_EVENT) is what user will be using. Look how coherent this becomes. No matter which event, which library we talk about, always the same two methods take care of event registration. Currently you do not declare player-specific register variant. With RegisterNativeEvent you don't have to, such api is already included..
Answer bolded. I've mentioned this like two times already.
 
Last edited:
Look, I've never worked with custom event libraries before, cut me some slack.

This good?
JASS:
library SmartTrack requires RegisterPlayerUnitEvent optional TimerUtils

/*
    SmartTrack v1.05
    By Spellbound
   
   
    ==== Description ====
   
    SmartTrack will track when a unit is ordered to right-click on another unit. For this to
    happen, the 'Tracker' will have to first be identified with CreateSmartOrderTracker, which
    takes the unit itself (usually a building) and the range with which it will detect a unit
    that right-clicked it.
   
    SmartTrack can thus be used in conjunction with other systems like a custom waygate system
    or docking system.
   
   
    ==== Requirements ====
   
    RegisterPlayerUnitEvent (by Bannar)
    https://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
   
    Any custom value unit indexer such as:
    Unit Event: https://www.hiveworkshop.com/threads/gui-unit-event-v2-5-0-0.201641/ or
    UnitDex: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
    Optionally uses TimerUtils for the SmartTrack.stop for the interruption boolean.
   
    ==== API ====
   
    function GetSmartUnit() returns the unit that did the right-clicking
   
    function GetTracker() is the building/unit that tracks right-clickers.
   
    function CreateSmartOrderTracker takes unit source, real range returns nothing
        ^ this sets up a unit as a Tracker that will fire an event whenever another unit that
        has right-clicked on it comes into range.
   
    function CancelSmartOrderTracker takes unit source returns nothing
        ^ this undoes the setup. Call this function when the unit dies or is de-indexed.
   
    function RegisterNativeEvent takes whichEvent, function code returns nothing
        ^ this uses RegisterNativeEvent's event-creation to register SmartTrack's many events. They
        are as followed:
        ST_UNIT_IN_RANGE ------ will detect when a unit is in range. Range is determined by the 
                                function CreateSmartOrderTracker()
        ST_TRACKING_STARTED --- will fire when tracking has begun. If a unit is already in range 
                                it will fire first, then ST_UNIT_IN_RANGE will fire.
        ST_TRACKING_TERMINATED  will fire when tracking has ended, either from cancellation or a
                                SmartUnit has come within range of its Tracker.
                               
    function RegisterIndexNativeEvent takes integer playerNumber, integer whichEvent, function code returns nothing
        ^ Same as above, but per-player. If you wish SmartTrack to only work for specific players,
        use this instead.
       
    SmartTrack.stop( takes unit smarty, boolean interruptOrders )
        ^ call this method when you want to prevent units from carrying out the right-click
        on ST_TRACKING_STARTED event. This is useful for when you want to filter out units for
        whatever reasons. For example, if you are using DockingSystem you'll want to prevent
        some units from docking into the building. You can prevent that from happening by
        calling the above method.
       
        smarty is the unit you wish to interrupt
        interruptOrders will tell the system whether you want the unit to stop moving.
   
*/

globals
    constant integer ORDER_SMART = 851971
   
    private unit eventSmarty = null
    private unit eventTracker = null
    private boolean array stopTracking
    public boolean ignoreOrders = false // This boolean is used by other libraries that have SmartTrack as a dependency.
   
    integer ST_UNIT_IN_RANGE
    integer ST_TRACKING_STARTED
    integer ST_TRACKING_TERMINATED
   
    private hashtable TRIGGER_TRACK = InitHashtable()
    private hashtable TimerHash
    private trigger SmartyTrack = CreateTrigger()
    private integer TrackerCount = 0
    private integer MaxSmarties = 0
    private integer array SmartySlot
    private unit array Smarty
    private unit array Tracker
    private real array TrackerRange
    private trigger array TrackerTrig
    private trigger array EventTrig
    public boolean array IsATracker
endglobals

native UnitAlive takes unit u returns boolean

// Getter function
function GetSmartUnit takes nothing returns unit
    return eventSmarty
endfunction

function GetTracker takes nothing returns unit
    return eventTracker
endfunction

// This is the function that controls what trigger is run.
private function FireEvent takes unit tracker, unit u, integer ev returns nothing
    local integer playerId = GetPlayerId(GetOwningPlayer(u))
    local integer id = GetUnitUserData(u)
    local unit prevSmarty = eventSmarty
    local unit prevTracker = eventTracker
   
    // If SmartTrack.stop is called, stopTracking[id] will be set to true to prevent custom events from firing.
    if not stopTracking[id] then
        set eventSmarty = u
        set eventTracker = tracker
        call TriggerEvaluate(GetNativeEventTrigger(ev))
        if IsNativeEventRegistered(playerId, ev) then
            call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, ev))
        endif
        set eventSmarty = prevSmarty
        set eventTracker = prevTracker
    endif
   
    set prevSmarty = null
    set prevTracker = null
endfunction


// This function handles removing right-clickers from the Smarty[] list. It checks where the
// right-clicker is on the list and then move all elements above it in the list down by 1.
private function RemoveSmarty takes unit u returns nothing
    local integer id = GetUnitUserData(u)
    local integer i = SmartySlot[id]
    local unit tracker = Tracker[id]
   
    if i != 0 then
        set Tracker[id] = null
        set SmartySlot[id] = 0
        loop
            set Smarty[i] = Smarty[i + 1]
            set SmartySlot[GetUnitUserData(Smarty[i])] = i
            set i = i + 1
            exitwhen i > MaxSmarties
        endloop
        set MaxSmarties = MaxSmarties - 1
        call FireEvent(tracker, u, ST_TRACKING_TERMINATED)
    endif
   
    set tracker = null
endfunction


// This function fires when a unit comes in range of a tracker. It goes through the Smart[] list
// and if the unit correspond to a Smarty in the list as well as if the tracker is the one the
// unit had right-clicked on, a ST_UNIT_IN_RANGE event will fire.
private function FilterSmarties takes nothing returns boolean
    local unit tracker = LoadUnitHandle(TRIGGER_TRACK, GetHandleId(GetTriggeringTrigger()), 1)
    local unit u = GetTriggerUnit()
    local integer idU = GetUnitUserData(u)
    local integer i = 0
   
    loop
        set i = i + 1
        exitwhen i > MaxSmarties
        if u == Smarty[i] and Tracker[idU] == tracker then
            // Events
            call FireEvent(tracker, u, ST_UNIT_IN_RANGE)
            call RemoveSmarty(u)
        endif
    endloop
   
    set u = null
    set tracker = null
   
    return false
endfunction


// This is the main function that tracks all right-clicks on trackers.
private function TrackOrders takes nothing returns boolean
    local unit smarty = GetTriggerUnit()
    local unit tracker = GetOrderTargetUnit()
    local integer idSmarty = GetUnitUserData(smarty)
    local integer idTracker = GetUnitUserData(tracker)
   
    if not ignoreOrders then
        // if tracker is not null that means you have right-clicked on a unit.
        if tracker != null and GetIssuedOrderId() == ORDER_SMART then
            if IsATracker[idTracker] then
                if Tracker[idSmarty] == null then // If the right-clicker has no tracker identified...
                    if IsUnitInRange(smarty, tracker, TrackerRange[idTracker]) then
                        // Events
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                        call FireEvent(tracker, smarty, ST_UNIT_IN_RANGE)
                        call FireEvent(tracker, smarty, ST_TRACKING_TERMINATED)
                    else
                        // If not in range, add right-clicker to the Smarty[] list.
                        set MaxSmarties = MaxSmarties + 1
                        set Smarty[MaxSmarties] = smarty
                        set SmartySlot[idSmarty] = MaxSmarties
                        set Tracker[idSmarty] = tracker
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                    endif
                else
                    // If the right-clicker was already being tracked by another tracker buy the
                    // right-clicked on another tracker, check if that new tracker is in range.
                    if IsUnitInRange(smarty, tracker, TrackerRange[idTracker]) then
                        call RemoveSmarty(smarty)
                        // Events
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                        call FireEvent(tracker, smarty, ST_UNIT_IN_RANGE)
                        call FireEvent(tracker, smarty, ST_TRACKING_TERMINATED)
                    else
                        set Tracker[idSmarty] = tracker
                        call FireEvent(tracker, smarty, ST_TRACKING_STARTED)
                    endif
                endif
            endif
        else
            // if you had a tracker but did not right-click on a unit, remove from Smarty[] list.
            if Tracker[idSmarty] != null then
                call RemoveSmarty(smarty)
            endif
        endif
    endif
   
    set smarty = null
    set tracker = null
    set stopTracking[idSmarty] = false
   
    return false
endfunction


// This function removes tracking from a unit so that right-clicking on it will not trigger any
// custom events from this library.
function CancelSmartOrderTracker takes unit source returns nothing
    local integer id = GetUnitUserData(source)
    local integer i
   
    call FlushChildHashtable(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]))
    call DestroyTrigger(TrackerTrig[id])
   
    set TrackerTrig[id] = null
    set IsATracker[id] = false
    set TrackerCount = TrackerCount - 1
   
    if TrackerCount < 1 then
        call DisableTrigger(SmartyTrack)
        set i = 0
        loop
            set i = i + 1
            exitwhen i > MaxSmarties
            set Tracker[GetUnitUserData(Smarty[i])] = null
            set Smarty[i] = null
        endloop
        set MaxSmarties = 0
    endif
endfunction


// This function will identify a unit as a tracker as well as at what range will the custom
// ST_UNIT_IN_RANGE event will trigger.
function CreateSmartOrderTracker takes unit source, real range returns nothing
    local integer id = GetUnitUserData(source)
   
    if TrackerTrig[id] == null then
        set TrackerTrig[id] = CreateTrigger()
    else
        // If TrackerTrig[id] already exists, it is destroyed and re-created rather than ignored
        // to overwrite the old tracking. This is useful in the event that you wish to modify
        // The range at which a unit tracks.
        call DestroyTrigger(TrackerTrig[id])
        set TrackerTrig[id] = CreateTrigger()
    endif
   
    call TriggerRegisterUnitInRange(TrackerTrig[id], source, range, null)
    call TriggerAddCondition(TrackerTrig[id], function FilterSmarties)
    call SaveUnitHandle(TRIGGER_TRACK, GetHandleId(TrackerTrig[id]), 1, source)
    set TrackerCount = TrackerCount + 1
    set TrackerRange[id] = range //necessary for units that right-click inside the tracker's range.
    set IsATracker[id] = true
   
    if not IsTriggerEnabled(SmartyTrack) then
        call EnableTrigger(SmartyTrack)
    endif
endfunction


// This module sets up the various events the library uses. Uses RegisterEvent
// that fire when a unit comes within range of a tracker, or tracking has started or ended.
private module SmartTrackInit
    private static method onInit takes nothing returns nothing
   
        static if not LIBRARY_TimerUtils then
            set TimerHash = InitHashtable()
        endif
       
        set ST_UNIT_IN_RANGE = CreateNativeEvent()
        set ST_TRACKING_STARTED = CreateNativeEvent()
        set ST_TRACKING_TERMINATED = CreateNativeEvent()
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function TrackOrders)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function TrackOrders)
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function TrackOrders)
       
    endmethod
endmodule


// This struct exists solely for prevent a tracking from occuring. It comes with the option to
// force a unit to stop. It orders the unit in question to stop after a zero-second timer, a
// necessary workaround because issuing a stop or stunned command in a function triggered by
// an order event will not work.
struct SmartTrack
   
    unit u
   
    private static method zeroTimerStun takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local thistype this
       
        static if LIBRARY_TimerUtils then
            set this = GetTimerData(t)
            call IssueImmediateOrderById(this.u, 851973) // order stunned
            set this.u = null
            call ReleaseTimer(t)
            call this.deallocate()
        else
            call IssueImmediateOrderById(LoadUnitHandle(TimerHash, GetHandleId(t), 1), 851973) // order stunned
            call FlushChildHashtable(TimerHash, GetHandleId(t))
            call DestroyTimer(t)
        endif
   
        set t = null
    endmethod
   
    static method stop takes unit smarty, boolean interruptOrders returns nothing
        local thistype this
        local timer t
       
        call RemoveSmarty(smarty)
        set stopTracking[GetUnitUserData(smarty)] = true
       
        if interruptOrders then
            static if LIBRARY_TimerUtils then
                set this = allocate()
                set this.u = smarty
                call TimerStart(NewTimerEx(this), 0., false, function thistype.zeroTimerStun)
            else
                set t = CreateTimer()
                call SaveUnitHandle(TimerHash, GetHandleId(t), 1, smarty)
                call TimerStart(t, 0., false, function thistype.zeroTimerStun)
                set t = null
            endif
        endif
    endmethod
   
    implement SmartTrackInit
   
endstruct

endlibrary
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Mate, writing code for you won't do anything good for both of us.
I want YOU to learn. I tried to be as clear in my explanations as possible. Not denying, maybe those could have been better.

However, at the end you did it, you learnt and you've written that code by yourself.
None done that for you. Congratz!

Now to module initializer and match is won : D

Module initializers take form of:
JASS:
private module MyInit
    private static method onInit takes nothing returns nothing
    endmethod
endmodule

struct MyStruct extends array
    implement MyInit
endstruct
The sole reason for this crap code is jasshelper's fked initialization order. If we don't do this, we risk child libraries (ones that would require our resources) to experience undesired behavior.
 
I never told you to write all that code for me. All I was asking was for some clarification. Much like my code, your explanations require some refinement ;)
RegisterIndexNativeEvent() particularly was giving me pause because not once in the documentation did it mention that the index was about a player number.

ANYWAY, it's done. I learned something new and I'll be updating the rest of my systems today.

EDIT: updated.
 
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Because.. it's not specifically about player's id :D. It's just the most common use case.
There are other use cases though e.g.: RegisterUnitEvent. In this case, instead of using GetPlayerId(p), I provide GetHandleId(u). That's why it is called RegisterIndexNativeEvent. User can provide any integral value to ensure there is a separate trigger ready and waiting for him. All other cases are registered under "any-player" id, which is based on bj_MAX_PLAYER_SLOTS, currently with value of 28.

Nitpick:
Perhaps event globals could use some better names. ST_UNIT_IN_RANGE -> EVENT_TRACK_UNIT_IN_RANGE or EVENT_TRACKABLE_UNIT_IN_RANGE? Just a suggestion : )
 
Top