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

SmartTracking

Status
Not open for further replies.
09/10/2017

SmartTrack has been really helpful in helping me recreate my Portal system as well as make the Docking System I made in one of my Techtree Contest submissions shareable. I've updated the demo map for SmartTrack to include all these libraries, and I have to say, they work pretty damn well. It was also an opportunity for me to learn how to use Table, so that's another bonus.

I do have one question, however. The only method I've known for creating custom events before I started this project was to use the real variable method, but that has proven to not be MUI in all situations. I'm not sure what those situations are, but I've been having significant overlap while testing
SmartTrack, which prompted me to use the TriggerEvaluate method. Currently, SmartTrack and DockingSystem are both using that, but CustomWaygate is still using the variable method. Should I just change CustomWaygate's event style for the sake of consistency?

Download Demo: SmartTrack

JASS:
library CustomWaygate initializer init /*
 
    By Spellbound
 
    ==== DESCRIPTION ====
 
    Custom Waygate allows two units to be connected via one-way 'tunnels'. A unit can have
    multiple tunnels, which creates a network that allows the player to chose, through
    right-clicking, the destination portal.
 
 
    ==== INSTALLATION ====
 
    • Copy-paste this trigger into your map.
    • Copy-paste the required libraries into your map.
    • The system is installed! Copy-paste the onCustomWaygate Event trigger or make your own
      event trigger and start coding.
 
    */ requires /*
  
        */ SmartTrack,      /* Comes with this map
        */ Table,           /* [URL='https://www.hiveworkshop.com/threads/snippet-new-table.188084/'][Snippet] New Table[/URL]
        */ TimerUtils       /* [URL='http://www.wc3c.net/showthread.php?t=101322']Database Error[/URL]
  
    */ optional /*
 
        */ BlizzardMessage  /* [URL='https://www.hiveworkshop.com/threads/snippet-blizzardmessage.237845/'][Snippet] BlizzardMessage[/URL]
      
    ==== API ====
 
    SETUP________
 
    CustomWaygate.createTunnel( takes unit source, unit target, real teleport_distance, real timeout )
        Sets up a tunnel that will teleport unit from the source to the target. The teleport_dsitance
        determines how close to the source the teleporting units have to be to use it. The timeout
        determines if there's a channeling time before teleportation itself occurs.
  
        To create a two-way teleporter, simply repeat this function but set the target as the source
        and the source as the target.
  
        A waygate become a network the moment it has more than one tunnel.
  
    CustomWaygate.destroyTunnel( takes unit source, unit target )
        Destroys a tunnel. This will only destroy the tunnel going from the source to the target.
        If the target has a tunnel going to the source, it will be unaffected.
  
  
    EVENTS________
 
    WAYGATE_EVENT
        The events that fire on pre-teleportation, post-teleportation, teleport prepping and
        teleport cancellation
  
        0.50 == right before a unit it moved.
        1.00 == right after a unit is moved.
        1.50 == When teleportation has a duration, this fires as that duration starts. It ends
                at pre-teleportation, or 0.50.
        2.00 == When teleportation has been cancelled, either manually or through INTERRUPT_TELEPORT.
                See below.
 
    WAYGATE_SOURCE
        The waygate unit itself. Origin waygate.
  
    WAYGATE_TARGET
        The waygate your units teleport to. Destination.
  
    WAYGATE_TRAVELLER
        The units that are using the waygate.
  
    INTERRUPT_TELEPORT
        Set this to true on 0.50 (pre-teleportaton) if you wish to interrupt the process and handle
        teleportation yourself. Such situations would be if you want to have your teleporter shoot
        your traveller as a missile towards the destination instead of just instantly displacing them.
 
    */
 
    globals
 
        private constant real NETWORK_TIMEOUT = .03125
        private constant real DESTINATION_MESSAGE_COOLDOWN = 5.
        private constant string MSG_INVALID = "Invalid target."
        private constant string MSG_PICK_DESTINATION = "Right-click on your destination to teleport."
  
        integer array NumberOfEntrances
        integer array NumberOfExits
        integer array EntrancesNetworkInstance
        integer array ExitsNetworkInstance
        integer array TeleportTimeInstance
        private integer array TravellerInstance
        private integer array NumberOfInstances
        private boolean array isPlayMessageOnCooldown
  
        //Events
        real WAYGATE_EVENT = 0.
        unit WAYGATE_SOURCE = null
        unit WAYGATE_TARGET = null
        unit WAYGATE_TRAVELLER = null
        boolean INTERRUPT_TELEPORT = false
    endglobals
 
    //G stands for Gate
    private struct G extends array
        static Table EXITS
        static Table ENTRANCES
        static Table TIMEOUT
    endstruct
 
    private struct PreventMessageSpam
  
        integer play_num
  
        private method destroy takes nothing returns nothing
            call this.deallocate()
        endmethod
  
        private static method Timer takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype this = GetTimerData(t)
            set isPlayMessageOnCooldown[this.play_num] = false
            call ReleaseTimer(t)
            call this.destroy()
            set t = null
        endmethod
  
        static method start takes integer player_number returns nothing
            local thistype this = allocate()
            set this.play_num = player_number
            set isPlayMessageOnCooldown[player_number] = true
            call TimerStart(NewTimerEx(this), DESTINATION_MESSAGE_COOLDOWN, false, function thistype.Timer)
        endmethod
  
    endstruct
 
    struct orderStunTimer
  
        unit u
        boolean b
  
        private static method zeroTimerStun takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype this = GetTimerData(t)
            local CustomWaygate ti = TravellerInstance[GetUnitUserData(this.u)]
            set ti.ignoreOrder = true
            set ST_IGNORE_ORDERS = this.b
            call IssueImmediateOrderById(this.u, 851973)//order stunned
            set ST_IGNORE_ORDERS = false
            set ti.ignoreOrder = false
            set this.u = null
            set this.b = false
            call ReleaseTimer(t)
            call this.deallocate()
            set t = null
        endmethod
  
        static method start takes unit u, boolean b returns nothing
            local thistype this = allocate()
            set this.u = u
            set this.b = b
            call TimerStart(NewTimerEx(this), 0., false, function thistype.zeroTimerStun)
        endmethod
  
    endstruct
 
    struct CustomWaygate
  
        unit source
        unit target
        unit traveller
        real countdown
        trigger trig
        trigger networkTrig
        boolean isTeleportCancelled
        boolean ignoreOrder
        boolean teleportReady
        integer timeoutSlot
        integer tunnelInstance
        timer clock
  
  
        private method destroy takes nothing returns nothing
            set this.source = null
            set this.target = null
            set this.traveller = null
            if this.trig != null then
                call DestroyTrigger(this.trig)
                set this.trig = null
            endif
            if this.clock != null then
                call ReleaseTimer(this.clock)
                set this.clock = null
            endif
            set this.networkTrig = null
            call this.deallocate()
        endmethod
  
  
        private static method simpleTeleport takes unit source, unit target, unit traveller returns nothing
            local real      xSource
            local real      ySource
            local real      xTraveller
            local real      yTraveller
            local real      xDestination
            local real      yDestination
            local real      angle
            local real      distance
            local thistype  this = allocate()
      
            set xTraveller = GetUnitX(traveller)
            set yTraveller = GetUnitY(traveller)
            set xSource = GetUnitX(source)
            set ySource = GetUnitY(source)
            set angle = Atan2(yTraveller - ySource, xTraveller - xSource)
            set distance = SquareRoot( (xTraveller - xSource)*(xTraveller - xSource) + (yTraveller - ySource)*(yTraveller - ySource) )
            set xDestination = GetUnitX(target) + Cos(angle) * distance
            set yDestination = GetUnitY(target) + Sin(angle) * distance
  
            call SetUnitX(traveller, xDestination)
            call SetUnitY(traveller, yDestination)
      
            call orderStunTimer.start(traveller, true)
        endmethod
  
  
        private static method cancelTeleport takes nothing returns nothing
            local unit traveller = GetTriggerUnit()
            local unit source = GetOrderTargetUnit()
            local integer id_T = GetUnitUserData(traveller)
            local thistype this = TravellerInstance[id_T]
      
            if not (source == this.source and GetIssuedOrderId() == ORDER_SMART) then
          
                //Events
                set WAYGATE_SOURCE = this.source
                set WAYGATE_TARGET = this.target
                set WAYGATE_TRAVELLER = this.traveller
                set WAYGATE_EVENT = 2.
                set WAYGATE_SOURCE = null
                set WAYGATE_TARGET = null
                set WAYGATE_TRAVELLER = null
                set INTERRUPT_TELEPORT = false
                set WAYGATE_EVENT = 0.
          
                if this.clock == null then
                    call this.destroy()
                else
                    set this.isTeleportCancelled = true
                endif
      
            endif
      
            set source = null
            set traveller = null
            //This instance is bound to isTeleportCancelled, which doesn't repeat and will
            //terminate on its own when it expires.
        endmethod
  
        private static method teleportTimer takes nothing returns nothing
            local timer     t = GetExpiredTimer()
            local thistype  this = GetTimerData(t)
            local integer   id_T = GetUnitUserData(this.traveller)
      
            call DestroyTrigger(this.trig)
            set this.trig = null
      
            set G.EXITS = this.tunnelInstance
            if not this.isTeleportCancelled and UnitAlive(this.source) and UnitAlive(this.traveller) and G.EXITS.unit[1] != null and TravellerInstance[id_T] == this then
          
          
                //Events
                set WAYGATE_SOURCE = this.source
                set WAYGATE_TARGET = this.target
                set WAYGATE_TRAVELLER = this.traveller
                set WAYGATE_EVENT = .5
          
                if not INTERRUPT_TELEPORT then
                    call CustomWaygate.simpleTeleport(this.source, this.target, this.traveller)
                    set WAYGATE_EVENT = 1.
                else
                    set WAYGATE_EVENT = 2.
                endif
          
                set WAYGATE_SOURCE = null
                set WAYGATE_TARGET = null
                set WAYGATE_TRAVELLER = null
                set INTERRUPT_TELEPORT = false
                set WAYGATE_EVENT = 0.
          
            endif
      
            set NumberOfInstances[id_T] = NumberOfInstances[id_T] - 1
            if NumberOfInstances[id_T] == 0 then
                set TravellerInstance[id_T] = 0
            endif
            call this.destroy()
            set t = null
        endmethod
  
  
        static method initiateTeleportation takes unit source, unit target, unit traveller, real timeout, integer tunnel_id returns nothing
      
            local integer id_T
            local thistype this
      
            if timeout > 0. then
          
                set this = allocate()
                set id_T = GetUnitUserData(traveller)
                set NumberOfInstances[id_T] = NumberOfInstances[id_T] + 1
                set TravellerInstance[id_T] = this
                set this.source = source
                set this.target = target
                set this.traveller = traveller
                set this.isTeleportCancelled = false
                set this.tunnelInstance = tunnel_id
                set this.trig = CreateTrigger()
                call TriggerRegisterUnitEvent(this.trig, traveller, EVENT_UNIT_ISSUED_TARGET_ORDER)
                call TriggerRegisterUnitEvent(this.trig, traveller, EVENT_UNIT_ISSUED_POINT_ORDER)
                call TriggerRegisterUnitEvent(this.trig, traveller, EVENT_UNIT_ISSUED_ORDER)
                call TriggerAddCondition(this.trig, function thistype.cancelTeleport)
                set this.clock = NewTimerEx(this)
                call TimerStart(this.clock, timeout, false, function thistype.teleportTimer)
          
                //Events
                set WAYGATE_SOURCE = source
                set WAYGATE_TARGET = target
                set WAYGATE_TRAVELLER = traveller
                set WAYGATE_EVENT = 1.5
                set WAYGATE_SOURCE = null
                set WAYGATE_TARGET = null
                set WAYGATE_TRAVELLER = null
                set INTERRUPT_TELEPORT = false
                set WAYGATE_EVENT = 0.
          
            else
          
                //Events
                set WAYGATE_SOURCE = source
                set WAYGATE_TARGET = target
                set WAYGATE_TRAVELLER = traveller
                set WAYGATE_EVENT = .5
                      
                if not INTERRUPT_TELEPORT then
                    call CustomWaygate.simpleTeleport(source, target, traveller)
                    set WAYGATE_EVENT = 1.
                else
                    set WAYGATE_EVENT = 2.
                endif
          
                set WAYGATE_SOURCE = null
                set WAYGATE_TARGET = null
                set WAYGATE_TRAVELLER = null
                set INTERRUPT_TELEPORT = false
                set WAYGATE_EVENT = 0.
          
            endif
        endmethod
  
  
        //NETWORK TELEPORT
        static method networkTeleport takes nothing returns nothing
            local unit      traveller   = GetTriggerUnit()
            local unit      target      = GetOrderTargetUnit()
            local thistype  this        = TravellerInstance[GetUnitUserData(traveller)]
            local integer   id_S        = GetUnitUserData(this.source)
            local integer   i           = 0
            local boolean   isUnitValid = false
            local timer t
            local player play
      
            if not this.ignoreOrder then
                if target != null and GetIssuedOrderId() == ORDER_SMART then
                    if target != this.source then
                        set G.EXITS = ExitsNetworkInstance[id_S]
                        loop
                            set i = i + 1
                            if target == G.EXITS.unit[I] then
                                set isUnitValid = true
                            endif
                            exitwhen i > NumberOfExits[id_S] or isUnitValid
                        endloop
                   
                        if isUnitValid then
                            set this.target = target
                            set this.timeoutSlot = i
                            call orderStunTimer.start(traveller, false)
                        else
                            if SmartTrack_IsATracker[GetUnitUserData(target)] then
                                set this.isTeleportCancelled = true
                            else
                                static if LIBRARY_BlizzardMessage then
                                    call BlizzardMessage(MSG_INVALID, "|cffffcc00", 31, GetOwningPlayer(traveller))
                                else
                                    set play = GetOwningPlayer(traveller)
                                    if play == GetLocalPlayer() then
                                        call DisplayTimedTextToPlayer(play, 0, 0, 5., MSG_INVALID)
                                    endif
                                endif
                                call orderStunTimer.start(traveller, false)
                            endif
                        endif
                    endif
                else
                    set this.isTeleportCancelled = true
                endif
            endif
       
        endmethod
   
        private static method networkTeleportCountdown takes nothing returns nothing
            local timer     t       = GetExpiredTimer()
            local thistype  this    = GetTimerData(t)
            local integer   id_T    = GetUnitUserData(this.traveller)
       
            if not this.teleportReady then
                set this.countdown = this.countdown - NETWORK_TIMEOUT
                if this.countdown <= 0. then
                    set this.teleportReady = true
               
                    //Events
                    set WAYGATE_SOURCE = this.source
                    set WAYGATE_TARGET = this.target
                    set WAYGATE_TRAVELLER = this.traveller
                    set WAYGATE_EVENT = 1.75
                    set WAYGATE_SOURCE = null
                    set WAYGATE_TARGET = null
                    set WAYGATE_TRAVELLER = null
                    set INTERRUPT_TELEPORT = false
                    set WAYGATE_EVENT = 0.
                endif
            endif
       
            if this.isTeleportCancelled then
       
                set NumberOfInstances[id_T] = NumberOfInstances[id_T] - 1
                if NumberOfInstances[id_T] == 0 then
                    set TravellerInstance[id_T] = 0
                endif
                call DestroyTrigger(this.networkTrig)
                set this.networkTrig = null
           
                //Events
                set WAYGATE_SOURCE = this.source
                set WAYGATE_TARGET = this.target
                set WAYGATE_TRAVELLER = this.traveller
                set WAYGATE_EVENT = 2.
                set WAYGATE_SOURCE = null
                set WAYGATE_TARGET = null
                set WAYGATE_TRAVELLER = null
                set INTERRUPT_TELEPORT = false
                set WAYGATE_EVENT = 0.
               
                call this.destroy()
           
            elseif this.target != null and this.teleportReady then
               
                //Events
                set WAYGATE_SOURCE = this.source
                set WAYGATE_TARGET = this.target
                set WAYGATE_TRAVELLER = this.traveller
                set WAYGATE_EVENT = .5
           
                set NumberOfInstances[id_T] = NumberOfInstances[id_T] - 1
                if NumberOfInstances[id_T] == 0 then
                    set TravellerInstance[id_T] = 0
                endif
                call DestroyTrigger(this.networkTrig)
                set this.networkTrig = null
           
                if not INTERRUPT_TELEPORT then
                    call CustomWaygate.simpleTeleport(this.source, this.target, this.traveller)
                    set WAYGATE_EVENT = 1.
                else
                    set WAYGATE_EVENT = 2.
                endif
           
                set WAYGATE_SOURCE = null
                set WAYGATE_TARGET = null
                set WAYGATE_TRAVELLER = null
                set INTERRUPT_TELEPORT = false
                set WAYGATE_EVENT = 0.
           
                call this.destroy()
           
            endif
            set t = null
        endmethod
   
   
        static method preTeleportation takes nothing returns nothing
            local unit      source = ST_EVENT_TRACKER
            local unit      traveller = ST_EVENT_UNIT
            local integer   id_S = GetUnitUserData(source) //The Waygate
            local integer   id_T = GetUnitUserData(traveller)
            local integer   player_number
            local integer   i
            local player    play
            local thistype  this = TravellerInstance[id_T]
       
            if this.source != source then
           
                if this != 0 then
                    set this.isTeleportCancelled = true
                endif
           
                set G.EXITS = ExitsNetworkInstance[id_S]
                set G.TIMEOUT = TeleportTimeInstance[id_S]
       
                if NumberOfExits[id_S] == 1 then
                    call initiateTeleportation(source, G.EXITS.unit[1], traveller, G.TIMEOUT.real[1], G.EXITS)
                elseif NumberOfExits[id_S] > 1 then
                    set this = allocate()
                    set NumberOfInstances[id_T] = NumberOfInstances[id_T] + 1
                    set TravellerInstance[id_T] = this
                    set this.source = source
                    set this.target = null
                    set this.traveller = traveller
                    set this.isTeleportCancelled = false
                    set this.teleportReady = false
                    set this.ignoreOrder = false
                    set this.countdown = G.TIMEOUT.real[1]
                    set this.networkTrig = CreateTrigger()
                    call TriggerRegisterUnitEvent(this.networkTrig, traveller, EVENT_UNIT_ISSUED_TARGET_ORDER)
                    call TriggerRegisterUnitEvent(this.networkTrig, traveller, EVENT_UNIT_ISSUED_POINT_ORDER)
                    call TriggerRegisterUnitEvent(this.networkTrig, traveller, EVENT_UNIT_ISSUED_ORDER)
                    call TriggerAddCondition(this.networkTrig, function thistype.networkTeleport)
                    set this.clock = NewTimerEx(this)
                    call TimerStart(this.clock, NETWORK_TIMEOUT, true, function thistype.networkTeleportCountdown)
               
                    set play = GetOwningPlayer(traveller)
                    set player_number = GetPlayerId(play)
                    if not isPlayMessageOnCooldown[player_number] then
                        static if LIBRARY_BlizzardMessage then
                            call BlizzardMessage(MSG_PICK_DESTINATION, "|cffffcc00", 132, play)
                        else
                            if play == GetLocalPlayer() then
                                call DisplayTimedTextToPlayer(play, 0, 0, 5., MSG_PICK_DESTINATION)
                            endif
                        endif
                        call PreventMessageSpam.start(player_number)
                    endif
               
                    //Events
                    set WAYGATE_SOURCE = source
                    set WAYGATE_TRAVELLER = traveller
                    set WAYGATE_EVENT = 1.5
                    set WAYGATE_SOURCE = null
                    set WAYGATE_TRAVELLER = null
                    set INTERRUPT_TELEPORT = false
                    set WAYGATE_EVENT = 0.
               
                endif
           
            endif
       
            set source = null
            set traveller = null
        endmethod
   
   
        static method destroyTunnel takes unit source, unit target returns nothing
            local integer id_S = GetUnitUserData(source)
            local integer id_T = GetUnitUserData(target)
            local integer i = 0
            local boolean isUnitValid = false
       
            //Source
            if ExitsNetworkInstance[id_S] != 0 then
                set G.EXITS = ExitsNetworkInstance[id_S]
                set G.TIMEOUT = TeleportTimeInstance[id_S]
                loop
                    set i = i + 1
                    if target == G.EXITS.unit[I] then
                        set isUnitValid = true
                    endif
                    exitwhen i > NumberOfExits[id_S] or isUnitValid
                endloop
                if isUnitValid then
                    call G.EXITS.remove(i)
                    call G.TIMEOUT.remove(i)
                    loop
                        exitwhen i > NumberOfExits[id_S]
                        set G.EXITS.unit[I] = G.EXITS.unit[i + 1]
                        set G.TIMEOUT.real[I] = G.TIMEOUT.real[i + 1]
                        set i = i + 1
                    endloop
                    call G.EXITS.remove(NumberOfExits[id_S])
                    call G.TIMEOUT.remove(NumberOfExits[id_S])
                    set NumberOfExits[id_S] = NumberOfExits[id_S] - 1
                endif
           
                if NumberOfExits[id_S] < 1 then
                    call G.EXITS.destroy()
                    call G.TIMEOUT.destroy()
                    set ExitsNetworkInstance[id_S] = 0
                    set TeleportTimeInstance[id_S] = 0
                endif
           
                set i = 0
                set isUnitValid = false
            endif
       
            //Target
            if EntrancesNetworkInstance[id_T] != 0 then
                set G.ENTRANCES = EntrancesNetworkInstance[id_T]
                loop
                    set i = i + 1
                    if source == G.ENTRANCES.unit[I] then
                        set isUnitValid = true
                    endif
                    exitwhen i > NumberOfEntrances[id_T] or isUnitValid
                endloop
                if isUnitValid then
                    call G.ENTRANCES.remove(i)
                    loop
                        exitwhen i > NumberOfEntrances[id_T]
                        set G.ENTRANCES.unit[I] = G.ENTRANCES.unit[i + 1]
                        set i = i + 1
                    endloop
                    call G.ENTRANCES.remove(NumberOfEntrances[id_T])
                    set NumberOfEntrances[id_T] = NumberOfEntrances[id_T] - 1
                endif
           
                if NumberOfEntrances[id_T] < 1 then
                    call G.ENTRANCES.destroy()
                    set EntrancesNetworkInstance[id_T] = 0
                endif
            endif
       
        endmethod
   
   
        static method createTunnel takes unit source, unit target, real teleport_distance, real timeout returns nothing
            local integer id_S = GetUnitUserData(source)
            local integer id_T = GetUnitUserData(target)
            local integer i
            local boolean isDuplicate = false
       
            if not (source == null and target == null) then
                //Source
                set G.EXITS = ExitsNetworkInstance[id_S]
                if G.EXITS == 0 then
                    set G.EXITS = Table.create()
                    set G.TIMEOUT = Table.create()
                    set ExitsNetworkInstance[id_S] = G.EXITS
                    set TeleportTimeInstance[id_S] = G.TIMEOUT
                    call CreateSmartOrderTracker(source, teleport_distance)
                    //Death Event?
                else
                    set i = 1
                    loop
                        if target == G.EXITS.unit[I] then
                            set isDuplicate = true
                        endif
                        exitwhen i > NumberOfExits[id_S] or isDuplicate
                        set i = i + 1
                    endloop
                endif
           
                if not isDuplicate then
                    set NumberOfExits[id_S] = NumberOfExits[id_S] + 1
                    set G.EXITS.unit[NumberOfExits[id_S]] = target
                    set G.TIMEOUT.real[NumberOfExits[id_S]] = timeout
                else
                    return
                endif
           
                //Target
                set NumberOfEntrances[id_T] = NumberOfEntrances[id_T] + 1
                set G.ENTRANCES = EntrancesNetworkInstance[id_T]
                if G.ENTRANCES == 0 then
                    set G.ENTRANCES = Table.create()
                    set EntrancesNetworkInstance[id_T] = G.ENTRANCES
                endif
                set G.ENTRANCES.unit[NumberOfEntrances[id_T]] = source
            endif
       
        endmethod
   
    endstruct
 
    private function init takes nothing returns nothing
        call RegisterSmartTrackEvent(ST_UNIT_IN_RANGE, function CustomWaygate.preTeleportation)
    endfunction
 
