library UnitDex uses GroupUtils, WorldBounds
/***************************************************************
*
* v1.1.8, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* An indexer assigns each unit an unique integer below the maximum array limit. This is useful for
* associating that index with an array, struct, or set of variables corresponding to data related to the unit.
*
* The default settings offer the best performance, however if you plan on having more than 8190 units
* on your map you need to enable DEFAULT_HASHTABLE below.
*
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
//! external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/* ________________________________________________________________________
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
module UnitDexConfig
// The raw code of the leave detection ability.
static constant integer DETECT_LEAVE_ABILITY = 'uDex'
// Use a hashtable instead of a unit's custom value
static constant boolean DEFAULT_HASHTABLE = false
// Allow debug messages (debug mode must also be on)
static constant boolean ALLOW_DEBUGGING = true
static constant boolean DEBUG_MODE = true
// Uncomment below to define a custom filter for indexing units
static method onFilter takes unit u returns boolean
return true
endmethod
end
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines except UnitRecycleId.
*
* function GetUnitId takes unit whichUnit returns integer
* function GetUnitById takes integer index returns unit
*
* function UnitDexEnable takes boolean flag returns nothing
* function UnitDexReset takes boolean flag returns nothing
* function UnitRecycleId takes unit u, boolean runEvents returns nothing
*
* function IsUnitIndexed takes unit u returns boolean
* function IsIndexingEnabled takes nothing returns boolean
*
* function GetIndexedUnit takes nothing returns unit
* function GetIndexedUnitId takes nothing returns integer
*
* function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
* function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
* function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
* _________________________________________________________________________
* 4. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex.Enabled = false // toggle the indexer
* UnitDex.Initialized // returns true if the preload timer has finished
* UnitDex.Count // returns the amount of units indexed
* UnitDex.Unit[0] // access the UnitDex array directly
* UnitDex.Group // a unit group containing every indexed unit (for enumeration)
* UnitDex.LastIndex // returns the last indexed unit's id
* _________________________________________________________________________
* 5. Public Variables
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* These are to be used with the "eventtype" argument of the event API:
*
* constant integer EVENT_UNIT_INDEX = 0
* constant integer EVENT_UNIT_DEINDEX = 1
* _________________________________________________________________________
* 6. Examples
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Allocate an instance for a unit
*
* local somestruct data = GetUnitId(unit)
*
* 2. Detect when a unit leaves the map
*
* function Exit takes nothing returns nothing
* call BJDebugMsg(GetUnitName(GetIndexedUnit()) + " has left.")
* endfunction
*
* call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
* _________________________________________________________________________
* 7. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex uses the TriggerRegisterEnterRegion native as well as enumerates all preplaced units on map initialization
* to index them. However, De-indeixing isn't as simple. The leave region event doesn't work properly for de-indexing so we have to give all units
* an ability based off of "Defend". When a unit dies, or are removed from the game they are issued the "undefend" order. So we catch that order and
* de-index the unit accordingly.
* _________________________________________________________________________
* 8. Notes
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* & This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
* & The object merger line should be commented out after saving and restarting.
* & All public functions are inlined except UnitRecycleId.
*
* [url]http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/[/url]
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
// Event types
constant integer EVENT_UNIT_INDEX = 0
constant integer EVENT_UNIT_DEINDEX = 1
// System variables
trigger array IndexTrig
integer Index = 0
integer E=-1
function UnitDexEnable takes boolean flag returns nothing
set UnitDex.Enabled = flag
endfunction
function GetUnitId takes unit whichUnit returns integer
if (UnitDex.DEFAULT_HASHTABLE) then
return LoadInteger(UnitDex.Hash, 0, GetHandleId(whichUnit))
else
return GetUnitUserData(whichUnit)
endif
endfunction
function GetUnitById takes integer index returns unit
if (UnitDex.DEFAULT_HASHTABLE) then
return LoadUnitHandle(UnitDex.Hash, 0, index)
else
return UnitDex.Unit[index]
endif
endfunction
function GetIndexedUnit takes nothing returns unit
if (UnitDex.DEFAULT_HASHTABLE) then
return LoadUnitHandle(UnitDex.Hash, 0, UnitDex.LastIndex)
else
return UnitDex.Unit[UnitDex.LastIndex]
endif
endfunction
function GetIndexedUnitId takes nothing returns integer
return UnitDex.LastIndex
endfunction
function IsUnitIndexed takes unit u returns boolean
return (GetUnitById(GetUnitUserData(u)) != null)
endfunction
function IsIndexingEnabled takes nothing returns boolean
return UnitDex.Enabled
endfunction
function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
return TriggerAddCondition(IndexTrig[eventtype], func)
endfunction
function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
call TriggerRemoveCondition(IndexTrig[eventtype], c)
endfunction
// TriggerVariableEvent not possible in Wurst/Jurst
// function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
// call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
// endfunction
/****************************************************************/
function UnitRecycleId takes unit u, boolean runEvents returns nothing
local integer i = GetUnitId(u)
if (i > 0 and u == GetUnitById(i)) then
set UnitDex.List[i]= Index
set Index = i
call GroupRemoveUnit(UnitDex.Group, u)
if (runEvents) then
set UnitDex.LastIndex = i
set E = EVENT_UNIT_DEINDEX
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
set E = -1
endif
set UnitDex.Unit[i] = null
endif
endfunction
struct UnitDex
static boolean Enabled = true
static integer LastIndex // UnitRecycleId needs access
static boolean Initialized=false
static group Group=CreateGroup()
static unit array Unit
static integer Count = 0
static integer array List
use UnitDexConfig
static hashtable Hash
private static method runInitTriggers takes nothing returns nothing
local integer i = 1
loop
exitwhen i == Count
set LastIndex = i
// run triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
set E = EVENT_UNIT_INDEX
set E = -1
set i = i + 1
endloop
set LastIndex = Count
set Initialized = true
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onEnter takes nothing returns boolean
local unit u = GetFilterUnit()
local integer i = GetUnitId(u)
local integer t = Index
if (i == 0 and Enabled) then
// If a filter was defined pass the unit through it.
if (not onFilter(u)) then
return false // check failed
endif
// Handle debugging
if (DEBUG_MODE and ALLOW_DEBUGGING and not DEFAULT_HASHTABLE) then
if (t == 0 and Count+1 >= JASS_MAX_ARRAY_SIZE) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached! (enable the DEFAULT_HASHTABLE option)")
set u = null
return false
endif
endif
// Add to group of indexed units
call GroupAddUnit(Group, u)
// Give unit the leave detection ability
call UnitAddAbility(u, DETECT_LEAVE_ABILITY)
call UnitMakeAbilityPermanent(u, true, DETECT_LEAVE_ABILITY)
// Create new index
if (Index != 0) then
set Index = List[t]
else
set Count = Count + 1
set t = Count
endif
set List[t] = -1
set LastIndex = t
set Unit[t] = u
// Set unit index
if (DEFAULT_HASHTABLE) then
call SaveInteger(Hash, 0, GetHandleId(u), t)
call SaveUnitHandle(Hash, 0, t, u)
else
call SetUnitUserData(u, t)
endif
if (Initialized) then
// Execute custom events registered with RegisterUnitIndexEvent
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_INDEX
// Reset so the event can occur again
set E = -1
endif
endif
set u = null
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit u
local integer i
// Check if order is undefend. I could check the ability level only but
// that would be a few function calls every time a unit is ordered instead
// of one.
if (GetIssuedOrderId() == 852056 and Enabled) then
set u = GetTriggerUnit()
// If unit was killed (not removed) then don't continue
if (GetUnitAbilityLevel(u, DETECT_LEAVE_ABILITY) != 0) then
return false
endif
set i = GetUnitId(u)
// If unit has been indexed then deindex it
if (i > 0 and u == GetUnitById(i) and i <= Count) then
// Recycle the index
set List[i] = Index
set Index = i
set LastIndex = i
// Remove to group of indexed units
call GroupRemoveUnit(Group, u)
// Execute custom events without any associated triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_DEINDEX
// Remove entry
if (DEFAULT_HASHTABLE) then
call RemoveSavedHandle(Hash, 0, i)
else
set Unit[i] = null
endif
// Reset so the event can occur again
set E = -1
endif
set u = null
endif
return false
endmethod
static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local boolexpr leave = Filter(function onLeave)
local boolexpr enter = Filter(function onEnter)
local player p
local unit u
// Begin to index units when they enter the map
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, enter)
call TriggerAddCondition(t, leave)
set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
loop
set p = Player(i)
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
// Index preplaced units
call GroupEnumUnitsOfPlayer(ENUM_GROUP, p, enter)
// Hide the detect ability from players
call SetPlayerAbilityAvailable(p, DETECT_LEAVE_ABILITY, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
if (DEFAULT_HASHTABLE) then
set Hash=InitHashtable()
endif
call TimerStart(CreateTimer(), 0, false, function runInitTriggers)
endmethod
endstruct
init
UnitDex.onInit()
end
endlibrary