Shops and player trade
Both kinds of trade use the game's shop screen. In both, the screen only estimates the deal. The server recalculates it from its own data when the player confirms.
NPC shops
kcds.characters.Vendor is a shop stored on the server. It has a shelf of priced rows, a list of what
it pays for players' items, and a purse. Prices are counted in the game's money item.
const { Vendor } = kcds.characters; const { Events } = kcds.core; const smith = Vendor.create({ name: 'Smith', purse: 5000 }); Vendor.setShelf(smith, [ { item: 'arrow_crude', amount: 50, price: 12 }, { item: 'bread', amount: 10, price: 30 }, ]); Vendor.setOffers(smith, [{ item: 'arrow_crude', price: 6 }]); Events.on('npcInteract', (npc, player) => { if (npc.name === 'Smith') { Vendor.open(smith, player, npc); } }); Events.on('vendorTrade', (vendor, player, bought, sold, balance) => { console.info(`${player.nickname} traded, balance ${balance}`); });
Setting up
Vendor.create({ name?, purse?, buys? }): with nopurse, the money never runs out. Withbuys: false, the shop only sells.setShelf(vendor, rows): up to 128 rows of{ item, amount, price, metadata? }.setOffers(vendor, rows): what the shop pays for items.fillPurseandpurseOf: set and read its money.
Running a session
Vendor.open(vendor, player, npc?) returns a session id. If you pass an npc, the shop closes when
that NPC dies or disappears. Also available: Vendor.close(session), activeSession(player) and
shopOf(session).
Refused trades
The server refuses a basket if it was priced against an older shelf, if either side cannot pay, or if the goods do not fit in the player's inventory. Items players sell do not appear on the shelf.
When a shop closes
The event vendorClosed(vendor, player, reason) gives one of these reasons:
| Reason | Meaning |
|---|---|
| 0 | the player closed it |
| 1 | the server closed it |
| 2 | another shop replaced it |
| 3 | the screen could not open |
| 4 | the player left |
Reference: Vendor.
Trade between players
kcds.players.Trade shows one player's inventory as a shop on another player's screen. The buyer sees
the seller's items and the seller's money as the purse, but not the seller's money items, quest items
or worn gear. Each basket is checked against both inventories, then both change together or neither
does.
const { Events, Chat } = kcds.core; const { Player, Trade } = kcds.players; // "/sell Hans" shows your inventory as a shop on Hans's screen. Events.on('playerCommand', (seller, command, args) => { if (command !== 'sell') { return; } const buyer = Player.all().find((player) => player.nickname === args[0]); if (!buyer) { return; } const session = Trade.open(seller, buyer, { prices: { apple: 7 }, buys: true, // Hans can also sell to the seller buyPrices: { apple: 3 }, }); if (session === 0) { Chat.sendToPlayer(seller, 'The trade could not open.'); } }); // Return false to refuse a basket; here, no more than 10 of anything at once. Events.on('tradeItem', (seller, buyer, bought, sold, balance) => { return bought.every((line) => line.amount <= 10); }); Events.on('tradeDone', (seller, buyer, bought, sold, balance) => { console.info(`${buyer.nickname} paid ${-balance} to ${seller.nickname}`); });
In tradeItem and tradeDone, bought and sold are lists of { item, name, amount, price, metadata }. balance is the money the buyer receives, and a negative number is what they pay.
Opening: Trade.open(seller, buyer, options?) returns the session id. It returns 0 if a player is
gone or not yet in the world, if both are the same player, or if a tradeOpen handler returned
false.
Prices: prices and buyPrices name items the same way as Items and take whole money units.
Items without a price use the server's price for their quality, from the item config or the game.
Changes while open: if either inventory changes, the screen updates, and a basket made on the old screen is refused.
Ending: a trade ends when any of these happens:
- the buyer closes the screen
- either player leaves or is knocked out
- a script calls
Trade.close(session) - the resource that opened it stops
tradeClosed(seller, buyer, reason) then fires with the same reasons as vendorClosed.
Trade.activeSession(player) gives the trade a player is in as buyer or seller, or 0.
Reference: Trade.