Item script structure
Each usable item has a Lua file in scripts/items/ named by the item’s in-game name (e.g., acorn_cookie.lua). An item script returns a table with up to two handler functions.
-- scripts/items/acorn_cookie.lua (actual source)
---@type TItemFood
local itemObject = {}
-- Called before the item is used to validate whether use is allowed.
-- Return 0 to allow use. Return a message ID to block use with that message.
itemObject.onItemCheck = function(target, item, param, caster)
return xi.itemUtils.foodOnItemCheck(target, xi.foodType.BASIC)
end
-- Called when the item is actually used.
itemObject.onItemUse = function(target, user, item, action)
target:addStatusEffect(xi.effect.FOOD, {
duration = 180,
origin = user,
sourceType = xi.effectSourceType.FOOD,
sourceTypeParam = item:getID()
})
end
-- Called when the status effect applied by this item takes hold.
itemObject.onEffectGain = function(target, effect)
effect:addMod(xi.mod.AQUAN_KILLER, 10)
effect:addMod(xi.mod.MPHEAL, 3)
end
-- Called when the status effect wears off.
itemObject.onEffectLose = function(target, effect)
end
return itemObject
Handler signatures
| Handler | Signature | Purpose |
|---|
onItemCheck | (target, item, param, caster) | Pre-use validation. Return 0 to allow, non-zero message ID to deny. |
onItemUse | (target, user, item, action) | Apply the item’s effect. |
onEffectGain | (target, effect) | Called when a status effect from this item takes hold (food, potions, etc.). |
onEffectLose | (target, effect) | Called when the status effect wears off. |
target is the player using the item. user is the character who used the item (usually the same as target for self-use items).
Item utility functions
scripts/globals/item_utils.lua provides xi.itemUtils with shared validation helpers.
Food check
-- Used by food items to validate race restrictions and the FOOD effect
xi.itemUtils.foodOnItemCheck = function(target, foodType)
-- foodType: xi.foodType.RAW_FISH or xi.foodType.RAW_MEAT
-- Only Mithra can eat raw fish; only Galka can eat raw meat
-- Returns 0 (allow), xi.msg.basic.CANNOT_EAT, or xi.msg.basic.IS_FULL
end
Inventory box check
-- Block use if the player has no free inventory slots
xi.itemUtils.itemBoxOnItemCheck = function(target)
if target:getFreeSlotsCount() == 0 then
return xi.msg.basic.ITEM_NO_USE_INVENTORY
end
return 0
end
Skill book check
-- Validate whether a player can use a skill-up book for the given skill
xi.itemUtils.skillBookCheck = function(target, skillID)
-- Checks main job cap and sub job cap vs. current skill level
-- Returns 0 if usable
end
Status effect removal list
xi.itemUtils.removableEffects is an ordered array of status effect constants that items like remedies can remove (paralysis, poison, blindness, silence, etc.).
Trade handling
Trade validation is done with npcUtil.tradeHas and npcUtil.tradeHasExactly. Both are available globally.
-- Allow any amount of copper ore (not exact)
npcUtil.tradeHas(trade, xi.item.CHUNK_OF_COPPER_ORE)
-- Require exactly copper ore x1 and tin ore x1, nothing else
npcUtil.tradeHasExactly(trade, { xi.item.CHUNK_OF_COPPER_ORE, xi.item.CHUNK_OF_TIN_ORE })
-- Require copper ore x2 exactly
npcUtil.tradeHasExactly(trade, { { xi.item.CHUNK_OF_COPPER_ORE, 2 } })
-- Require copper ore x2 and gil x200 exactly
npcUtil.tradeHasExactly(trade, { { xi.item.CHUNK_OF_COPPER_ORE, 2 }, { 'gil', 200 } })
-- Allow one item type at any quantity
npcUtil.tradeHasOnly(trade, xi.item.CHUNK_OF_COPPER_ORE)
All tradeHas* functions automatically call trade:confirmItem() on matched items when returning true.
Trade container API
The trade object passed to onTrade handlers is a CLuaTradeContainer (bound in src/map/lua/lua_trade_container.cpp).
trade:getSlotCount() -- number of slots used in the trade
trade:getItemId(slot) -- item ID at the given slot index (0-based)
trade:getItemQty(itemId) -- quantity of the given item ID in the trade
trade:getItemCount() -- total item count across all slots
trade:confirmItem(itemId, qty) -- confirm/consume qty of itemId from the trade
Item IDs
Item IDs are defined as constants under xi.item.*. The full enum is in scripts/enum/item.lua.
xi.item.CHUNK_OF_COPPER_ORE
xi.item.ANTIDOTE
xi.item.ONION_SWORD
xi.item.SCROLL_OF_CURE_EX
Use xi.item.* in scripts rather than raw integers to keep code readable and maintainable.
Giving items from NPC scripts
Always use npcUtil.giveItem() rather than calling player:addItem() directly, because giveItem handles the inventory check and displays the correct obtained message automatically:
-- Single item
npcUtil.giveItem(player, xi.item.CHUNK_OF_COPPER_ORE)
-- Multiple items
npcUtil.giveItem(player, { xi.item.CHUNK_OF_COPPER_ORE, xi.item.CHUNK_OF_TIN_ORE })
-- Specific quantity
npcUtil.giveItem(player, { { xi.item.CHUNK_OF_COPPER_ORE, 12 } })
-- Randomly 3–15 copper ores
npcUtil.giveItem(player, { { xi.item.CHUNK_OF_COPPER_ORE, math.random(3, 15) } })
Returns false if the player lacks inventory space, true otherwise.