Player Shop
Classic only. Find personal shops and hired merchants on the current map, visit them and buy their listings. NPC shops use Shop instead.
enter() and buy() returning true means sent, not server-confirmed. Send once, then check the window, listing, mesos and inventory on later ticks. Do not repeat purchases every tick or assume unchanged state means it is safe to retry. Buying does not ask for confirmation.
Shop structure
| Property | Type | Description |
|---|---|---|
id | number | Positive unsigned 32-bit shop ID. Pass unchanged to enter(). |
kind | string | "personal" or "merchant". |
owner | string | Owner name. |
title | string | Shop title. |
x | number or nil | Map position; omitted when unavailable. |
y | number or nil | Map position; omitted when unavailable. |
Listing structure
| Property | Type | Description |
|---|---|---|
slot | number | 0-based shop row, not the Lua array index. Pass unchanged to buy(). |
item_id | number | Item template ID. |
name | string or nil | Omitted when item data is unavailable. |
bundles | number | Bundles remaining; zero or negative means sold. |
per_bundle | number | Items in each bundle. |
price | number | Mesos per bundle, not per item. |
stats | table or nil | Equip stats only; omitted for other items. |
Equip stats contains numeric str, dex, int, luk, hp, mp, watk, matk, wdef, mdef, acc, avoid, speed, jump, and upgrade_slots. req_level is optional. No required-job field is exposed.
Functions
core.player_shop.get_shops() -> table<player_shop>
Available on CW
Classic only. Returns visitable shops on the current map, or an empty array off-map. Each entry has id, kind, owner, title and optional x/y coordinates.
core.player_shop.enter(id: number) -> boolean, string?
Available on CW
Classic only. Sends one visit request; true means sent, not opened. Poll is_open() on later ticks. id is a positive unsigned 32-bit integer from get_shops(); all scripts share a one-second visit limit.
Returns false, error on refusal. Validation order: no_shop (invalid ID), busy (a dialog is open or the visit limit applies), unavailable (character data unavailable), no_shop (shop no longer listed), then busy (another request prevents entering a personal shop).
core.player_shop.is_open() -> boolean
Available on CW
Classic only. Returns whether a personal-shop or hired-merchant window is open, including your own shop.
core.player_shop.get_info() -> player_shop_info | nil
Available on CW
Classic only. Returns kind ("personal" or "merchant"), owner and title, or nil when no shop window is open.
core.player_shop.get_items() -> table<player_shop_item> | nil
Available on CW
Classic only. Returns listings from the open shop, or nil when none is open. Rows contain slot, item_id, bundles, per_bundle, price and optional name/stats; equip stats may omit req_level. slot is 0-based and price is mesos per bundle.
The array may skip unavailable rows. Use each entry's slot, not its array index.
core.player_shop.buy(slot: number, bundles: number) -> boolean, string?
Available on CW
Classic only. Sends one purchase request; true means sent, not bought. slot is the 0-based listing slot; bundles is an integer from 1 through 65535. Check items, mesos and inventory later to confirm the result; no confirmation prompt is shown.
Returns false, error on refusal. Validation order:
| Error | Meaning |
|---|---|
bad_slot | Slot is not an integer in 0..4294967295. |
bad_count | Bundles is not an integer in 1..65535. |
unavailable | Purchase support, character data or item data is unavailable. |
not_open | No shop window is open. |
own_shop | Shop belongs to this character or another character on this account. |
closed | Shop is not selling. |
bad_slot | Row does not exist or is malformed. |
bad_count | Sold row, more bundles than remain, more than one for a single-unit item, or total cost exceeds signed 64-bit range. |
restricted | Regional trade restrictions apply. |
not_enough_mesos | Not enough mesos for price multiplied by bundles. |
inventory_full | No empty slot in the item's inventory tab; possible stack merges do not count. |
busy | Another request is pending or purchases cannot be sent yet. |
core.player_shop.close() -> boolean
Available on CW
Classic only. Leaves the open shop window and returns true, or returns false if none is open.
core.player_shop.get_fm_room() -> number | nil
Available on CW
Classic only. Returns 0 at Free Market Entrance (80002000), 1..11 in Free Market rooms (80002001..80002011), or nil elsewhere. This reads the current room; it does not travel.
Read listings without buying
local items = core.player_shop.get_items()
if items then
for _, item in ipairs(items) do
core.log(string.format("slot %d: %s, %d mesos per bundle",
item.slot, item.name or tostring(item.item_id), item.price))
end
endClose the shop before equipping gear. Check API availability before calling these functions on an older core or another game.