endlibrary

JASS:
library DockingSystem initializer init /*
 
    By Spellbound
 
    ==== DESCRIPTION ====
 
    DockingSystem intends to replicate the Undead Haunted Gold Mine system by allowing you to
    'dock' units (aka plugs) into a stationary unit, usually a building. The system comes with
    the ability to move docking ports (aka sockets) so if you want to dock units to another
    unit that's in motion, you may do that as well. This system comes with a lot of features
    so take a moment and read through the API.
 
 
    ==== INSTALLATION ====
 
    • Copy-paste this trigger into your map.
    • Copy-paste the required libraries into your map.
    • The system is installed! Copy-paste the onDock Event trigger or make your own
      event trigger and start coding.
 
    */ requires /*
   
        */ SmartTrack,  /* Comes with this map
        */ Table        /* https://www.hiveworkshop.com/threads/snippet-new-table.188084/
 
    ==== API ====
 
 
    SETUP and MODIFICATION________
 
    DockingSystem.createStation( takes unit station, real socket_trigger_distance )
        This sets up a unit as the station that will then be given sockets for units to 'plug'
        into. Sockets are added separately.
   
    DockingSystem.terminateStation( takes unit station )
        This terminates a station instance. Doing do will destroy all added sockets and undock
        any docked units.
   
    DockingSystem.addSocket( takes unit station, real x, real y, real z, real facing_angle )
        Creates a socket and adds it to the desires station. Sockets can have any coordinates
        you desire them to have so they can be where ever you want them to be. The last argument,
        facing_angle, will determine the plug's facing when it docks.
   
        IMPORTANT: This method will return the socket instance. If you wish to refer to this
        specific socket at a later date (eg: if you want to move it) you can do so by storing
        the instance in an integer variable.
   
    DockingSystem.removeSocket( takes P socket_instance )
        Removes a socket from a station. Socket instances are obtained upon addSocket.
   
    DockingSystem.moveSocket( takes P socket_instance, real x, real y, real z, real facing_angle )
        Alter the parameters of a socket. If a unit is currently docked in that socket, it will
        move with it. Socket instances are obtained upon addSocket.
   
    DockingSystem.addSocketPointer( takes unit pointer, real range, boolean kill_pointer, P socket_instance )
        Creates a unit that acts as a pointer to a specific socket. When units dock into a station
        the chosen socket is done at random, but with a pointer you can tell the unit to dock into
        a specific socket. Pointers are proxies for the station itself with their own docking range,
        so placing one too far from the station will defacto teleport plugs around. If the
        socket_isntance is null, then they will teleport units to random open sockets. Socket
        instances are obtained upon addSocket.
   
    DockinSystem.dock( takes unit plug, P socket_instance )
        Docks a unit into an empty plug. If the plug is currently occupied, nothing will happen.
   
    DockinSystem.undock( takes unit plug )
        Undocks a unit from the station it's currently plug to and fires an undocking event.
        Useful for when you want to handle the undocking manually.
   
    DockingSystem.undockAll( takes unit station )
        Flushes all docked unit from a station. The sockets themselves remain intact.
   
    RegisterDockEvent( takes integer ev, code c )
        Sets up events for docking. The available events are DS_ON_PRE_DOCK, DS_ON_DOCK and
        DS_ON_UNDOCK. They should be fairly self-explanatory, but just in case I'm just
        projecting, here's what they do:
   
        DS_ON_PRE_DOCK  == BEFORE the plug has moved into place.
        DS_ON_DOCK      == After the plug has moved into place.
        DS_ON_UNDOCK    == When docking ends.
   
    EVENTS________
 
    DS_EVENT_PLUG
        the unit that is about to dock/has docked/has undocked.
 
    DS_EVENT_STATION
        the station
 
    DS_INTERRUPT_DOCKING
        set this to true on DS_ON_PRE_DOCK to stop docking from happening.
 
    DS_MANUAL_UNDOCKING
        set his to trur on DS_ON_PRE_DOCK to stop automatic undocking triggers from being created.
        This means that ordering plugs to move or to stop will no undock them, so you will have
        to undock them manually.
 
    call GetSocketX(socket_instance)
        returns the x co-ordinate of the socket.
   
    call GetSocketY(socket_instance)
        returns the y co-ordinate of the socket.
   
    call GetSocketZ(socket_instance)
        returns the z co-ordinate of the socket.
   
    call GetSocketFacing(socket_instance)
        returns the facing angle of the socket in degrees.
 
    call GetSocketAngleFromStation(socket_instance, offset)
        returns the angle between the socket and the station in radians. This is useful for when
        you want to rotate the socket around the station with DockinSystem.moveSocket(), for
        example. The offset value is for when you want to make sure your sockets are referrencing
        the actual center of the station model as some models tend to offset their center by -16
        both on their x and y coordinate. If you station center appears off, try setting the offset
        to 16 or modifying the collision size in the object editor.
   
    */
 
 
    globals
 
        integer array DS_Instance
        integer array DS_SocketId
        integer array DS_PlugCount
   
        unit DS_EVENT_PLUG = null
        unit DS_EVENT_STATION = null
        boolean DS_INTERRUPT_DOCKING = false
        boolean DS_MANUAL_UNDOCKING = false
   
        integer DS_ON_PRE_DOCK = 1
        integer DS_ON_DOCK = 2
        integer DS_ON_UNDOCK = 3
        private trigger array EventTrig
   
    endglobals
 
 
    //P for Plug
    struct P
 
        real x
        real y
        real z
        real facing
        unit plug
        unit station
        unit socketPointer
        boolean killSocketPointer
        boolean ignoreOrder
        integer slot
        trigger trig
   
        method destroy takes nothing returns nothing
            if this.plug != null then
                call DockingSystem.undock(this.plug)
            endif
            set this.station = null
            set this.slot = 0
            if this.killSocketPointer then
                call UnitApplyTimedLife(this.socketPointer, 'BTLF', .01)
                set this.killSocketPointer = false
            endif
            if this.socketPointer != null then
                set DS_SocketId[GetUnitUserData(this.socketPointer)] = 0
                set this.socketPointer = null
            endif
            call this.deallocate()
        endmethod
   
    endstruct
 
 
    //S for Station
    private struct S
        static Table SOCKETS
    endstruct
 
    private function FireEvent takes unit plug, unit station, integer ev returns nothing
        set DS_EVENT_PLUG = plug
        set DS_EVENT_STATION = station
        call TriggerEvaluate(EventTrig[ev])
        set DS_EVENT_PLUG = null
        set DS_EVENT_STATION = null
        set DS_INTERRUPT_DOCKING = false
    endfunction
 
    struct DockingSystem
   
        integer tableInstance
        integer maxSockets
   
   
        private static method zeroTimerStun takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local P p = GetTimerData(t)
            set p.ignoreOrder = true
            set ST_IGNORE_ORDERS = true
            call IssueImmediateOrderById(p.plug, 851973)//order stunned
            set ST_IGNORE_ORDERS = false
            set p.ignoreOrder = false
            call ReleaseTimer(t)
            set t = null
        endmethod
   
   
        static method undock takes unit plug returns nothing
       
            local integer id_P = GetUnitUserData(plug)
            local P p = DS_SocketId[id_P]
            local integer id_S = GetUnitUserData(p.station)
       
            set DS_PlugCount[id_S] = DS_PlugCount[id_S] - 1
       
            call SetUnitPathing(plug, true)
            set p.plug = null
            call DestroyTrigger(p.trig)
            set p.trig = null
            call FireEvent(plug, p.station, DS_ON_UNDOCK)
            set DS_SocketId[id_P] = 0
       
        endmethod
   
   
        static method undockAll takes unit station returns nothing
       
            local integer id_S = GetUnitUserData(station)
            local thistype this = DS_Instance[id_S]
            local integer i
            local P p
       
            if this != 0 then
                set S.SOCKETS = this.tableInstance
                set i = this.maxSockets
                loop
                    set p = S.SOCKETS.integer[i]
                    if p.plug != null then
                        call undock(p.plug)
                    endif
                    set i = i - 1
                    exitwhen i <= 0
                endloop
            endif
       
        endmethod
   
   
        private static method trackOrders takes nothing returns nothing
            local unit      plug        = GetTriggerUnit()
            local unit      station     = GetOrderTargetUnit()
            local P p = DS_SocketId[GetUnitUserData(plug)]
       
            if not p.ignoreOrder then
                if station == p.station and GetIssuedOrderId() == ORDER_SMART then
                    call DockingSystem.undock(plug)
                    call DockingSystem.dock(plug, p)
                else
                    call DockingSystem.undock(plug)
                endif
            endif
       
            set plug = null
            set station = null
        endmethod
   
   
        static method dock takes unit plug, P p returns nothing
       
            local integer id_S = GetUnitUserData(p.station)
       
            call FireEvent(plug, p.station, DS_ON_PRE_DOCK)
       
            if not DS_INTERRUPT_DOCKING then
                call SetUnitPathing(plug, false)
                call SetUnitX(plug, p.x)
                call SetUnitY(plug, p.y)
                if GetUnitAbilityLevel(plug, 852155) > 0 then
                    call UnitAddAbility(plug, 852155)
                    call UnitRemoveAbility(plug, 852155)
                endif
                call SetUnitFlyHeight(plug, p.z, 0.)
                call SetUnitFacing(plug, p.facing)
                call TimerStart(NewTimerEx(p), 0., false, function thistype.zeroTimerStun)
           
                set p.plug = plug
                set DS_SocketId[GetUnitUserData(plug)] = p
           
                if not DS_MANUAL_UNDOCKING then
                    set p.trig = CreateTrigger()
                    call TriggerRegisterUnitEvent(p.trig, plug, EVENT_UNIT_ISSUED_TARGET_ORDER)
                    call TriggerRegisterUnitEvent(p.trig, plug, EVENT_UNIT_ISSUED_POINT_ORDER)
                    call TriggerRegisterUnitEvent(p.trig, plug, EVENT_UNIT_ISSUED_ORDER)
                    call TriggerAddCondition(p.trig, function thistype.trackOrders)
                else
                    set DS_MANUAL_UNDOCKING = false
                endif
           
                set DS_PlugCount[id_S] = DS_PlugCount[id_S] + 1
           
                call FireEvent(plug, p.station, DS_ON_DOCK)
            else
                set DS_INTERRUPT_DOCKING = false
            endif
       
        endmethod
   
   
        static method dockPrep takes nothing returns nothing
            local unit      station = ST_EVENT_TRACKER
            local unit      plug = ST_EVENT_UNIT
            local boolean   firstPass = false
            local boolean   exitLoop = false
       
            //local integer   player_number
            //local player    play
       
            local thistype  this
            local integer   i
            local P p
       
            set this = DS_Instance[GetUnitUserData(station)]
            set S.SOCKETS = this.tableInstance
            set i = GetRandomInt(0, this.maxSockets)
            loop
                set i = i + 1
                if i > this.maxSockets then
                    set i = 1
                    set firstPass = true
                endif
                exitwhen (i > this.maxSockets and firstPass) or exitLoop
           
                set p = S.SOCKETS.integer[i]
           
                if p.plug == null then
                    call DockingSystem.dock(plug, p)
                    set exitLoop = true
                endif
           
            endloop
       
            set station = null
            set plug = null
        endmethod
   
   
        static method GetSocketX takes P p returns real
            return p.x
        endmethod
   
        static method GetSocketY takes P p returns real
            return p.y
        endmethod
   
        static method GetSocketZ takes P p returns real
            return p.z
        endmethod
   
        static method GetSocketFacing takes P p returns real
            return p.facing
        endmethod
   
        static method GetSocketAngleFromStation takes P p, real offset returns real
            return Atan2(p.y - (GetUnitY(p.station) + offset), p.x -  (GetUnitX(p.station) + offset))
        endmethod
   
   
        static method removeSocket takes P p returns nothing
       
            local thistype this = DS_Instance[GetUnitUserData(p.station)]
            local integer i = p.slot
       
            if this != 0 then
           
                set S.SOCKETS = this.tableInstance
           
                call p.destroy()
                call S.SOCKETS.remove(i)
           
                loop
                    set S.SOCKETS.integer[i] = S.SOCKETS.integer[i + 1]
                    set i = i + 1
                    exitwhen i >= this.maxSockets
                endloop
           
                set this.maxSockets = this.maxSockets - 1
           
            endif
       
        endmethod
   
   
        static method moveSocket takes P p, real x, real y, real z, real facing returns nothing
            set p.x = x
            set p.y = y
            set p.z = z
            set p.facing = facing
            if p.plug != null then
                //Move plug
                call SetUnitX(p.plug, x)
                call SetUnitY(p.plug, y)
                call SetUnitFacing(p.plug, facing)
                call SetUnitFlyHeight(p.plug, z, 0.)
            endif
        endmethod
   
   
        static method addSocketPointer takes unit pointer, real range, boolean kill_pointer, P p returns nothing
            if pointer != null then
                set p.socketPointer = pointer
                set p.killSocketPointer = kill_pointer
                set DS_SocketId[GetUnitUserData(pointer)] = p
                call CreateSmartOrderTracker(pointer, range)
            endif
        endmethod
   
   
        static method addSocket takes unit station, real x, real y, real z, real facing_angle returns P
   
            local thistype this = DS_Instance[GetUnitUserData(station)]
            local P p
       
            if this != 0 then
                set S.SOCKETS = this.tableInstance
                set this.maxSockets = this.maxSockets + 1
           
                set p = allocate()
                set p.x = x
                set p.y = y
                set p.z = z
                set p.facing = facing_angle
                set p.station = station
                set p.slot = this.maxSockets
           
                set S.SOCKETS.integer[this.maxSockets] = p
            endif
       
            return p
        endmethod
   
   
        static method terminateStation takes unit station returns nothing
       
            local integer id_S = GetUnitUserData(station)
            local thistype this = DS_Instance[id_S]
            local integer i
       
            if this != 0 then
                set S.SOCKETS = this.tableInstance
                set i = this.maxSockets
                loop
                    call removeSocket(S.SOCKETS.integer[i])
                    set i = i - 1
                    exitwhen i <= 0
                endloop
                set this.maxSockets = 0
                call CancelSmartOrderTracker(station)
                set DS_Instance[id_S] = 0
                call S.SOCKETS.destroy()
                call this.deallocate()
            endif
       
        endmethod
   
   
        static method createStation takes unit station, real socket_trigger_distance returns nothing
   
            local integer id_S = GetUnitUserData(station)
            local thistype this = DS_Instance[id_S]
       
            if this == 0 then
                set S.SOCKETS = Table.create()
                set this = allocate()
                set this.tableInstance = S.SOCKETS
                set this.maxSockets = 0
                call CreateSmartOrderTracker(station, socket_trigger_distance)
                set DS_Instance[id_S] = this
                set DS_PlugCount[id_S] = 0
            endif
       
        endmethod
   
   
    endstruct
 
    function RegisterDockEvent takes integer ev, code c returns nothing
        call TriggerAddCondition(EventTrig[ev], Condition(c))
    endfunction
 
    private function init takes nothing returns nothing
   
        set EventTrig[DS_ON_PRE_DOCK] = CreateTrigger()
        set EventTrig[DS_ON_DOCK] = CreateTrigger()
        set EventTrig[DS_ON_UNDOCK] = CreateTrigger()
 
        call RegisterSmartTrackEvent(ST_UNIT_IN_RANGE, function DockingSystem.dockPrep)
    endfunction
 
 
