Violet

Player Shop

On this page

Classic only. Find personal shops and hired merchants on the current map, visit them and buy their listings. NPC shops use Shop instead.

Requests are asynchronous

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

PropertyTypeDescription
idnumberPositive unsigned 32-bit shop ID. Pass unchanged to enter().
kindstring"personal" or "merchant".
ownerstringOwner name.
titlestringShop title.
xnumber or nilMap position; omitted when unavailable.
ynumber or nilMap position; omitted when unavailable.

Listing structure

PropertyTypeDescription
slotnumber0-based shop row, not the Lua array index. Pass unchanged to buy().
item_idnumberItem template ID.
namestring or nilOmitted when item data is unavailable.
bundlesnumberBundles remaining; zero or negative means sold.
per_bundlenumberItems in each bundle.
pricenumberMesos per bundle, not per item.
statstable or nilEquip 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:

ErrorMeaning
bad_slotSlot is not an integer in 0..4294967295.
bad_countBundles is not an integer in 1..65535.
unavailablePurchase support, character data or item data is unavailable.
not_openNo shop window is open.
own_shopShop belongs to this character or another character on this account.
closedShop is not selling.
bad_slotRow does not exist or is malformed.
bad_countSold row, more bundles than remain, more than one for a single-unit item, or total cost exceeds signed 64-bit range.
restrictedRegional trade restrictions apply.
not_enough_mesosNot enough mesos for price multiplied by bundles.
inventory_fullNo empty slot in the item's inventory tab; possible stack merges do not count.
busyAnother 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

lua
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
end

Close the shop before equipping gear. Check API availability before calling these functions on an older core or another game.