Shops
Build NPC shops with stock, prices, a currency item and buy/sell rules, and open them from dialogue, NPC scripts or a command.
7 min read
What shops are#
A shop is a named stock list that players can buy from and, optionally, sell to. Each shop has a currency item, a list of stocked items with a quantity and a price, multipliers that turn that price into what the player pays or receives, and flags controlling restocking and buy-back. The Shop Editor in the Tools hub builds these definitions; the bundled shop module loads them when the server starts and drives the client's Shop interface.
Use shops for any NPC store: a general store that buys anything, a specialist that only trades in what it stocks, or a one-way vendor that never buys. Shops deal in an item of your choosing, so a game can have coin shops, token shops and reputation-scrip shops side by side.
Opening the tool#
- Click Tools in the top bar.
- Click Shop Editor on the hub.
The tool is laid out in three panels:
- Shops (left) — a Search shops... box, the list of shops shown as
Name (shop_id), and + Add Shop. Right-click a shop for Duplicate or Delete. - Editor (center) — open shops appear as tabs along the top (right-click a tab for Close Tab, Close Other Tabs, Close All Tabs). Below is a header row with Name, the read-only ID and a Save button, then two sub-tabs: Stock and Configuration. Until you open a shop it reads "Select a shop to edit".
- Items (right) — every item in the project with a Search items... box and a quick-add panel with Qty (default 100) and Price (default 100). Double-click an item to add it to the open shop with those values.
Shops save when you click Save, press Ctrl+S, or leave the tool; tabs with unsaved changes carry a *. Creating, duplicating or deleting a shop saves immediately.
Create a shop#
- Click + Add Shop. In the dialog, enter a Shop ID (for example
my_shop) and a Name (for exampleMy Shop); the name defaults to the id if you leave it blank. Click Create. - The shop opens in a tab with the defaults: restocks on, buys items on, stock-only off, buy multiplier 1.3, sell multiplier 0.6 and an empty stock list.
- Switch to Configuration and set the Currency. The dropdown lists every item as
id: name; the search button beside it opens a picker. This is the item players pay with and receive when selling. - Back on Stock, set Qty and Price in the quick-add panel and double-click items in the Items list to add them. Each addition becomes a slot in the stock grid.
- Click a slot to edit it in the bar above the grid: Item ID (with a picker button and the resolved item name), Qty (1–999,999) and Price (0–9,999,999). Remove deletes the slot; right-click a slot for Duplicate or Delete.
- Click Save.
Arranging stock#
The footer under the grid shows Stock (n items) and two view buttons: the grid view shows thumbnail tiles (eight per row) with price and quantity; the list view shows one row per slot with editable quantity and price spin boxes and a remove button. Drag a tile onto another to reorder. With the Insert toggle off, dragging swaps the two slots; with it on, the dragged item is inserted before the target and the rest shift. Slot order is the order the client shows.
Pricing#
Every stocked item carries its own Price. At runtime:
- Buy price = slot price × Buy Multiplier (0.01–10, default 1.3), truncated to a whole number.
- Sell price = slot price × Sell Multiplier (0.01–10, default 0.6), truncated.
For an item the shop does not stock (only relevant when selling to a shop that buys anything), the reference price is the item's value property from its server definition, falling back to a price property and then to 10. Set value on the item's Properties tab in the Data Editor if you want sell-anything shops to pay sensible amounts; see Items.
Two optional per-slot keys in the JSON, custom_buy_price and custom_sell_price, bypass the multipliers entirely when present. The editor does not expose them; add them with the Script Editor's schema table view if you need a fixed price on one slot.
Configuration options#
| Option | Default | Effect |
|---|---|---|
| Currency | item 995 | Item removed from the player's inventory on a purchase and added on a sale. |
| Buy Multiplier | 1.3 | Scales slot prices into what the player pays. |
| Sell Multiplier | 0.6 | Scales slot prices into what the player receives. |
| Restocks | on | Stock drifts back toward its authored quantity: every 400 server ticks (about 100 seconds at 4 ticks per second) each item moves one unit toward its default, whether it was bought down or sold up. Off means stock only changes through trades. |
| Buys Items | on | Players may sell to this shop. Off replies "This shop doesn't buy items." |
| Stock Only | off | The shop buys only items currently in its stock. Because sold items join the stock, an item a player sells once becomes buyable thereafter. Off means the shop buys anything, at the item's reference price. |
Files written#
Shops live in one data file in the project's server schemas: server/schemas/Shops/shops/shops_data.json, alongside the shops.json schema description that the server's data store reads. The file is a JSON array of shop objects.
[
{
"shop_id": "general_store",
"name": "Aldenmoor General Store",
"currency_id": 7,
"restocks": true,
"buys_items": true,
"buys_only_stock": false,
"buy_multiplier": 1.3,
"sell_multiplier": 0.6,
"stock": [
{ "item_id": 16, "price": 40, "quantity": 10 },
{ "item_id": 17, "price": 25, "quantity": 10 }
]
}
]
| Field | Type | Meaning |
|---|---|---|
shop_id | string | Unique id used to open the shop. Duplicating a shop appends _copy. A second shop with the same id overwrites the first at load, with a warning in the server log. |
name | string | Title shown at the top of the Shop interface. |
currency_id | int | Item players pay with. |
restocks, buys_items, buys_only_stock | bool | See the options table above. |
buy_multiplier, sell_multiplier | float | See Pricing. |
stock[] | array | Slots of item_id, price, quantity; optional custom_buy_price and custom_sell_price. |
How the server runs a shop#
The Shops gameplay system owns ShopModule.cs, the ShopScript.cs interface script, the ::shop command, the Shop.tscn interface and its manifest. It is one of the server-enforced systems, so disabling it in Game Config also stops the platform's built-in copy. See Templates and gameplay systems.
When a player opens a shop the server opens interface id 1 — the id it hardcodes for shops, which the starter templates register as Shop — sets its title, spawns one slot per stocked item into the interface's item grid (eight columns wide in the starter template's Shop.tscn) and fills each with the item, its current stock count and the computed buy price. Each slot offers Buy 1, Buy 5 and Buy 10. While the shop is open the tab area is locked to the inventory and inventory items gain Sell-1, Sell-5 and Sell-10 options instead of their usual menu; closing the shop restores both.
A purchase checks, in order, that the shop has enough stock ("This item is out of stock."), that the player holds enough currency ("You don't have enough coins. You need n coins."), and that they have a free inventory slot ("Your inventory is full."); then it removes the currency, adds the item and decrements the stock. A sale checks Buys Items, then Stock Only, then that the player holds enough ("You don't have enough of that item."), and pays out in currency while adding the sold units to the shop's stock. Purchases also raise a PlayerPurchaseEvent that feeds the dashboard's player activity log.
Shop stock is held in memory: it resets to the authored quantities whenever the server restarts.
Opening a shop from the game#
There is no shop field on an NPC definition; something has to call the shop by id. Three ways are available out of the box:
- Dialogue — add an Action node to the NPC's dialogue whose script calls
openShop("general_store");(orplayer.openShop(...)). This is the usual shopkeeper pattern: the player talks to the NPC, picks "Let me see your wares", and the action opens the shop. See Dialogue. - NPC script — in a server script attached to the NPC, handle the option (for example a
Tradeoption you added to the NPC's options list) and callShopRegistry.Instance.OpenShop(player, "general_store"), returningtrueto mark the click handled. See NPC, object and interaction scripts. - Command —
::shop <shop_id>(alias::openshop) opens any shop;::shopwith no argument lists every registered shop with its id and name. Useful for testing stock and prices before wiring the NPC.
Scripts can also call ShopRegistry.Instance.BuyItem(player, shopId, itemId, quantity) and SellItem(...) directly, HasShop(id), GetShop(id) and GetAll().
With AI (MCP)#
define_shop— creates or replaces one shop byshop_id:name,currency_id,restocks,buys_items,buys_only_stock,buy_multiplier,sell_multiplierand astockarray ofitem_id,price(default 1) andquantity(default 1). Every stocked item and the currency must exist in the project.currency_idis required for the first shop in a project; later shops default to the currency the existing shops use. PassdryRun: trueto preview.get_schema_datawith the table nameshopsreads the raw rows.
See the tools reference for full argument lists.
