- Joined
- Jan 9, 2005
- Messages
- 2,124
Any recommendations on how to improve this is welcome.
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
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