endlibrary

----------------------------------------------------------------

30/09/2017

So I've been looking for a way for the game to keep track of which unit right-clicked which unit for some resources I've been trying to code (like a custom Waygate), so I figured I'd create a library designed specifically for that. However, I'm not entirely certain as to its implementation.

The way this works is that you initially identify which units will be Trackers. The units will have a range associated with them so that any other unit that is given the Smart order will be added to an array variable. Upon reaching the defined range of the Tracker, a real variable event fires. Issuing any order other than right-clicking on another Tracker will remove the unit from the array variable. I call these units Smarties because creativity is hard and I just wanted to get on with coding the thing.

A demo map is attached if you wish to the system in action.

JASS:
library SmartTrack initializer init
 
    /*
 
        ==== 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 https://www.hiveworkshop.com/threads/gui-unit-event-v2-4-0-0.201641/
   
   
        ==== API ====
   
        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.
       
    */
 
    globals
 
        constant integer ORDER_SMART = 851971
   
        unit ST_EVENT_UNIT = null
        unit ST_EVENT_TRACKER = null
        boolean ST_IGNORE_ORDERS = false
   
        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 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 SmartyTrig
        private trigger array EventTrig
        public boolean array IsATracker
    endglobals
 
    native UnitAlive takes unit u returns boolean
 
    private function FireEvent takes unit tracker, unit u, integer ev returns nothing
        set ST_EVENT_UNIT = u
        set ST_EVENT_TRACKER = tracker
        call TriggerEvaluate(EventTrig[ev])
        set ST_EVENT_UNIT = null
        set ST_EVENT_TRACKER = null
    endfunction
 
    function RegisterSmartTrackEvent takes integer ev, code c returns nothing
        call TriggerAddCondition(EventTrig[ev], Condition(c))
    endfunction
 
    private function RemoveSmarty takes unit u returns nothing
        local integer id = GetUnitUserData(u)
        local integer i = SmartySlot[id]
        local unit tracker = Tracker[id]
        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)
        set tracker = null
    endfunction
 
 
    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)
            endif
        endloop
        set u = null
        return false
    endfunction
 
 
    private function TrackOrders takes nothing returns nothing
        local unit orderSource = GetTriggerUnit()
        local unit orderTarget = GetOrderTargetUnit()
        local integer idSource = GetUnitUserData(orderSource)
        local integer idTarget = GetUnitUserData(orderTarget)
        local integer i
        if not ST_IGNORE_ORDERS then
            if orderTarget != null and GetIssuedOrderId() == ORDER_SMART then
                if IsATracker[idTarget] then
                    if Tracker[idSource] == null then //the unit is not in the Smarty list
                        if IsUnitInRange(orderSource, orderTarget, TrackerRange[idTarget]) then
                            //Events
                            call FireEvent(orderTarget, orderSource, ST_TRACKING_STARTED)
                            call FireEvent(orderTarget, orderSource, ST_UNIT_IN_RANGE)
                        else
                            set MaxSmarties = MaxSmarties + 1
                            set Smarty[MaxSmarties] = orderSource
                            set SmartySlot[idSource] = MaxSmarties
                            set Tracker[idSource] = orderTarget
                            call FireEvent(orderTarget, orderSource, ST_TRACKING_STARTED)
                        endif
                    else
                        if IsUnitInRange(orderSource, orderTarget, TrackerRange[idTarget]) then
                            call RemoveSmarty(orderSource)
                            //Events
                            call FireEvent(orderTarget, orderSource, ST_TRACKING_STARTED)
                            call FireEvent(orderTarget, orderSource, ST_UNIT_IN_RANGE)
                        else
                            set Tracker[idSource] = orderTarget
                        endif
                    endif
                endif
            else
                if Tracker[idSource] != null then
                    call RemoveSmarty(orderSource)
                endif
            endif
        endif
    endfunction
 
 
    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 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
 
 
    function CreateSmartOrderTracker takes unit source, real range returns nothing
        local integer id = GetUnitUserData(source)
        if TrackerTrig[id] == null then
            set TrackerTrig[id] = CreateTrigger()
        else
            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
        set IsATracker[id] = true
        if not IsTriggerEnabled(SmartyTrack) then
            call EnableTrigger(SmartyTrack)
        endif
    endfunction
 
 
    private function init takes nothing returns nothing
 
        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, function TrackOrders)
   
        call DisableTrigger(SmartyTrack)
    endfunction

