Shops
Usage guide of trade.shop: shops as data (offers, stock, prices), merchants in the world, buying and selling against the balance and the inventory, restocking, settings and defaults, service methods, verbs, hooks (shop.open, shop.buy, shop.sell), events, refusal reasons.
Merchants and shops (trade.shop) #
What it is for #
trade.shop turns the shops collection into merchants. A shop lists offers (item, price, stock), may buy the player's goods back at a rate, and brings sold-out offers back after a while. Buying takes money through economy.money and gives the item through inventory.items; selling does the reverse. The kit holds the rules and emits events; the counter on screen is drawn by its interface fragment (buy on the left, sell on the right, the purse). For the balance itself and the family overview, see the money and shops guide.
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "trade.shop" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "trade.shop",
config: { "buyRate": 0.4, "restockMinutes": 5 } }
It requires economy.money, inventory.items and game.engine, so installing it brings them. Describe the merchants in the shops collection of the project:
{ "shops": [
{ "id": "smith", "name": "Smith", "greeting": "Welcome", "buyRate": 0.25,
"offers": [{ "item": "sword", "price": 60, "stock": 1 }, { "item": "potion", "price": 12.5 }],
"buys": [{ "item": "gold", "price": 40 }] }
] }
A merchant in the world is an entity with the component shop { id }; interacting with it opens that shop.
First example #
<!-- test: kits/game_engine/host/tests/trade_shop.rs::buying_checks_the_stock_the_balance_and_the_room_in_the_bag --> With 100 in the purse, a bag of two slots and the shop above, calling the service trade.shop:
buy("smith", "potion", 2) -> { ok: true, paid: 25 } balance 75
buy("smith", "sword") -> { ok: true, paid: 60 }
buy("smith", "sword") -> { ok: false, reason: "sold_out" }
buy("smith", "potion", 2) -> { ok: false, reason: "too_poor" }
buy("smith", "rock") -> { ok: false, reason: "unknown" }
buy("grocer", "apple") -> { ok: false, reason: "no_room" } a full bag refuses before any money moves
Every refusal also emits shop.refused with the same reason.
Settings and defaults #
| Setting | Type | Default | Meaning |
|---|---|---|---|
buyRate | number | 0.5 | Share of an item's price paid when the player sells it (0 to 2). A shop can set its own buyRate. |
buysAnything | boolean | true | A merchant buys any item that has a price in any shop. |
restockMinutes | number | 0 | Minutes before a sold-out offer comes back to its stock (0 to 1440); 0: never. |
interactLabel | string | Trade with {name} | The prompt on a merchant; {name} is the shop name. |
A shop document has id, name, offers (required), and greeting, buyRate, buys. An offer has item (an item of the items collection), price and stock (-1, the default: unlimited). buys lists items the merchant buys that are not in its offers: an id, or { item, price } with its own base price.
Service methods #
Service trade.shop:
shops(): every shop as{ id, name }.open(id): open a shop (true or false),close(),current(): the id of the open shop.offers(id): each offer withitem,price,stock,affordableandsellPrice.priceOf(shop, item)andsellPriceOf(shop, item): the buy price of an offer, and what the merchant pays for the item.buy(shop, item, n)answers{ ok: true, paid };sell(shop, item, n)answers{ ok: true, earned }. A refusal is{ ok: false, reason }.
Verbs, events, variables, hooks #
open_shop { shop }andclose_shop(actions),shop_open { shop? }(condition: a shop, or this one, is open).- Variable
shop.open: the id of the open shop, empty when none. - Events:
shop.opened { shop, name, greeting },shop.closed { shop },shop.bought { shop, item, count, paid },shop.sold { shop, item, count, earned },shop.refused { shop, item, reason }. - Refusal reasons:
too_poor,sold_out,not_held,unknown,no_room,not_buying,closed, or the reason given by a tap that cancelled. - Hook
shop.open{ shop }: cancellable; the refusal isclosedunless the tap gives a reason. - Hook
shop.buy{ shop, item, count, price }: mutableprice(the total), cancellable. Asked after the stock check and before the balance check. - Hook
shop.sell{ shop, item, count, price }: mutableprice(the total the merchant pays), cancellable.
{ "hook": "shop.buy", "match": { "shop": "smith" }, "mul": { "price": 0.8 } }
Hooks and modding #
The settings and the shops collection are moddable. shops is a registry: { "id": "smith_hard", "extends": "smith" } of a project or a mod inherits an entry and changes only what it lists. A discount, a tax or a closed-at-night merchant is a hook, not a change to the kit.
Engines #
The rules run on the web and in the native player, where the hooks also run. The counter is an ui.webui fragment shared by the page and every engine.
When it does not do what you expect #
- Nothing opens when interacting: the entity has no
shopcomponent, or itsidis not inshops; ashop.opentap may refuse (closed). - Buying says
no_room: the inventory cannot take the units; nothing was paid. - Selling says
not_buying: the item is not in the offers orbuysandbuysAnythingis off, or the item has no price. - Selling pays little:
buyRateis0.5by default. - A sold-out offer never returns:
restockMinutesis0. Selling a unit of an offer back puts it on the shelf.
Limits #
A price is the one in the shop document, changed only by the hooks shop.buy and shop.sell. The stock and the restock timers are saved with the game.
Versions #
Kit version 1.2.1; requires economy.money 1, inventory.items 1 and game.engine 1. Hooks arrived in 1.1.0 and the inheriting shops registry in 1.2.0.