Appearance
Shops & Currency
Buying and selling through shops, and declaring and formatting currencies.
Shop System
Item shops with buy/sell, finite stock, time-of-day hours, and price modifiers.
Shops are defined in GameInit and referenced by a string id. A shop tracks its own inventory (items for sale), stock levels, open/closed state, and optional time-of-day hours. Prices can be adjusted globally per shop via markup/discount factors.
(shop-define: "id", merchant, "npcId", markup, 0.2, discount, 0.1) (GameInit)
Registers a new shop. Only the id is required; all kwargs are optional.
| Kwarg | Type | Default | Description |
|---|---|---|---|
merchant | string | None | NPC id of the merchant. Displayed in the shop header if provided. |
markup | number | 0 | Fraction added to item base price on buy. 0.2 = 20% over base. |
discount | number | 0 | Fraction subtracted from item base value on sell. 0.3 = player gets 70% of item value. |
currency | $variable | primary currency | The currency variable the player pays with. Defaults to the primary currency registered via (currency-define:). |
wallet | $variable | None | The shopkeeper's money variable. When set: (shop-buy:) transfers the purchase price into this variable instead of destroying it; (shop-sell:) checks the shopkeeper can afford the sell price and deducts from it. Supports 3-part namespace paths ($npc.bartender.gold). |
Open/closed state: Shops start open by default. Use (shop-hours:) to configure time-of-day hours, or (shop-open:) / (shop-close:) to manually control state. When a shop is closed, (shop-buy:) and (shop-sell:) return false and push an error feedback message; they do not throw. Use (shop-is-open:) to check before showing the shop UI.
ana
(shop-define: "bar_shop", merchant, "bartender", markup, 0.1)
(shop-define: "blacksmith", merchant, "mira", markup, 0.2, discount, 0.4)
(shop-define: "market_stall")
// Shop with currency and wallet — money flows between player and merchant
(shop-define: "tavern", merchant, "bartender",
currency, $player.gold,
wallet, $npc.bartender.gold
)(shop-add: "id", "itemId", price, N, stock, N, requires, <condition>) (GameInit)
Adds an item to a shop's catalog. Only id and itemId are required.
| Kwarg | Type | Default | Description |
|---|---|---|---|
price | number | item's value property | Base buy price in gold. Markup is applied on top of this. |
stock | number | unlimited | How many can be purchased. Decrements on each buy. Omit for infinite stock. |
requires | expression | None | Condition that must be true for the item to appear in (shop-browse:). |
ana
(shop-add: "bar_shop", "beer", price, 5) // unlimited stock
(shop-add: "bar_shop", "whiskey", price, 20, stock, 3) // only 3 available
(shop-add: "general_store", "map", price, 50, requires, $quest.started_journey is false)(shop-open: "id") / (shop-close: "id")
Manually set a shop's open or closed state. Useful in event handlers or scripted story beats.
ana
(shop-open: "bar_shop")
(shop-close: "bar_shop")(shop-hours: "id", open, "periodName", close, "periodName")
Registers time-of-day open and close periods. The shop auto-opens when $world.timeOfDay matches the open period and auto-closes when it matches the close period. Period names correspond to whatever string values your game uses for $world.timeOfDay.
ana
(shop-hours: "bar_shop", open, "morning", close, "night")
(shop-hours: "blacksmith", open, "morning", close, "evening")Auto-management fires when (time-advance:) changes $world.timeOfDay. If the shop has no hours set, its open/closed state is controlled entirely by explicit (shop-open:) / (shop-close:) calls.
(shop-browse: "id")
Renders the shop's available inventory as an HTML grid in the current zone. Shows item name, buy price (base + markup), and stock. If the shop is closed, shows "The shop is currently closed." If the shop has no available items, shows "Nothing available for sale."
Items with a failing requires condition are hidden entirely.
ana
(shop-browse: "bar_shop")Grid CSS classes: .shop-grid, .shop-col-head, .shop-cell, .shop-item-name, .shop-item-price, .shop-item-stock. Override in game.css.
(shop-buy: "id", "itemId")
Executes a purchase. Deducts the buy price from the shop's configured currency variable (default: primary currency), decrements the shop's stock, and pushes a feedback message. If the shop has a wallet variable configured, the purchase price is transferred into it.
If itemId names a stackable template, adds one unit of it to the player's inventory. If itemId names a unique template, creates a new instance and adds that instance to the player's inventory, the same as (add: $inv, "itemId").
Returns false and pushes an error feedback if: shop is closed, item is out of stock, or the player can't afford it. Returns true on success.
ana
(link: "Buy a beer")[(action: Bar_BuyBeer)]
:: Bar_BuyBeer
(shop-buy: "bar_shop", "beer")(shop-sell: "id", "itemId")
Sells one unit of itemId from the player's inventory to the shop. Adds the sell price to the shop's configured currency variable. Returns false if the shop is closed, the player doesn't have the item, or (when a wallet is configured) the shopkeeper can't afford the sell price; true on success.
itemId is whatever key the item occupies in $inv: a template id for stackables, or an instance uid for uniques (e.g. from (ids: $inv)). Selling a unique instance frees it from the item system, same as (destroy:).
Sell price formula: floor(item.value × (1 − discount)), where discount is the shop's discount factor configured via (shop-define:). The default discount is 0, meaning the player receives the item's full value. A discount of 0.4 means the player receives 60% of the item's base value.
Wallet: When the shop has a wallet configured, the sell price is deducted from the shopkeeper's wallet (they pay you from their own funds), and the macro returns false if they cannot afford it.
Restocking: Selling an item back to the shop does restock it: the shop's stock count for that item is incremented by 1 after a successful sale.
ana
(shop-sell: "bar_shop", "lockpick")(shop-price: "id", "itemId") → number
Expression macro. Returns the current buy price for an item (base price + shop markup). Use with (print:) to display prices inline.
ana
A beer costs (print: (shop-price: "bar_shop", "beer")) gold.(shop-stock: "id", "itemId") → number
Expression macro. Returns the remaining stock count, or -1 for unlimited. Use with (if:) to gate stock-dependent options.
ana
(if: (shop-stock: "bar_shop", "whiskey") > 0)[
(link: "Buy whiskey")[(action: Bar_BuyWhiskey)]
](shop-is-open: "id") → boolean
Expression macro. Returns true if the shop is currently open. Use with (if:) to show or hide shop UI.
ana
(if: (shop-is-open: "bar_shop"))[
(shop-browse: "bar_shop")
](else:)[
The bar is closed for the night.
]Full shop pattern
ana
// GameInit (_init.ana)
(shop-define: "bar_shop", merchant, "bartender", markup, 0.1)
(shop-add: "bar_shop", "beer", price, 5)
(shop-add: "bar_shop", "whiskey", price, 20, stock, 3)
(shop-hours: "bar_shop", open, "morning", close, "night")
// Bar_OrderMenu passage
(if: (shop-is-open: "bar_shop"))[
(shop-browse: "bar_shop")
](else:)[
The bar isn't serving right now.
]
@zone(options)
(link: "Buy a beer")[(action: Bar_BuyBeer)]
(link: "Buy whiskey")[(action: Bar_BuyWhiskey)]
(link: "Never mind")[(update: Bar_BartenderGreet)]
// Bar_BuyBeer passage
(shop-buy: "bar_shop", "beer")Currency System
Currency in Ana is a variable-backed system. Define one or more currency variables using (currency-define:), then use (pay:) and (earn:) to move value, and (currency:) to display formatted balances.
The first defined currency becomes the primary currency, the default used by (pay:), (earn:), and (currency:) when no variable is specified.
ana
// GameInit — define currencies
(currency-define: $player.gold, "USD")
// or with custom formatting:
(currency-define: $player.gold, symbol, "gp", decimals, 0, position, "suffix")(currency-define:)
Config macro. Registers a variable as a currency. The first call sets the primary currency.
ana
(currency-define: $player.gold)
(currency-define: $player.gold, "USD")
(currency-define: $player.gold, name, "Gold Coins", symbol, "g", decimals, 0, position, "suffix")Arguments:
| Argument | Type | Description |
|---|---|---|
$variable | globalVar | The dotPath variable holding the currency amount (required) |
| ISO code | string | Optional second positional arg: "USD", "GBP", "EUR", "JPY", "CAD", "AUD", "CHF", "CNY"; auto-fills symbol/decimals/position |
name, "..." | kwarg | Display name for the currency |
symbol, "..." | kwarg | Symbol string (e.g. "g", "$") |
decimals, N | kwarg | Number of decimal places (default 0) |
position, "prefix"|"suffix" | kwarg | Symbol position (default "suffix") |
Key-based matching: Registering $player.gold automatically applies the same format to any other variable whose last path segment is gold, including $npc.bartender.gold, $flowershop.gold, or any namespace. You only need one (currency-define:) per currency type; NPC and shop wallets pick up the format automatically.
(currency:)
Expression macro. Formats a currency value for display. Must be used inside a block body or with (print:) to appear in prose; see Expression macros in prose in the authoring guide.
ana
(currency:) // primary currency balance, formatted
(currency: $player.gold) // $player.gold balance, using its registered format
(currency: $npc.bartender.gold) // NPC wallet — auto-matches gold format via key-based lookup
(currency: 9.99, "USD") // format a literal value using an ISO codeDisplay patterns:
ana
// In a block body (renders inline):
Your gold: (text-style: "bold")[(currency:)]
// In prose with (print:):
Old Bill has (print: (currency: $npc.bartender.gold)) left.(pay:)
Statement macro. Deducts an amount from a currency variable. Supports optional transfer semantics so money can move from one variable to another.
ana
(pay: 50) // deduct 50 from primary
(pay: 50, $npc.merchant.gold) // deduct 50 from primary, add 50 to merchant wallet
(pay: $player.bank, 50) // deduct 50 from $player.bank
(pay: $player.bank, 50, $npc.merchant.gold) // transfer 50 from bank to merchantDoes not enforce affordability; check the balance yourself before paying.
(earn:)
Statement macro. Adds an amount to a currency variable. Supports optional transfer semantics.
ana
(earn: 50) // add 50 to primary
(earn: 50, $npc.merchant.gold) // add 50 to primary, deduct 50 from merchant wallet
(earn: $player.bank, 50) // add 50 to $player.bank
(earn: $player.bank, 50, $npc.merchant.gold) // transfer 50 from merchant to bank