endlibrary

There are 2 things that are bugging me about this:

1. I have preset events (eg: call TriggerRegisterAnyUnitEventBJ(SmartyTrack, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER) ) that will track the Smarties whenever they are given an order... but those will also fire for every unit in the game that's given an order. I've wanted to create a trigger and assign those orders specifically for that unit, but wouldn't that just be inefficient and an unnecessary performance hog? How costly is it to create triggers with three events on the fly like that? I know things like CreateUnit() have high overhead, but I have no idea what the performance cost of CreateTrigger() is.

2. I'm having to track the Tracker itself by binding it to the handleId of the trigger with a hashtable. Is there any other way to do this? local unit tracker = LoadUnitHandle(TRIGGER_TRACK, GetHandleId(GetTriggeringTrigger()), 1).

If you have a moment, could you review the code? It's quite simple and initially started out with the ability to create multiple instances of range-detection, but I couldn't really find a use for it so I scrapped it.
 
Last edited:
Okay, so I made an alternate version that creates triggers for every unit that right-click on a Tacker. I don't know how efficient/inefficient this is, so I kinda need feedback :/
This uses OrderEvent to track the right-clicks for every unit in the game, so already it has multiple requirements, which some might dislike.

JASS:
library SmartTrack initializer init requires OrderEvent

    /*
        Alternate version that creates a trigger for every unit that right-clicks a Tracker.
        This trigger is destroyed when tracking terminates.
    */
   
    globals
   
        //EVENT VARIABLES
        real ST_EVENT = 0.
        unit ST_EVENT_UNIT = null
        unit ST_EVENT_TRACKER = null
       
        private constant integer ORDER_SMART = 851971
        private hashtable TRIGGER_TRACK = InitHashtable()
        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 boolean array IsATracker
        private trigger array TrackerTrig
        private trigger array SmartyTrig
    endglobals
   
   
    function RemoveSmarty takes unit u returns nothing
        local integer id = GetUnitUserData(u)
        local integer i = SmartySlot[id]
        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
    endfunction
   
   
    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
                set ST_EVENT_UNIT = u
                set ST_EVENT_TRACKER = tracker
                set ST_EVENT = 1.
                call RemoveSmarty(u)
                set ST_EVENT_UNIT = null
                set ST_EVENT_TRACKER = null
                set ST_EVENT = 0.
            endif
        endloop
        set u = null
        return false
    endfunction
   
   
    private function CancelTracking takes nothing returns nothing
        local unit orderSource = GetTriggerUnit()
        call RemoveSmarty(orderSource)
        call DestroyTrigger(SmartyTrig[GetUnitUserData(orderSource)])
        call BJDebugMsg("Tracking Terminated")
        set orderSource = null
    endfunction
   
   
    private function StartTracking takes nothing returns nothing
        local unit orderSource = GetTriggerUnit()
        local unit orderTarget = GetOrderTargetUnit()
        local integer idSource = GetUnitUserData(orderSource)
        local integer idTarget = GetUnitUserData(orderTarget)
        if IsATracker[idTarget] then
            if Tracker[idSource] == null then //the unit is not in the Smarty list
                if IsUnitInRange(orderSource, orderTarget, TrackerRange[idTarget]) then
                    //Events
                    set ST_EVENT_UNIT = orderSource
                    set ST_EVENT_TRACKER = orderTarget
                    set ST_EVENT = 1.
                    set ST_EVENT_UNIT = null
                    set ST_EVENT_TRACKER = null
                    set ST_EVENT = 0.
                else
                    set MaxSmarties = MaxSmarties + 1
                    set Smarty[MaxSmarties] = orderSource
                    set SmartySlot[idSource] = MaxSmarties
                    set Tracker[idSource] = orderTarget
                    if SmartyTrig[idSource] == null then
                        set SmartyTrig[idSource] = CreateTrigger()
                    else
                        call DestroyTrigger(SmartyTrig[idSource])
                        set SmartyTrig[idSource] = CreateTrigger()
                    endif
                    call TriggerRegisterUnitEvent(SmartyTrig[idSource], orderSource, EVENT_UNIT_ISSUED_POINT_ORDER)
                    call TriggerRegisterUnitEvent(SmartyTrig[idSource], orderSource, EVENT_UNIT_ISSUED_ORDER)
                    call TriggerAddCondition(SmartyTrig[idSource], function CancelTracking)
                endif
            else
                if IsUnitInRange(orderSource, orderTarget, TrackerRange[idTarget]) then
                    call RemoveSmarty(orderSource)
                    call DestroyTrigger(SmartyTrig[idSource])
                    call BJDebugMsg("Tracking Terminated")
                    //Events
                    set ST_EVENT_UNIT = orderSource
                    set ST_EVENT_TRACKER = orderTarget
                    set ST_EVENT = 1.
                    set ST_EVENT_UNIT = null
                    set ST_EVENT_TRACKER = null
                    set ST_EVENT = 0.
                else
                    set Tracker[idSource] = orderTarget
                endif
            endif
        endif
    endfunction
   
   
    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 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
   
   
    function CreateSmartOrderTracker takes unit source, real range returns nothing
        local integer id = GetUnitUserData(source)
        if TrackerTrig[id] == null then
            set TrackerTrig[id] = CreateTrigger()
        else
            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
        set IsATracker[id] = true
        if not IsTriggerEnabled(SmartyTrack) then
            call EnableTrigger(SmartyTrack)
        endif
    endfunction


    private function init takes nothing returns nothing
        call RegisterOrderEvent(851971, function StartTracking)
    endfunction
   
