Cohadar
master of fugue
- Reaction score
- 209
JASS:
//==============================================================================
// PII -- Perfect Item Indexing by Cohadar -- v1.1
//==============================================================================
//
// PURPOUSE:
// * Extending ItemUserData()
// * This is basically perfect hashing algorithm for items
//
// HOW TO USE:
// * You have only one function at your disposal GetItemIndex(item)
// It will return a unique index for each item in range 1..8190
//
// * What you will do with that index is all up to you
// Of course using global arrays is the most obvious choice
// Advanced jassers will think of a couple of more clever ones ofc.
//
// * There are also 2 textmacros available at the end of library code
// They can be used for easier attaching to items
// PII for structs
// PII_PROPERTY for unit, integer, real, boolean and string variables
//
// PROS:
// * You can use any number of systems that previously could not work together
// because they all required exclusive access of ItemUserData()
//
// * You can also use this to add bonus mod data structs to items
//
// * System removes dead items after they decay
// (unbelievably but this is not done automatically by the game)
//
// * There are no SetItemIndex() or ClearItemIndex() functions here
// Each item gets assigned one index that cannot be changed
// That index will be automatically recycled some time after item death.
//
// CONS:
// * This system uses ItemUserData() itself
// That means that if you want to use PII you must recode
// any other system that uses ItemUserData() to use GetItemIndex() instead
//
// * If you use ItemIndex for arrays of non-native types (timers, effects and similar)
// you must check if timer on that index already exists before you create a new one.
// This can happen if GetItemIndex() assigns a recycled index (index of some dead and removed item)
// to the newly created item for which you intended to use timer for
//
// * All in all this is not a sys for newbies, it gives great power,
// but it requires knowledge and carefull handling
//
// DETAILS:
// * System is using item array to keep track of all items with an index.
// Array is periodically checked for dead items, when dead item is found, index is recycled.
//
// * Indexes have "decay time" to prevent bugs from using RemoveItem() function
// or from selling items to shop (game instantly removes sold items)
// When item decays it will be removed by PII in case it was not removed by game!
//
// * PII automatically registers any picked item, this ensures
// all runes, tomes and charged items are properly removed from the game
//
// SYSTEM COMMANDS: (debug mode only, red player only)
//
// * type -pii to display indexes of items in inventory of currently selected unit
// * type -piistats to display some stats
//
// THANKS TO:
// * Vexorian - for his help with PUI textmacro
//
// HOW TO IMPORT:
// * Just create a trigger named PII
// * convert it to text and replace the whole trigger text with this one
//
//==============================================================================
library PII initializer Init
//==============================================================================
globals
//-----------------------------------------------
private constant real INDEX_DECAY_TIME = 5. // seconds
//-----------------------------------------------
private constant real PERIOD = 1.0 // check one item every second
//-----------------------------------------------
private constant integer DECAY_TICKS = R2I(INDEX_DECAY_TIME/PERIOD)
//-----------------------------------------------
private integer array Indexz
private item array Itemz
private integer array Decayz
private integer array Tickz
private integer maxindex = 0
private integer topindex = 0
private integer decayindex = 0
private integer checker = 0
private integer decayer = 0
private integer tick = 0
endglobals
//==============================================================================
private function PeriodicRecycler takes nothing returns boolean
local integer temp
set tick = tick + 1
if topindex > decayindex then
set checker = checker + 1
if checker > topindex then
set checker = decayindex + 1
endif
if (GetWidgetLife(Itemz[checker])<0.405) then
set decayindex = decayindex + 1
// Itemz[0] is temp, index 0 is never used by system
set Itemz[0] = Itemz[checker]
set Itemz[checker] = Itemz[decayindex]
set Itemz[decayindex] = Itemz[0]
// swap(checker, decayindex)
set temp = Indexz[checker]
set Indexz[checker] = Indexz[decayindex]
set Indexz[decayindex] = temp
set Decayz[decayindex] = DECAY_TICKS
set Tickz[decayindex] = tick
endif
endif
if decayindex > 0 then
set decayer = decayer + 1
if decayer > decayindex then
set decayer = 1
endif
set Decayz[decayer] = Decayz[decayer] - (tick-Tickz[decayer])
if Decayz[decayer] > 0 then
set Tickz[decayer] = tick
else
// swap(decayer, decayindex)
set temp = Indexz[decayer]
set Indexz[decayer] = Indexz[decayindex]
set Indexz[decayindex] = temp
// if item is not removed by the game after it decays, we do it.
if GetItemUserData(Itemz[decayindex]) != 0 then
//debug call BJDebugMsg("PII_Removing: "+GetItemName(Itemz[decayindex]))
call RemoveItem(Itemz[decayindex])
endif
set Itemz[decayindex] = Itemz[topindex]
// swap(decayindex, topindex)
set temp = Indexz[decayindex]
set Indexz[decayindex] = Indexz[topindex]
set Indexz[topindex] = temp
set decayindex = decayindex - 1
set topindex = topindex - 1
endif
endif
return false
endfunction
//==============================================================================
// Main and only function exported by this library
//==============================================================================
function GetItemIndex takes item whichItem returns integer
local integer index
debug if whichItem == null then
debug call BJDebugMsg("|c00FF0000ERROR: PII - Index requested for null item")
debug return 0
debug endif
set index = GetItemUserData(whichItem)
if index == 0 then
set topindex = topindex + 1
if topindex > maxindex then
set maxindex = topindex
set Indexz[topindex] = topindex
endif
set index = Indexz[topindex]
set Itemz[topindex] = whichItem
call SetItemUserData(whichItem, index)
set index = GetItemUserData(whichItem)
// this happens when requesting item index for removed item
debug if index == 0 then
debug call BJDebugMsg("|c00FFCC00WARNING: PII - Bad item handle")
debug endif
//debug call BJDebugMsg("|c00FFCC00PII: Index assigned #" + I2S(index))
endif
return index
endfunction
//==============================================================================
// Registers all picked items, this ensures runes tomes and charged items
// are properly removed after they decay
//==============================================================================
private function RegisterConditions takes nothing returns boolean
call GetItemIndex(GetManipulatedItem())
return false
endfunction
//==============================================================================
private function DisplayStats takes nothing returns nothing
call BJDebugMsg("=============================================")
call BJDebugMsg("Biggest index ever = " + I2S(maxindex))
call BJDebugMsg("Indexes in use = " + I2S(topindex-decayindex))
call BJDebugMsg("Decaying indexes = " + I2S(decayindex))
call BJDebugMsg("Released indexes = " + I2S(maxindex-topindex))
call BJDebugMsg("=============================================")
endfunction
//===========================================================================
private function DisplaySelectedEnum takes nothing returns nothing
local integer i
local item m
call BJDebugMsg("Unit: "+GetUnitName(GetEnumUnit()))
set i = 0
loop
exitwhen i>=6
set m = UnitItemInSlot(GetEnumUnit(), i)
if m != null then
call BJDebugMsg( "PII(" + ( GetItemName(m) + ( ") = " + I2S(GetItemUserData(m)) ) ) )
endif
set i = i + 1
endloop
endfunction
//===========================================================================
private function DisplaySelected takes nothing returns nothing
local group g = CreateGroup()
call SyncSelections()
call GroupEnumUnitsSelected(g, Player(0), null)
call ForGroup(g, function DisplaySelectedEnum)
call DestroyGroup(g)
set g = null
endfunction
//==============================================================================
private function Init takes nothing returns nothing
local trigger trig
set trig = CreateTrigger()
call TriggerRegisterTimerEvent( trig, PERIOD, true )
call TriggerAddCondition( trig, Condition(function PeriodicRecycler) )
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_PICKUP_ITEM )
call TriggerAddCondition( trig, Condition( function RegisterConditions ) )
debug set trig = CreateTrigger()
debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-pii", true )
debug call TriggerAddAction( trig, function DisplaySelected )
debug set trig = CreateTrigger()
debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-piistats", true )
debug call TriggerAddAction( trig, function DisplayStats )
endfunction
endlibrary
//===========================================================================
// Allowed PII_PROPERTY TYPES are: item, integer, real, boolean, string
// Do NOT put handles that need to be destroyed here (timer, trigger, ...)
// Instead put them in a struct and use PII textmacro
//===========================================================================
//! textmacro PII_PROPERTY takes VISIBILITY, TYPE, NAME, DEFAULT
$VISIBILITY$ struct $NAME$
private static item array pii_item
private static $TYPE$ array pii_data
//-----------------------------------------------------------------------
// Returns default value when first time used
//-----------------------------------------------------------------------
static method operator[] takes item whichItem returns $TYPE$
local integer pii = GetItemIndex(whichItem)
if .pii_item[pii] != whichItem then
set .pii_item[pii] = whichItem
set .pii_data[pii] = $DEFAULT$
endif
return .pii_data[pii]
endmethod
//-----------------------------------------------------------------------
static method operator[]= takes item whichItem, $TYPE$ whichData returns nothing
local integer pii = GetItemIndex(whichItem)
set .pii_item[pii] = whichItem
set .pii_data[pii] = whichData
endmethod
endstruct
//! endtextmacro
//===========================================================================
// Never destroy PII structs directly.
// Use .release() instead, will call .destroy()
//===========================================================================
//! textmacro PII
private static item array pii_item
private static integer array pii_data
private static integer array pii_id
//-----------------------------------------------------------------------
// Returns zero if no struct is attached to item
//-----------------------------------------------------------------------
static method operator[] takes item whichItem returns integer
local integer pii = GetItemIndex(whichItem)
if .pii_data[pii] != 0 then
if .pii_item[pii] != whichItem then
// recycled handle detected
call .destroy(.pii_data[pii])
set .pii_item[pii] = null
set .pii_data[pii] = 0
endif
endif
return .pii_data[pii]
endmethod
//-----------------------------------------------------------------------
// This will overwrite already attached struct if any
//-----------------------------------------------------------------------
static method operator[]= takes item whichItem, integer whichData returns nothing
local integer pii = GetItemIndex(whichItem)
if .pii_data[pii] != 0 then
call .destroy(.pii_data[pii])
endif
set .pii_item[pii] = whichItem
set .pii_data[pii] = whichData
set .pii_id[whichData] = pii
endmethod
//-----------------------------------------------------------------------
// If you do not call release struct will be destroyed when item handle gets recycled
//-----------------------------------------------------------------------
method release takes nothing returns nothing
local integer pii= .pii_id[integer(this)]
call .destroy()
set .pii_item[pii] = null
set .pii_data[pii] = 0
endmethod
//! endtextmacro