- Joined
- Mar 18, 2012
- Messages
- 1,716
Inventory source code
JASS:
// Inventory main code. Written by BPower. Version 1.1
library Inventory uses InventoryUserSetup, InventoryCore
//===================================================================================
// Inventory events.
// When an event trigger fires these values allow
// the action code to determine which event was dispatched.
// The functions listed below can be used to get information about the event.
//===================================================================================
globals
//=========================================
// Available Event Ids.
//=========================================
// RegisterInventoryEvent(eventId, code)
constant integer EVENT_INVENTORY_UNIT_EQUIP_ITEM = 0
constant integer EVENT_INVENTORY_UNIT_UNEQUIP_ITEM = 1
constant integer EVENT_INVENTORY_UNIT_PICKUP_ITEM = 2
constant integer EVENT_INVENTORY_UNIT_DROP_ITEM = 3
constant integer EVENT_INVENTORY_UNIT_PAWN_ITEM = 4
constant integer EVENT_INVENTORY_UNIT_BUY_ITEM = 5// ( Not working yet )
constant integer EVENT_INVENTORY_UNIT_TRADE_ITEM = 6// ( Not working yet )
//
constant integer MAX_INVENTORY_EVENTS = 7
endglobals
globals
//=======================================================
// Event Variables
//=======================================================
// These variables update before an event fires.
// Get their information through the set of functions below.
private integer eventId = -1
private item eventItem = null
private integer eventIndex = 0
private unit eventTarget = null
//
private trigger array events
endglobals
//=========================================
// Inventory trigger interface.
//=========================================
// Returns the trigger handle for the specified event id,
// in order to use it with the native trigger interface.
// Use this function very carefully.
function GetInventoryTrigger takes integer eventId returns trigger
return events[eventId]
endfunction
//=======================================================
// Trigger Inventory Event API.
//=======================================================
// These functions only return their proper information when called
// from within a registered trigger action function.
// For incorrect usage they will either return "null", "-1" or "0".
// Returns the triggering Inventory instance.
constant function GetTriggerInventory takes nothing returns Inventory
return eventIndex
endfunction
// Returns the interacting item.
constant function Inventory_GetTriggerItem takes nothing returns item
return eventItem
endfunction
// Returns the interacting unit handle.
// Can be null for some event ids.
constant function Inventory_GetEventTargetUnit takes nothing returns unit
return eventTarget
endfunction
// Returns the most recent event id.
constant function Inventory_GetTriggerEventId takes nothing returns integer
return eventId
endfunction
// Returns the inventory owning unit handle.
constant function Inventory_GetTriggerUnit takes nothing returns unit
return GetTriggerInventory().source
endfunction
// Registers code to available inventory events.
// Condition function not have to return a boolean, as the function outsmarts PJASS.
function RegisterInventoryEvent takes integer eventId, code func returns nothing
local boolean PJASS = false
call TriggerAddCondition(events[eventId], Condition(func))
//
debug call ThrowError(events[eventId] == null, "Inventory", "RegisterInventoryEvent", "eventId", 0, "Invalid event id [" + I2S(eventId) + "]!")
endfunction
//=======================================================
// Event Trigger Evaluation.
//=======================================================
// Fires inventory events while providing recursion safety.
private function FireEvent takes integer id, integer instance, item object, unit target returns nothing
// Save previous data.
local integer prevIndex = eventIndex
local integer prevId = eventId
local item prevItem = eventItem
local unit prevTarget = eventTarget
// Set current data and fire.
set eventId = id
set eventIndex = instance
set eventItem = object
set eventTarget = target
if IsTriggerEnabled(events[id]) then// TriggerEvaluate does also force disabled trigger to fire.
call TriggerEvaluate(events[id])
endif
// Restore previous data.
set eventIndex = prevIndex
set eventItem = prevItem
set eventTarget = prevTarget
set eventId = prevId
// Prevent handle leaks.
set prevTarget = null
set prevItem = null
endfunction
//=======================================================
// Event Trigger Initialization.
//=======================================================
// Internal process which is called as soon as possible via module initializer.
private function InitEventTriggers takes nothing returns nothing
local integer index = 0
loop
exitwhen (index == MAX_INVENTORY_EVENTS)
set events[index] = CreateTrigger()
set index = index + 1
endloop
endfunction
//=======================================================
// Internal On Create Event Trigger.
//=======================================================
// This event trigger fires when a new inventory instance is created
// and executes code in all structs using the InventoryStruct module.
// Use GetTriggerInventory() to get the most recent created instance.
globals
private constant trigger ON_CREATE = CreateTrigger()
endglobals
private function FireOnCreate takes integer index returns nothing
local integer prev = eventIndex
set eventIndex = index
call TriggerEvaluate(ON_CREATE)
set eventIndex = prev
endfunction
//=======================================================
// Module InventoryStruct.
//=======================================================
// Private interface for structs which should run code when an Inventory is created.
// Those structs must have a "static method onCreate takes Inventory new returns thistype".
module InventoryStruct
static method onCreateBefore takes nothing returns boolean
local Inventory created = GetTriggerInventory()
local boolean autostart = thistype.typeid != GetTradeTypeId() and thistype.typeid != GetMerchantTypeId()
local thistype this = thistype.onCreate(created)
if (0 != this) then
call created.addWindowOfType(thistype.typeid, this, autostart)
endif
return false
endmethod
private static method onInit takes nothing returns nothing
call TriggerAddCondition(ON_CREATE, Condition(function thistype.onCreateBefore))
endmethod
endmodule
//===================================================================================
// Inventory instance referencing.
// As an inventory belongs to a unit handle, you can get an instance
// by calling function GetUnitInventory(unit).
//===================================================================================
// Returns the Inventory instance for a unit.
// Returns 0 on incorrect usage.
function GetUnitInventory takes unit source returns Inventory
return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_UNIT_REFERENCE, GetHandleId(source))
endfunction
//===================================================================================
// Inventory interface sounds.
// These sound files run during various Inventory interface actions.
// For example when the backpack is full and therefore an item can't be picked up.
//===================================================================================
//=======================================================
// Sound File Registration.
//=======================================================
// Which sound file should be played is race based determined.
// Each sound file can and will be accessed via UISound.searchSoundFile(fileName)
// This function is called via module initializer on map initialization.
private function InitSoundFiles takes nothing returns nothing
call UISound.allocateIndex("Abilities\\Spells\\Items\\ResourceItems\\ReceiveGold.wav", 589, 10, .54)
call UISound.allocateIndex("Sound\\Interface\\QuestActivateWhat1.wav", 539, 10, .539)
call UISound.allocateIndex("Sound\\Interface\\PickUpItem.wav", 174, 10, .174)
call UISound.allocateIndex("Sound\\Interface\\HeroDropItem1.wav", 486, 10, .54)
//
call UISound.allocateIndex("Sound\\Interface\\Warning\\Orc\\GruntInventoryFull1.wav", 1567, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\Orc\\GruntNoLumber1.wav", 1602, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\Orc\\GruntNoGold1.wav", 1498, 10, 1.)
//
call UISound.allocateIndex("Sound\\Interface\\Warning\\Human\\KnightInventoryFull1.wav", 1498, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\Human\\KnightNoGold1.wav", 1486, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\Human\\KnightNoLumber1.wav", 1863, 10, 1.)
//
call UISound.allocateIndex("Sound\\Interface\\Warning\\Naga\\NagaInventoryFull1.wav", 2106, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\Naga\\NagaNoGold1.wav", 1808, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\Naga\\NagaNoLumber1.wav", 1576, 10, 1.)
//
call UISound.allocateIndex("Sound\\Interface\\Warning\\Nightelf\\SentinelInventoryFull1.wav", 1498, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\NightElf\\SentinelNoGold1.wav", 1323, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\NightElf\\SentinelNoLumber1.wav", 1501, 10, 1.)
//
call UISound.allocateIndex("Sound\\Interface\\Warning\\Undead\\NecromancerNoGold1.wav", 1805, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\Undead\\NecromancerNoLumber1.wav", 1904, 10, 1.)
call UISound.allocateIndex("Sound\\Interface\\Warning\\Undead\\NecromancerInventoryFull1.wav", 1521, 10, 1.)
endfunction
//===================================================================================
// Special API.
// ( Place holder. No special API is defined yet )
//===================================================================================
// NO CONTENT.
//===================================================================================
// InventoryCell code.
// Handles and stores information for each
// interactable cell within an Inventory instance.
//===================================================================================
// The textmacro is located at the very bottom of the Inventory library.
//! runtextmacro INVENTORY_CELL_CODE("")
//===================================================================================
// Interface Inventory.
// Inventory extends UIScreen and therefore doesn't have own interactable cells.
// It provides the common object orientated API for Inventory instances
// and serves as main screen for the entire in-game user interface surface.
//===================================================================================
struct Inventory extends UIScreen
//====================================
// Struct members.
//====================================
readonly unit source
//
readonly unit dummy
readonly real dummyY
readonly unit shop
// AddUnitAnimationProperties()
readonly string animation
// The global user setup defines a default font for all instances,
// you can change it at any time via this.setFont(font).
private integer fontType
method getFont takes nothing returns integer
return fontType
endmethod
method setFont takes integer newFont returns nothing
set fontType = newFont
endmethod
//===================================================================================
// Text messages and effects for Inventories.
// The following methods help to create a good
// ambience when using the Inventory interface in-game.
//===================================================================================
//=============================================
// Inventory sounds. ( Run for a local player)
//=============================================
// Based on race handle ids.
// Reference: 1 - Human, 2 - Orc, 3 - Undead, 4 - Nightelf, 5 - Demon, 7 - Other, 11 - Naga
//
// API: .iHaveNoRoom(), .notEnoughLumber(), .notEnoughGold()
// Runs for an insufficient backpack space.
private method iHaveNoRoom takes nothing returns nothing
local integer id = GetHandleId(GetUnitRace(source))
if (id == 1) or ((id != 11) and (id > 5)) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Human\\KnightInventoryFull1.wav", 10, 1.498)
elseif (id == 2) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Orc\\GruntInventoryFull1.wav", 10, 1.567)
elseif (id == 3) or (id == 5) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Undead\\NecromancerInventoryFull1.wav", 10, 1.521)
elseif (id == 4) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Nightelf\\SentinelInventoryFull1.wav", 10, 1.498)
elseif (id == 11) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Naga\\NagaInventoryFull1.wav", 10, 2.106)
endif
endmethod
// Runs for an insufficient lumber resource state.
private method notEnoughLumber takes nothing returns nothing
local integer id = GetHandleId(GetUnitRace(source))
if (id == 1) or ((id != 11) and (id > 5)) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Human\\KnightNoLumber1.wav", 10, 1.863)
elseif (id == 2) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Orc\\GruntNoLumber1.wav", 10, 1.602)
elseif (id == 3) or (id == 5) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Undead\\NecromancerNoLumber1.wav", 10, 1.904)
elseif (id == 4) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Nightelf\\SentinelNoLumber1.wav", 10, 1.501)
elseif (id == 11) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Naga\\NagaNoLumber1.wav", 10, 1.575)
endif
endmethod
// Runs for an insufficient gold resource state.
private method notEnoughGold takes nothing returns nothing
local integer id = GetHandleId(GetUnitRace(source))
if (id == 1) or ((id != 11) and (id > 5)) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Human\\KnightNoGold1.wav", 10, 1.486)
elseif (id == 2) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Orc\\GruntNoGold1.wav", 10, 1.1498)
elseif (id == 3) or (id == 5) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Undead\\NecromancerNoGold1.wav", 10, 1.904)
elseif (id == 4) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Nightelf\\SentinelNoGold1.wav", 10, 1.323)
elseif (id == 11) then
call audio.searchSoundFile("Sound\\Interface\\Warning\\Naga\\NagaNoGold1.wav", 10, 1.808)
endif
endmethod
//====================================
// Custom Player Text Message.
//====================================
// Similar to a player text message, but it doesn't delete the current chat on screen.
//
// API: .timedMsg(msg, duration), .errorMsg(msg)
private real spam
private real lastMsgTime
private textsplat lastMsgObject
// Called upon hiding an inventory interface or when a new message is generated.
private method hideLastMsg takes nothing returns nothing
if (UI_GetElapsedTime() < lastMsgTime) then
call SetTextSplatColor(lastMsgObject, 0, 0, 0, 0)
call SetTextSplatLifespan(lastMsgObject, 0.)
endif
endmethod
method timedMsg takes string msg, real duration returns nothing
local real elapsed = UI_GetElapsedTime()
local ARGB color = Inventory_GetMsgFontColor()
local textsplat t
if (elapsed >= spam) then
call hideLastMsg()// Let the previous text fade out invisible.
//
set spam = elapsed + .05// Spam protection.
set lastMsgTime = elapsed + duration
//
set t = CreateTextSplat(fontType)
call SetTextSplatFadepoint(t, duration - 1.)
call SetTextSplatLifespan(t, duration)
call SetTextSplatPermanent(t, false)
call SetTextSplatText(t, msg, INVENTORY_FONT_SIZE)
call SetTextSplatVisibility(t, enabled and GetLocalClient() == user)
call SetTextSplatColor(t, color.red, color.green, color.blue, color.alpha)
// Y position determined by try and error.
call SetTextSplatPos(t, centerX - t.width*.5, originY + height*.036, 0.)
set lastMsgObject = t
endif
endmethod
method errorMsg takes string msg returns boolean
call timedMsg(msg, 2.)
call audio.error()
return false
endmethod
//====================================
// Custom Player Text Box & Message.
//====================================
// This should definitly be outsourced to the UIPackage, but
// currently the API is not methodologically sound enough to work in any UI.
// The textbox is a bit more hardcoded than the rest. Get over it!
//
// API: .createTextBox(msg, posX, posY, width), .releaseTextBox()
private textsplat splat
private UIBorder box
private timer tmr
method releaseTextBox takes nothing returns nothing
local integer dex = 0
if (tmr != null) then
call ReleaseTimer(tmr)
call box.clear()
set tmr = null
call splat.unlock()
endif
endmethod
private static method fadeBox takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local textsplat t = splat
local real val
if (t.age >= t.fadepoint) then
set val = 1. - (t.age - t.fadepoint)/(t.lifespan - t.fadepoint)
if (val > 0.) then
call box.setColor(255, 255, 255, R2I(255*val))
else
call releaseTextBox()
endif
endif
endmethod
method createTextBox takes string msg, real posX, real posY, real width returns nothing
local integer dex = 1
local ARGB color = Inventory_GetMsgFontColor()
local textsplat t = CreateTextSplat(fontType)
local integer end
// Free previous timer, textsplat and box.
call releaseTextBox()
// Prepare the text.
call WordWrap.create(msg, width, false)
set end = WordWrap.getCount()
set msg = WordWrap.getLine(0)
loop
exitwhen (dex >= end)
set msg = msg + "\n" + WordWrap.getLine(dex)
set dex = dex + 1
endloop
// TextSplat API.
call SetTextSplatColor(t, color.red, color.green, color.blue, color.alpha)
call SetTextSplatText(t, msg, 4.146479*1.5*scale)
call SetTextSplatPos(t, posX - t.width*.5, posY - t.height*.5, 0.)
call SetTextSplatVisibility(t, GetLocalClient() == user)
call SetTextSplatPermanent(t, false)
call SetTextSplatFadepoint(t, 2.)
call SetTextSplatLifespan(t, 4.)
call t.lock()// Prevent double free.
set splat = t
// Create a box with extra 32 units in both dimensions.
call Inventory_SetTooltipBoxBorderFields(source)
call box.construct(posX, posY, t.width + tileSize*.5, t.height + tileSize*.5, .65*scale).show(enabled and GetLocalClient() == user)
set tmr = NewTimerEx(this)
call TimerStart(tmr, .031250000, true, function thistype.fadeBox)
endmethod
//====================================
// Custom Item Tooltip.
//====================================
// The tooltip is looked up in library CustomItemTooltip
// and displayed line by line in a multiboard for a local player.
//
// API: .showItemTooltip(item)
private method displayTooltip takes item i returns nothing
local CustomItem id = GetHandleId(i)
local string text = id.getName()
local integer gold = id.getGoldCost()
local integer wood = id.getWoodCost()
local integer end = GetItemTooltipSize(id)
local integer dex = end
local string icon
local UIBoard mb
// Check for item charges.
if (GetItemCharges(i) != 0) then
set text = text + " [" + I2S(GetItemCharges(i)) + "]"
endif
//
// Check for item resource costs.
if (gold != 0) and (wood != 0) then
set dex = dex + 3
elseif (wood != 0) or (gold != 0) then
set dex = dex + 2
endif
//
// Create the board with dex rows.
set mb = board.new(dex, 1)
set dex = 0
call MultiboardSetItemsWidth(mb.board, .16)
call MultiboardSetTitleText(mb.board, text)
//
loop
// Import the tooltip for "id" line by line.
exitwhen (dex == end)
set text = GetItemTooltipFragment(id, dex)
set icon = GetItemTooltipFragmentIcon(id, dex)
call mb.setText(dex, 0, text)
call mb.setIcon(dex, 0, icon)
call mb.setStyle(dex, 0, text != null, icon != null)
set dex = dex + 1
endloop
//
// Add extra lines for resource tooltips in the end.
if (gold != 0) then
set dex = dex + 1
call mb.setText(dex, 0, "Sell Value: " + I2S(gold))
call mb.setIcon(dex, 0, INVENTORY_RESOURCE_GOLD_ICON)
call mb.setStyle(dex, 0, true, true)
endif
if (wood != 0) then
set dex = dex + 1
call mb.setText(dex, 0, "Sell Value: " + I2S(wood))
call mb.setIcon(dex, 0, INVENTORY_RESOURCE_WOOD_ICON)
call mb.setStyle(dex, 0, true, true)
endif
//
// And finally show it to the player.
call MultiboardMinimize(mb.board, false)
endmethod
method showItemTooltip takes item i returns boolean
if (i == null) then
call board.release()
return false
endif
call displayTooltip(i)
return true
endmethod
//====================================
// Item Fxs & Animation Properties.
//====================================
// These informations update when equipping or unequipping an item.
// Run for the source unit and for the dummy ( if a dummy is set ).
//
// API: .addFx(item), .removeFx(item)
private Table effects
method removeFx takes item i returns nothing
local integer itemId = GetItemTypeId(i)
local integer id = GetCustomItemFxAbilityId(itemId)
local string ani = GetCustomItemAnimationProperty(itemId)
//
// Check for added effect handles.
if effects.has(itemId) then
set effects[itemId] = effects[itemId] - 1
if (effects[itemId] <= 0) then
call DestroyEffect(effects.effect[itemId])
call effects.handle.remove(itemId)
call effects.remove(itemId)
// Check for the dummy portrait.
if (dummy != null) then
call DestroyEffect(effects.effect[-itemId])
call effects.handle.remove(-itemId)
endif
endif
endif
//
// Check for added abilities to display a complex fx.
if effects.has(-itemId) then
set effects[-itemId] = effects[-itemId] - 1
if (effects[-itemId] <= 0) then
call effects.remove(itemId)
call UnitRemoveAbility(source, id)
if dummy != null then
call UnitRemoveAbility(dummy, id)
endif
endif
endif
//
// Check for the animation properties.
// Uses the "real" table field, because I'm running out of integers child keys :)!
if effects.real.has(itemId) then
set effects.real[itemId] = effects.real[itemId] - 1
if (effects.real[itemId] <= 0.5) then
call effects.real.remove(itemId)
call AddUnitAnimationProperties(source, ani, false)
if dummy != null then
call AddUnitAnimationProperties(dummy, ani, false)
endif
endif
endif
endmethod
method addFx takes item i returns nothing
local integer itemId = GetItemTypeId(i)
local integer id = GetCustomItemFxAbilityId(itemId)
local string pos = GetCustomItemFxPos(itemId)
local string ani = GetCustomItemAnimationProperty(itemId)
local string path = GetCustomItemFxFilePath(itemId)
//
// Effects via string path.
if (path != "") and (path != null) then
if not effects.has(itemId) then
if (GetLocalClient() != user) then
set path = ""
endif
set effects.effect[itemId] = AddSpecialEffectTarget(path, source, pos)
if (dummy != null) then
set effects.effect[-itemId] = AddSpecialEffectTarget(path, dummy, pos)
endif
endif
set effects[itemId] = effects[itemId] + 1
endif
//
// Animation properties.
if (ani != "") and (ani != null) then
if not effects.real.has(itemId) then
set animation = ani
call AddUnitAnimationProperties(dummy, ani, true)
if (dummy != null) then
call AddUnitAnimationProperties(source, ani, true)
endif
endif
set effects.real[itemId] = effects.real[itemId] + 1
endif
//
// Effects via ability.
if (id != 0) then
if not effects.has(-itemId) and UnitAddAbility(source, id) then
call UnitMakeAbilityPermanent(source, true, id)
if (dummy != null) then
call UnitAddAbility(dummy, id)
call UnitMakeAbilityPermanent(dummy, true, id)
endif
endif
set effects[-itemId] = effects[-itemId] + 1
endif
endmethod
//===================================================================================
// Core API for items and units.
// It's as close as possible to the
// native API for unit and item objects.
//===================================================================================
//====================================
// Cell Selection.
//====================================
// Basically always called when clicking a trackable in the entire interface.
readonly InventoryCell selected
readonly destructable selector
method deselect takes nothing returns nothing
if (selector != null) then
call RemoveDestructable(selector)
set selector = null
endif
set selected = 0
endmethod
method select takes InventoryCell cell returns nothing
local UIButton temp = cell.getButton()
call deselect()
set selected = cell
set selector = CreateDestructableZ(INVENTORY_CELL_SELECTOR_ID, temp.x, temp.y, temp.z, temp.facing, temp.scale*INVENTORY_CELL_SELECTOR_SCALE*scale, 0)
call ShowDestructable(selector, enabled and GetLocalClient() == user)
//
debug call ThrowWarning(not cell.exists, "Inventory", "select", "cell", cell, "Attempt to select invalid cell instance!")
endmethod
//====================================
// Sync With The Native Inventory.
//====================================
// Doesn't have much meaning so far as the native to custom inventory support is very low implemented.
private boolean syncReady
method syncWithNative takes nothing returns nothing
local UIWindow window = getWindowOfType(GetNativeInventoryTypeId())
local integer size = IMinBJ(UnitInventorySize(source), window.getButtonCount())
local integer index = 0
local item temp
local InventoryCell cell
//
set syncReady = false
loop
exitwhen (index == size)
set temp = UnitItemInSlot(source, index)
set cell = window.getButton(index).data
set cell.enabled = IsCustomItem(temp) or (temp == null)
if (temp == null) then
call cell.clear()
else
call cell.moveItem(temp, enabled)
endif
set index = index + 1
endloop
set temp = null
set syncReady = true
endmethod
//====================================
// Shop Detection.
//====================================
private method detectShops takes nothing returns nothing
local real dist = INVENTORY_MAXIMUM_SHOP_RANGE*INVENTORY_MAXIMUM_SHOP_RANGE + 1.
local real posX = GetUnitX(source)
local real posY = GetUnitY(source)
local real tx
local real ty
local real td
local unit u
call GroupEnumUnitsInRange(bj_lastCreatedGroup, posX, posY, INVENTORY_MAXIMUM_SHOP_RANGE, null)
loop
set u = FirstOfGroup(bj_lastCreatedGroup)
exitwhen u == null
call GroupRemoveUnit(bj_lastCreatedGroup, u)
if IsUnitAlly(u, user) and (GetUnitAbilityLevel(u, 'Apit') != 0) then
set tx = posX - GetUnitX(u)
set ty = posY - GetUnitY(u)
set td = tx*tx + ty*ty
if (td < dist) then
set shop = u
endif
endif
endloop
if (shop != null) then
call getWindowOfType(GetMerchantTypeId()).show(true)
endif
endmethod
//====================================
// On Show.
//====================================
// Inventory is a child struct of UIScreen, therefore it executes an onShow stub method
// each time instance.show(boolean) is called.
private method onShow takes boolean flag returns nothing
set shop = null
if (flag) then
call syncWithNative()
if hasWindowOfType(GetMerchantTypeId()) then
call detectShops()
endif
else
call releaseTextBox()
call hideLastMsg()
endif
endmethod
//====================================
// Searching Cells Of Struct Type.
//====================================
// This function helps us to find empty cells for an item
// inside a specific struct interface.
// Returns 0 if no matching cell was found.
private method searchCell takes integer structId, item i returns InventoryCell
local UIWindow window = getWindowOfType(structId)
local integer end = window.getButtonCount()
local integer dex = 0
local InventoryCell cell
loop
exitwhen (dex == end)
set cell = window.getButton(dex).data
if (cell.enabled) and (cell.getItem() == null) and (cell.matchesItem(i)) then
return cell
endif
set dex = dex + 1
endloop
return 0
endmethod
// This function does the same as searchCell, but only for the equipment interface.
// It has a much, much better performance, which is essential for equipment related API ( see below ).
//
private Table equipment // For "true" the function considers cells which are not empty.
private method searchEquipCell takes item whichItem, boolean forceAction returns InventoryCell
local integer pos = GetCustomItemClass(whichItem) + 1
local integer end = equipment[-pos]
local integer dex = 0
local integer forced = 0
local InventoryCell cell
//*
set pos = pos*JASS_MAX_ARRAY_SIZE
loop
exitwhen (dex == end)
set cell = equipment[pos + dex]
if (cell.enabled) then
if (cell.getItem() == null) then
return cell
endif
set forced = cell
endif
set dex = dex + 1
endloop
if (forceAction) then
return forced
endif
return 0
endmethod
// Internal function wrapper to search an suit-able cell for an item.
private method searchCellForItem takes UIWindow structId, item i, boolean forceAction returns InventoryCell
debug call ThrowWarning(i == null, "Inventory", "searchCellForItem", "object", this, "Invalid item handle ( null )!")
//
if not hasWindowOfType(structId) then
return 0
// Equipment has an individual lookup algorithm for better performance.
elseif (structId == GetEquipmentTypeId()) then
return searchEquipCell(i, forceAction)
endif
return searchCell(structId, i)
endmethod
//====================================
// Swapping Cell Content.
//====================================
// Works only for cells of the same struct.
method swapCellContents takes InventoryCell a, InventoryCell b returns boolean
if (a.getTypeId() == b.getTypeId()) and (b.enabled) and (a.enabled) then
if ((a.getItem() == null) or b.matchesItem(a.getItem())) and ((b.getItem() == null) or a.matchesItem(b.getItem())) then
call a.swap(b, enabled)
return true
endif
endif
//
debug call ThrowWarning(not a.exists, "Inventory", "swapCellContents", "a", a, "Attempt to swap to an invalid cell instance!")
debug call ThrowWarning(not b.exists, "Inventory", "swapCellContents", "b", b, "Attempt to swap to an invalid cell instance!")
return false
endmethod
//====================================
// Merging Item Charges.
//====================================
// REQUIRES AN UPDATE.
private method checkStructItemCharges takes item whichItem, integer structId returns boolean
local CustomItem object = GetHandleId(whichItem)
local InventoryCell source = object.getCell()
local UIWindow window = getWindowOfType(structId)
local integer size = window.getButtonCount()
local integer index = 0
local InventoryCell cell
if (GetItemType(whichItem) != ITEM_TYPE_CHARGED) or not (source.exists) then
return false
endif
loop
exitwhen (index == size)
set cell = window.getButton(index).data
if (cell.data.getItemId() == GetItemTypeId(whichItem)) and (source != cell) and (cell.enabled) then
exitwhen cell.data.mergeCharges(whichItem)
endif
set index = index + 1
endloop
return not (object.exists)
endmethod
//===============================
// Equipment realted API.
//===============================
// For a specific item handle.
method unitHasItemEquipped takes item whichItem returns boolean
local integer pos = GetCustomItemClass(whichItem) + 1
local integer end = equipment[-pos]
local integer dex = 0
set pos = pos*JASS_MAX_ARRAY_SIZE
loop
exitwhen (dex == end)
if (InventoryCell(equipment[pos + dex]).getItem() == whichItem) then
return true
endif
set dex = dex + 1
endloop
return false
endmethod
// For an item of type id.
method unitHasItemIdEquipped takes integer itemId returns boolean
local integer pos = GetCustomItemIdClass(itemId) + 1
local integer end = equipment[-pos]
local integer dex = 0
set pos = pos*JASS_MAX_ARRAY_SIZE
loop
exitwhen (dex == end)
if (InventoryCell(equipment[pos + dex]).data.getItemId() == itemId) then
return true
endif
set dex = dex + 1
endloop
return false
endmethod
//===========================================
// Mimic Native Unit Item API & Specific API
//===========================================
//
// Any interface related.
// .unitRemoveItem(item) - UnitRemoveItem(unit, item)
// Equipment related.
// .unitUnequipItemToSlot(item, slot)
// .method unitEquipItemToSlot(item, slot, forceAction)
// .method unitEquipItem(item)
// Backpack related.
// .method unitAddItemToSlot(item, slot)
// .method unitAddItem(item)
// Merchant related.
// .method unitPawnItem(item, shop)
// .method unitBuyItem(item, shop) ( Not implemented yet )
// Player player interaction related.
// .method unitTradeItem(item, otherInventory) ( Not implemented yet )
// Item related
// .method mergeItems(item1, item2)
// .method socketItem(item, gem) ( Not implemented yet )
// .method unsocketItem(item) ( Not implemented yet )
method unitRemoveItem takes item whichItem returns boolean
local CustomItem object = GetHandleId(whichItem)
local InventoryCell cell = object.getCell()
// Check cell and item owner.
if (cell.exists) and ((object.getOwner()) == source) then
// Check if the item is equipped.
if (cell.getTypeId() == GetEquipmentTypeId()) then
// Check for twohand item.
if IsCustomItemTwohand(whichItem) then
call InventoryCell.removeShadow(whichItem)
endif
call FireEvent(EVENT_INVENTORY_UNIT_UNEQUIP_ITEM, this, whichItem, null)
endif
call cell.clear()
//
// Place and fire.
call object.placeInMap(GetUnitX(source), GetUnitY(source))
call FireEvent(EVENT_INVENTORY_UNIT_DROP_ITEM, this, whichItem, null)
return true
endif
return false
endmethod
method unitUnequipItemToSlot takes item whichItem, InventoryCell slot returns boolean
local CustomItem object = GetHandleId(whichItem)
local InventoryCell cell = object.getCell()
// Invalid operation.
if ((object.getOwner()) != source) or (cell.getTypeId() != GetEquipmentTypeId()) or (whichItem == null) then
return false
endif
// slot == 0, Try again.
if (slot == 0) then
set slot = searchCellForItem(GetBackpackTypeId(), whichItem, false)
if (slot == 0) then
return unitRemoveItem(whichItem)
endif
endif
// Check if it's a twohand item.
if IsCustomItemTwohand(whichItem) then
call InventoryCell.removeShadow(whichItem)
endif
// Move and fire.
call slot.moveItem(whichItem, enabled)
call FireEvent(EVENT_INVENTORY_UNIT_UNEQUIP_ITEM, this, whichItem, null)
return true
endmethod
// Equips items to the unit.
method unitEquipItemToSlot takes item whichItem, InventoryCell slot, boolean forceAction returns boolean
local InventoryCell cell
local CustomItem object = GetHandleId(whichItem)
// Invalid operation.
if (slot.getTypeId() != GetEquipmentTypeId()) or (object.getOwner() != source) then
debug call ThrowWarning((object.getOwner() != source), "Inventory", "unitEquipItemToSlot", "owner", this, "Can't equip an item of different owners!")
return false
//
// Missing requirements.
elseif not equipment.has(-(GetCustomItemClass(whichItem) + 1)) then
return errorMsg(GetUnitName(source) + ": I can not wear " + object.getName() + "!")
//
// Invalid cell.
elseif not slot.matchesItem(whichItem) then
return errorMsg(object.getName() + " does not fit in a " + slot.class.name + " slot!")
endif
// Check if it's a twohand item.
if (IsCustomItemTwohand(whichItem)) then
set slot.enabled = false
set cell = searchCellForItem(GetEquipmentTypeId(), whichItem, forceAction)
set slot.enabled = true
if (cell == 0) then
return false
endif
// Check cell content.
if (cell.getItem() != null) then
call unitUnequipItemToSlot(cell.getItem(), searchCellForItem(GetBackpackTypeId(), cell.getItem(), false))
endif
call cell.addShadow(whichItem, enabled)
endif
set cell = object.getCell()// Get the source cell.
call unitUnequipItemToSlot(slot.getItem(), cell)
// Move and fire.
call slot.moveItem(whichItem, enabled)
call FireEvent(EVENT_INVENTORY_UNIT_EQUIP_ITEM, this, whichItem, null)
return true
endmethod
// Does not force the equip process.
method unitEquipItem takes item whichItem returns boolean
return unitEquipItemToSlot(whichItem, searchCellForItem(GetEquipmentTypeId(), whichItem, false), false)
endmethod
// Only accepts Backpack cells as slot.
method unitAddItemToSlot takes item whichItem, InventoryCell slot returns boolean
local thistype temp
local CustomItem object
// Invalid operation. Only happens if you screwed up something.
if (slot.getTypeId() != GetBackpackTypeId()) then
return false
endif
// Check item.
if (IsCustomItem(whichItem)) and not IsItemPurchasable(whichItem) then
// Check slot.
if (slot.enabled) and (slot.getItem() == null) and slot.matchesItem(whichItem) then
//
// Prepare item.
// Analogous to UnitAddItem this method must work,
// if the owner is not this.source.
set object = GetHandleId(whichItem)
set temp = GetUnitInventory(object.getOwner())
if (temp != 0) and (object.getCell() != 0) then
call temp.unitRemoveItem(whichItem)// Fires events for temp.
endif
// Move and fire.
call slot.moveItem(whichItem, enabled)
call FireEvent(EVENT_INVENTORY_UNIT_PICKUP_ITEM, this, whichItem, source)
return true
endif
endif
//
debug call ThrowWarning(not IsCustomItem(whichItem), "Inventory", "unitAddItemToSlot", "whichItem", this, GetItemName(whichItem) + " is not registered as CustomItem!")
return false
endmethod
// Returns false, if no backpack cell is available.
method unitAddItem takes item whichItem returns boolean
return unitAddItemToSlot(whichItem, searchCellForItem(GetBackpackTypeId(), whichItem, false))
endmethod
// UNDER CONSTRUCTION.
method unitPawnItem takes item whichItem, unit shop returns boolean
local CustomItem object = GetHandleId(whichItem)
// Check pawn condition.
if (object.isItemPawnPossible(source, shop)) then
// Fire other events.
call unitRemoveItem(whichItem)
// Pawn item and fire.
call object.pawnItem(source)
call FireEvent(EVENT_INVENTORY_UNIT_PAWN_ITEM, this, whichItem, shop)
// No one took the item within the event. Remove it.
if (object.getOwner() == null) then
call RemoveItem(whichItem)
endif
return true
endif
return false
endmethod
// UNDER CONSTRUCTION.
method unitBuyItem takes item whichItem, unit shop returns boolean
local CustomItem object = GetHandleId(whichItem)
// Check buy condition.
if (object.isItemBuyPossible(source, shop)) then
call FireEvent(EVENT_INVENTORY_UNIT_BUY_ITEM, this, whichItem, shop)
call unitAddItem(whichItem)
return true
endif
return false
endmethod
// UNDER CONSTRUCTION.
method unitTradeItem takes item whichItem, thistype partner returns boolean
return false
endmethod
// UNDER CONSTRUCTION.
method socketItem takes item whichItem, item gem returns boolean
return false
endmethod
// UNDER CONSTRUCTION.
method unsocketItem takes item whichItem returns boolean
return false
endmethod
method mergeItems takes item target, item source returns boolean
if (GetItemType(target) == ITEM_TYPE_CHARGED) and (GetItemTypeId(source) == GetItemTypeId(target)) then
call CustomItem[target].mergeCharges(source)
return true
endif
return false
endmethod
//===========================================
// Response To Native Unit Item Events.
//===========================================
// In order to maintain a proper order of event evaluation,
// each native item event is catched in library CustomItem
// and afterwards passed to library Inventory.
// Sync the custom with the native inventory. Not very much supported feature.
private static method sync takes nothing returns nothing
static if LIBRARY_TimerUtilsEx then
call thistype(ReleaseTimer(GetExpiredTimer())).syncWithNative()
else
local thistype this = GetTimerData(GetExpiredTimer())
call ReleaseTimer(GetExpiredTimer())
call syncWithNative()
endif
endmethod
// EVENT_PLAYER_UNIT_PICKUP_ITEM.
static method onPickUpItem takes nothing returns nothing
local item picked = GetManipulatedItem()
local thistype this = GetUnitInventory(GetTriggerUnit())
if (exists) then
if IsCustomItem(picked) and not IsItemPurchasable(picked) then
if hasWindowOfType(GetBackpackTypeId()) then
if not unitAddItem(picked) then
call iHaveNoRoom()
call UnitRemoveItem(source, picked)
endif
elseif hasWindowOfType(GetEquipmentTypeId()) then
if not unitEquipItem(picked) then
call iHaveNoRoom()
call UnitRemoveItem(source, picked)
endif
endif
elseif (syncReady) then
call syncWithNative()
endif
endif
set picked = null
endmethod
// EVENT_PLAYER_UNIT_DROP_ITEM.
static method onDropItem takes nothing returns nothing
local thistype this = GetUnitInventory(GetTriggerUnit())
if (exists) and (syncReady) then
set syncReady = false
call TimerStart(NewTimerEx(this), 0, false, function thistype.sync)
endif
endmethod
// EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER.
static method onTargetOrder takes nothing returns nothing
local thistype this = GetUnitInventory(GetTriggerUnit())
if (exists) and (syncReady) then
set syncReady = false
call TimerStart(NewTimerEx(this), 0, false, function thistype.sync)
endif
endmethod
//=============================================
// Internal Response To Custom Events.
//=============================================
private static method equip takes nothing returns nothing
local thistype this = GetTriggerInventory()
local item eventItem = Inventory_GetTriggerItem()
call AddUnitItemAffixes(source, eventItem)
call addFx(eventItem)
set eventItem = null
endmethod
private static method unequip takes nothing returns nothing
local thistype this = GetTriggerInventory()
local item eventItem = Inventory_GetTriggerItem()
call RemoveUnitItemAffixes(source, eventItem)
call removeFx(eventItem)
set eventItem = null
endmethod
//=============================================
// Inventory Creator, Destructor & Related API.
//=============================================
// Must be called from each interface struct, which should have interactable cells.
method initInventoryCells takes UIWindow window returns nothing
local integer index = 0
local integer size = window.getButtonCount()
loop
exitwhen (index == size)
call InventoryCell.create(window.getButton(index))
set index = index + 1
endloop
endmethod
// Re-structures the equipment interface.
// Boosts the lookup time drastically for useful Equipment related API.
// JASS_MAX_ARRAY_SIZE is used as offset between two classes.
private static integer tempInventory = 0
private static method structure takes nothing returns nothing
local thistype this = thistype.tempInventory
local integer index
local integer child
local integer size
local integer space
local UIWindow window
local ItemClass class
local InventoryCell cell
if not (hasWindowOfType(GetEquipmentTypeId())) then
return
endif
set index = 0
set equipment = Table.create()
set window = getWindowOfType(GetEquipmentTypeId())
set size = window.getButtonCount()
loop
exitwhen (index == size)
set cell = window.getButton(index).data
set class = cell.class
loop
set child = class + 1
set space = equipment[-child]
set equipment[child*JASS_MAX_ARRAY_SIZE + space] = cell
set equipment[-child] = space + 1
set class = class.parent
exitwhen class.root
endloop
set index = index + 1
endloop
endmethod
// Users should not use these methods, because only the dedicated wrapper function check for misuse.
// Go with CreateInventory(), DestroyInventory(), AddUnitInventoryDummy(). I also do so.
method createDummyPortrait takes integer dummyId, real extraY returns nothing
debug local string error = "Failed to create a dummy for " + GetUnitName(source) + " from unit type id ['" + A2S(dummyId) + "']!"
//
local boolean prev = ToogleUnitIndexer(false)
local unit temp = CreateUnit(user, dummyId, WorldBounds.maxX, WorldBounds.maxY, 1.)
call ToogleUnitIndexer(prev)
call SetUnitFacing(temp, 0.)
call PauseUnit(temp, true)
call ShowUnit(temp, false)
call UnitAddAbility(temp, 'Amrf')
call UnitAddAbility(temp, 'Aloc')
call UnitAddAbility(temp, 'Abun')
if (GetLocalClient() != user) then
call SetUnitVertexColor(temp, 0, 0, 0, 0)
endif
set dummyY = extraY
set dummy = temp
set temp = null
//
debug call ThrowWarning((GetUnitTypeId(dummy) == 0), "Inventory", "createDummyPortrait", "dummy", this, error)
endmethod
// Actual creator function. Use function wrapper CreateInventory().
static method construct takes unit sourceUnit, real originX, real originY, real width, real height returns thistype
local thistype this = thistype.create(GetOwningPlayer(sourceUnit), originX, originY, width, height)
set source = sourceUnit
set fontType = Inventory_GetDefaultFont()
set syncReady = true
set box = UIBorder.create()
set effects = Table.create()
//
static if LIBRARY_ItemPower then
if not (UnitUsesItemPower(sourceUnit)) then
call CreateUnitItemPower(sourceUnit)
endif
endif
//
// Reference the inventory instance.
call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_UNIT_REFERENCE, GetHandleId(sourceUnit), this)
//
// Init all interfaces for Inventory.
call FireOnCreate(this)
//
// Restructure all equipment cells. ForForce to avoid hitting an OP limit.
set tempInventory = this
call ForForce(bj_FORCE_PLAYER[0], function thistype.structure)
return this
endmethod
// onDestroy has an inappropriate timing for Inventory,
// because it runs before the window destructor.
//
// The inventory instance is reference on the unit handle id.
// We release the handle last, so all windows can be destroyed properly.
method pastDestroy takes nothing returns nothing
call RemoveSavedInteger(Inventory_GetTable(), INVENTORY_KEY_UNIT_REFERENCE, GetHandleId(source))
call effects.destroy()
call equipment.destroy()
call RemoveUnit(dummy)
call box.destroy()
call deselect()
set dummy = null
set source = null
endmethod
private static method init takes nothing returns nothing
call InitEventTriggers()
call InitSoundFiles()
call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_EQUIP_ITEM, function thistype.equip)
call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_UNEQUIP_ITEM, function thistype.unequip)
endmethod
implement UIInit
endstruct
//===================================================================================
// Wrapper functions for safe Inventory API.
// Please always use these functions below.
//===================================================================================
function CreateInventory takes unit source, real originX, real originY returns Inventory
local boolean valid = (GetUnitTypeId(source) != 0) and (GetUnitInventory(source) == 0)
if (valid) then
return Inventory.construct(source, originX, originY, 1500, 1500/UI_PERFECT_SCREEN_RATIO)
endif
//
debug call ThrowWarning((GetUnitInventory(source) != 0), "Inventory", "CreateInventory", "", 0, GetUnitName(source) + " has already an inventory!")
debug call ThrowWarning((GetUnitTypeId(source) == 0), "Inventory", "CreateInventory", "", 0, "Invalid unit argument ( null )!")
return 0
endfunction
function AddUnitInventoryDummy takes unit source, integer dummyId, real dummyY returns nothing
local Inventory instance = GetUnitInventory(source)
local boolean valid = (instance.exists) and (instance.dummy == null)
if (valid) then
call instance.createDummyPortrait(dummyId, dummyY)
endif
//
debug call ThrowWarning((not instance.exists), "Inventory", "AddUnitInventoryDummy", "", 0, GetUnitName(source) + " has no inventory!")
debug call ThrowWarning((instance.exists and not valid), "Inventory", "AddUnitInventoryDummy", "dummy", instance, "This inventory has already a dummy unit!")
endfunction
function DestroyInventory takes unit source returns nothing
local Inventory instance = GetUnitInventory(source)
if (instance.exists) then
call instance.destroy()// Hides and destroys all interface windows.
call instance.pastDestroy()//
//
debug else
debug call ThrowWarning(true, "Inventory", "DestroyInventory", "", 0, GetUnitName(source) + " has no inventory!")
endif
endfunction
//==========================================================================================
// Struct InventoryCell handles each interactable cell in Inventory.
// It's limited to 8910 instances and therefore also limits the amount
// of maximum Inventory instances. More cells can be allocated by
// passing a struct space into the textmacro located at the top of the Inventory library.
// A table allocator could enabled endless cell instances, but I hope you don't need it.
//==========================================================================================
//! textmacro INVENTORY_CELL_CODE takes MORE
struct InventoryCell $MORE$
// Tracks items which need two cells.
private static Table shadows
// Returns false for invalid cells.
method operator exists takes nothing returns boolean
return (icon != 0)
endmethod
readonly CustomItem data // Item in this cell. 0 for no item.
readonly ItemClass class // Class of this cell.
readonly UIButton icon // UIButton ( trackable & visual for this cell )
public boolean enabled // Status of this cell.
method getButton takes nothing returns UIButton
return icon
endmethod
method hasItem takes nothing returns boolean
return (data != 0)
endmethod
method getDescription takes nothing returns string
return class.name
endmethod
// CustomItem and InventoryCell are strongly linked with each other.
// Get a cell by CustomItem index. Returns 0 for no cell.
static method operator [] takes CustomItem index returns thistype
return index.getCell()
endmethod
// Get the struct this cell is located in.
method getTypeId takes nothing returns integer
return icon.typeId
endmethod
// Get the actual item handle. Returns "null" for no item in this cell.
method getItem takes nothing returns item
return data.getHandle()
endmethod
// Called each time something happens in an Inventory.
method update takes boolean show returns nothing
local integer itemId = data.getItemId()
local string path = GetCustomItemIconPath(itemId)
if (data == 0) then
call icon.remove()
else
if (path != "") and (path != null) then
call icon.addImage(path, show)
else
call icon.addDest(GetCustomItemIconId(itemId), show)
endif
//
// Reference to this cell in CustomItem.
call data.setCell(this, getTypeId() != GetNativeInventoryTypeId())
endif
endmethod
// Depreciated, but I don't know if still used somewhere.
method move takes integer it, boolean show returns nothing
debug call ThrowWarning(true, "Inventory", "InventoryCell", "move", 0, "Don't use method move!")
set data = it
call update(show)
endmethod
method swap takes thistype cell, boolean show returns nothing
local integer temp = data
set data = cell.data
set cell.data = temp
call cell.update(show)
call this.update(show)
endmethod
static method create takes UIButton node returns thistype
local thistype this = thistype.allocate()
set icon = node
set data = 0
set class = node.data
set enabled = true
set node.data = this
return this
endmethod
// Compare item which cell class.
public method matchesItem takes item i returns boolean
local CustomItem whichItem = GetHandleId(i)
local ItemClass leaf = class
local ItemClass itemClass = whichItem.getClass()
loop
exitwhen (itemClass == leaf) or (leaf.root)
set leaf = leaf.parent
endloop
return (leaf == itemClass) or (class.root)
endmethod
// Depreciated, but still here.
public method matches takes CustomItem id returns boolean
return matchesItem(id.getHandle())
endmethod
// For twohand items.
method addShadow takes item whichItem, boolean flag returns nothing
local integer itemId = GetItemTypeId(whichItem)
local string file = GetCustomItemIconDISPath(itemId)
local integer dest = GetCustomItemIconDISId(itemId)
local integer id = GetHandleId(whichItem)
if (dest == 0) and (file != "") and (file != null) then
call icon.addImage(file, flag)
elseif (dest != 0) then
call icon.addDest(dest, flag)
elseif (CUSTOM_ITEM_DIS_ICON_ID != 0) then
call icon.addDest(CUSTOM_ITEM_DIS_ICON_ID, flag)
elseif (CUSTOM_ITEM_DIS_ICON_PATH != "") and (CUSTOM_ITEM_DIS_ICON_PATH != null) then
call icon.addImage(CUSTOM_ITEM_DIS_ICON_PATH, flag)
endif
set shadows[id] = this
set data = id
endmethod
static method removeShadow takes item whichItem returns nothing
local thistype this = shadows[GetHandleId(whichItem)]
if (this != 0) and (exists) then
call icon.remove()
set data = 0
call shadows.remove(GetHandleId(whichItem))
endif
endmethod
method clear takes nothing returns nothing
debug call ThrowError(not exists, "InventoryCell", "clear", "thistype", this, "This instance is not allocated!")
set data = 0
call icon.remove()
endmethod
method moveItem takes item i, boolean show returns nothing
local CustomItem ci = CustomItem[i]
local thistype node = ci.getCell()
debug call ThrowWarning(GetItemTypeId(i) == 0, "InventoryCell", "moveItem", "it", this, "Invalid item handle ( null )!")
if (node.exists) and (node.getItem() == i) then
call node.clear()
endif
set data = ci
call update(show)
endmethod
method destroy takes nothing returns nothing
if shadows.has(data) then
call removeShadow(data.getHandle())
endif
call clear()
set enabled = false
set icon = 0
set class = 0
call deallocate()
endmethod
static method onRemove takes thistype this returns nothing
if (data != 0) then
call clear()
endif
endmethod
private static method onDestroyItem takes nothing returns nothing
local CustomItem ci = GetTriggerCustomItem()
local thistype this = ci.getCell()
if (exists) and (data == ci) then
call clear()
endif
endmethod
private static method init takes nothing returns nothing
call RegisterCustomItemEvent(EVENT_CUSTOM_ITEM_DESTROY, function thistype.onDestroyItem)
set shadows = Table.create()
endmethod
implement UIInit
endstruct
//! endtextmacro
endlibrary