endlibrary
 
I'm fiddling with a filtering system for the libraries that use SmartTrack and I'm wondering if it's at all needed or if there isn't a more efficient way of doing it? Maybe I should just use The Filtering System...

This library creates a Table that stores all, eg, unittypes that you want the Tracker to filter through and then checks them. You can also store a boolean in it to make sure you filter for the proper unittype. For example, filtering for ground unit will return false on structure, so instead you filter for flying unit, but false. This way you'll filter for everything that's not flying, including structures and amphibious units.

Filters are added one by one to the Tracker through the FilterAddUnitType() function.

JASS:
library SmartTrackFilter requires Table
  
  
    globals
        integer array STF_UNIT_ID
        integer array STF_CLAS_ID
        integer array STF_PLAY_ID
        integer array STF_UNIT_BOOLEAN_ID
        integer array STF_CLAS_BOOLEAN_ID
        integer array STF_PLAY_BOOLEAN_ID
    endglobals
  
  
    //STF stands for SmartTrackFilter
    struct STF
        static Table UNITTYPE
        static Table PLAYER
        static Table CLASSIFICATION
        static Table UNITTYPE_BOOLEAN
        static Table PLAYER_BOOLEAN
        static Table CLASSIFICATION_BOOLEAN
    endstruct
  
  
    function FilterUnitType takes unit u, unit filterUnit, integer tableInstance returns boolean
        local integer i = 0
        local integer unitId = GetUnitTypeId(filterUnit)
        local integer id = GetUnitUserData(u)
        local boolean passCheck = true
        set STF.UNITTYPE = STF_UNIT_ID[id]
        set STF.UNITTYPE_BOOLEAN = STF_UNIT_BOOLEAN_ID[id]
        loop
            set i = i + 1
            if STF.UNITTYPE_BOOLEAN.boolean[i] then
                if unitId != STF.UNITTYPE.integer[i] then
                    set passCheck = false
                endif
            else
                if unitId == STF.UNITTYPE.integer[i] then
                    set passCheck = false
                endif
            endif
            exitwhen i > STF.UNITTYPE.integer[0] or not passCheck
        endloop
        return passCheck
    endfunction
  
  
    function FilterRemoveUnitType takes unit u, integer unitTypeId returns nothing
        local integer id = GetUnitUserData(u)
        local integer i = 0
        local boolean exitLoop
        set STF.UNITTYPE = STF_UNIT_ID[id]
        set STF.UNITTYPE_BOOLEAN = STF_UNIT_BOOLEAN_ID[id]
        if STF.UNITTYPE > 0 then
            loop
                set i = i + 1
                if STF.UNITTYPE.integer[i] == unitTypeId then
                    call STF.UNITTYPE.remove(i)
                    loop
                        set STF.UNITTYPE.integer[i] = STF.UNITTYPE.integer[i + 1]
                        set STF.UNITTYPE_BOOLEAN.boolean[i] = STF.UNITTYPE_BOOLEAN.boolean[i + 1]
                        set i = i + 1
                        exitwhen i > STF.UNITTYPE.integer[0]
                    endloop
                    set STF.UNITTYPE.integer[0] = STF.UNITTYPE.integer[0] - 1
                endif
                exitwhen i > STF.UNITTYPE.integer[0]
            endloop
        endif
    endfunction
  
  
    function FilterAddUnitType takes unit u, integer unitTypeId, boolean b returns nothing
        local integer id = GetUnitUserData(u)
        local integer i = 0
        local integer limit
        local boolean passCheck = true
        set STF.UNITTYPE = STF_UNIT_ID[id]
        if STF.UNITTYPE == 0 then
            set STF.UNITTYPE = Table.create()
            set STF.UNITTYPE_BOOLEAN = Table.create()
            set STF_UNIT_ID[id] = STF.UNITTYPE
            set STF_UNIT_BOOLEAN_ID[id] = STF.UNITTYPE_BOOLEAN
        endif
        //the [0] slot stores the number of parameters to filter through
        set limit = STF.UNITTYPE.integer[0]
        loop
            set i = i + 1
            if STF.UNITTYPE.integer[i] == unitTypeId then
                set passCheck = false
            endif
            exitwhen i > limit or not passCheck
        endloop
        if passCheck then
            set limit = limit + 1
            set STF.UNITTYPE.integer[0] = limit
            set STF.UNITTYPE.integer[limit] = unitTypeId
            set STF.UNITTYPE_BOOLEAN.boolean[limit] = b
        endif
    endfunction
  
  
endlibrary
 
Last edited:
Status
Not open for further replies.
Top