# Brass and Sigil — Economy & Quest System Design > Status: architecture locked (v0.2). Implementation ready to begin. > Owner: matt > Last updated: 2026-06-06 --- ## 1. Goals - **One unified currency** — spurs (Numismatics). - **Multiple ways to earn, each one bounded.** No infinite money printer. - **Multiple sinks that scale with progression.** Money disappears as you advance, so wealth never dwarfs the cost of new ambitions. - **Spatial design that gives spawn personality.** Themed guild buildings, each with a clear purpose, each tied to the same wallet. - **Forward-compat with a proper quest line** via FTB Quests, while supporting daily/repeatable activities at spawn. - **Anti-automation by design**, not by patches. --- ## 2. Core architecture ``` ┌────── MONEY IN (bounded) ──────┐ │ │ Adventurer's Guild — Daily bounties (cooldowns, scaling) Merchant's Bazaar — Sell shop (fixed + DR), dynamic exchange Mint — Capped emerald → spur conversion * First-time milestones — One-shot per player FTB Quests — Progression milestone payouts │ ▼ ┌───────────────────┐ │ Player wallet │ ← Numismatics │ (spurs) │ └───────────────────┘ │ ▼ ┌────── MONEY OUT (scaling) ──────┐ │ │ Civic Office — Plot claim costs (FTB Chunks override) Civic Office — Tier upgrades (FTB Ranks integration) Merchant's Bazaar — Player-to-player job board (no minting) Villagers — Re-priced trades, net sink Custom services — Warps, sethomes, premium cosmetics ``` \* Only if we keep emeralds; see §5 — we're moving villagers to spurs, which probably retires the Mint as a currency-conversion station and repurposes it as a wallet/info hub. --- ## 3. Spawn — themed guild buildings Each guild is a destination. The guild's NPC is both the **entry point** (accept work) and **exit point** (return to claim reward). This pattern applies universally — see §4. | Building | Purpose | Primary mechanics inside | |---|---|---| | **Adventurer's Guild** | Combat & exploration | Bounty board (daily mob hunts), boss-kill quests, dungeon-delve quests (future) | | **Merchant's Bazaar** | Trade & raw-mat economy | Numismatics sell shop, dynamic exchange counter, player-driven job board | | **Civic Office** | Land & status | Plot claim office, tier upgrades, sethome/warp services | | **Library / Academy** | Knowledge & magic | FTB Quests anchor NPC, Ars Nouveau themed quests, lore terminals | | **Tavern** | Social & onboarding | First-time newbie quests, casual NPCs, food/buff vendor | | **Foundry / Workshop** | Crafting & commissions | Create-themed contracts, crafting orders, raw-material market | | **Mint** | Currency information | Wallet info, currency tutorial, possibly emerald exchange | ### Forward-compat with a full quest line The architecture is naturally dual-track: - **FTB Quests** = the *progression* backbone — chapter-by-chapter pack content, one-time rewards, story arcs. The campaign. - **Spawn-building NPCs** (Easy NPC + KubeJS) = the *daily/repeatable* layer — bounties, sell shops, services. The live activities. Both pay into the same wallet. Players run the campaign at their pace and the daily content for income/replay value. They reinforce each other rather than compete. --- ## 4. Quest & bounty flow (universal pattern) **Core principle:** for any guild, the player **accepts** work from an NPC, **completes** it in the world, then **returns** to that NPC to **turn it in** and claim the reward. No instant payouts. This: - Anchors guilds as destinations - Creates risk/reward tension (die on the return, lose the reward) - Encourages waypoint use and travel - Lets the server centrally control payouts and detect cheating ### State machine Each quest, per player, lives in one of these states: ``` AVAILABLE ─[player accepts]─► ACTIVE ─[objective met]─► READY ─[turned in]─► COMPLETED ▲ │ └──────────────────────[cooldown elapses]───────────────────────────────────────────┘ ``` - **AVAILABLE** — NPC offers the quest in dialog. Player has no current copy of it. - **ACTIVE** — Player accepted. KubeJS tracks objective progress via relevant events (`EntityEvents.death` for kills, `ItemEvents.firstRightClicked` for explorations, etc.). - **READY** — Objective met. Reward is *promised but not yet paid.* NPC dialog now offers a turn-in option. - **COMPLETED** — Reward paid out. Quest goes on cooldown for this player. - **AVAILABLE** (again) — after the cooldown, the NPC will offer it again. ### Worked example — daily bounty 1. **Adventurer's Guild NPC** has 5 randomized daily quests (e.g. "Slay 10 Plunderers — 50 spurs"). 2. Player right-clicks NPC → Easy NPC dialog → "Accept: Slay 10 Plunderers." 3. KubeJS writes `quest:plunderer_10` to the player's quest state with `count: 0`. 4. Player kills Plunderers in the world. `EntityEvents.death` for `supplementaries:plunderer` increments the count. 5. At count=10, KubeJS flips state to **READY** and notifies the player ("Return to the Adventurer's Guild to claim your reward."). 6. Player returns. Right-click NPC. Easy NPC dialog detects READY state and offers "Turn in: Slay 10 Plunderers." 7. KubeJS pays 50 spurs into the player's Numismatics wallet, flips state to **COMPLETED**, sets cooldown to 24h. ### Universal applicability The same accept → complete → return pattern works for every guild: | Guild | Accept | Complete | Return | |---|---|---|---| | **Adventurer's** | "Hunt 10 plunderers" | kill them | NPC pays bounty | | **Merchant's** | "Procure 32 diamonds for our buyer" | gather them | NPC pays inflated price + completes | | **Civic** | "Survey 5 unclaimed chunks" | walk through them | NPC pays survey fee | | **Library** | "Recover this lost tome" | find the book in a structure | NPC pays + grants reputation | | **Foundry** | "Craft 10 Andesite Casings" | craft them | NPC pays commission | ### Player-facing tracking & cancellation (locked) Three surfaces, **all shipping together in Phase 3.** Not iterated, not optional. | Surface | Role | Mechanism | |---|---|---| | **Bossbars** | Always-visible HUD — one bossbar per active quest, shows name + N/M progress bar | Vanilla `/bossbar` commands driven by KubeJS. Native per-player display (scoreboards are global; bossbars aren't). Up to 3 stacked at top of screen. | | **`/quests` commands** | Management — list, inspect, cancel | KubeJS `ServerEvents.commandRegistry`. Works in chat and over the Telegram bridge. Clickable chat components for `[cancel]` and `[details]`. | | **Easy NPC dialog** | Accept + turn-in at guild NPCs | Dialog action runs `/bns_quest accept ` (KubeJS-registered command). Dialog branching uses scoreboard objectives that KubeJS mirrors quest state into. | | Command | Effect | |---|---| | `/quests` | Lists your ACTIVE / READY quests with progress, source guild, reward | | `/quests info ` | Detail view | | `/quests cancel ` | Drops an active quest, frees the slot. Applies the same cooldown as if completed (prevents accept-cancel-accept RNG-shopping). | ### Why NOT FTB Quests for bounties FTB Quests is the right tool for **progression** (Phase 5+), not for daily bounties. Confirmed via source review: - **No abandon/cancel event** — players can `Ctrl+Shift+R` to reset quest progress, but nothing fires that KubeJS can hook → can't enforce cancellation cooldown. - **Cooldown is per-team, not per-player** — in a 4-player team, one member completing puts it on cooldown for all four. - **No per-player quest visibility** — gating "5 of 20 active today" requires `CustomTask` gates rather than first-class per-player visibility. For long-form progression questing, all three are non-issues. So: FTB Quests for the campaign, custom KubeJS system for daily bounties. ### Quest slot limit Players have a **maximum of 3 active quests at a time.** This is the friction that makes cancel meaningful: - Forces players to pick which quests they actually want — no "accept all 5, see what's easiest, cancel the rest" RNG-shopping. - Keeps the state files small. - Makes ready-to-turn-in queues meaningful (you can hold 3 unrewarded quests in your back pocket). A quest moving to READY frees the slot for accepting a new one even before turn-in. ### KubeJS implementation — locked specifics - **State storage:** per-player JSON in `world/data/bns_quests/.json`. Schema: ```json { "active": [ { "id": "advg_plunderer_10", "acceptedAt": 1717684800, "progress": 7, "target": 10, "rewardSpurs": 50, "source": "adventurers_guild", "bossbarId": "bns:quest_advg_plunderer_10_" } ], "completed": { "advg_plunderer_10": { "cooldownUntil": 1717771200, "completions": 3 } } } ``` - **Event tracking:** KubeJS event handlers (`EntityEvents.death`, `ItemEvents.pickedUp`, `BlockEvents.broken`) read the player's active quests and increment progress + update bossbar value. - **Bossbar lifecycle:** on quest accept, KubeJS creates a unique per-player bossbar via `server.runCommandSilent('bossbar add ')`, assigns it to that player only, updates `value` and `max` on progress. On turn-in or cancel: `bossbar remove`. - **Numismatics integration (direct Java access — no events shipped, no KubeJS bindings):** ```js const Numismatics = Java.loadClass('dev.ithundxr.createnumismatics.Numismatics'); const ReasonHolder = Java.loadClass('dev.ithundxr.createnumismatics.content.backend.ReasonHolder'); const acct = Numismatics.BANK.getAccount(player); // ServerPlayer const balance = acct.getBalance(); // int (spurs) acct.deposit(50); // pay quest reward const ok = acct.deduct(100, ReasonHolder.empty()); // returns false if broke Numismatics.BANK.markBankDirty(); // force persistence ``` - **Dialog branching:** Easy NPC dialog branches on scoreboard objective `bns.quest.` that KubeJS mirrors quest state into. Dialog `Command` action triggers `/bns_quest accept ` which routes to KubeJS. - **Commands:** registered via `ServerEvents.commandRegistry` in KubeJS. Scoped to the player who runs them. The custom `/bns_quest` namespace handles NPC-triggered actions; `/quests` is the player-facing tracker. - **FTB Chunks claim cost (Phase 2):** requires `FTB XMod Compat` mod. Then: ```js FTBChunksEvents.before('claim', event => { const player = event.source.player; const acct = Numismatics.BANK.getAccount(player); const cost = computeClaimCost(player); if (acct.getBalance() < cost) { event.setResult(ClaimResult.YOU_DO_NOT_HAVE_ENOUGH_SPURS); // custom enum value return; } acct.deduct(cost, ReasonHolder.empty()); }); ``` **Note:** the `.before` may fire twice (sim + real). Guard with idempotent state, or do balance check in `.before` and deduct in `.after('claim')`. --- ## 5. Villager rebalance **Decision:** move all villagers to **spurs** (not emeralds). Thematic consistency over preserving vanilla mental model. **Decision principles:** - Villagers are **net money sinks** — players spend more on villagers than they earn from them. - **Useful trades stay** — enchanted books, tools, food, decorative blocks. Reasonable spur prices. - **Trash trades get deleted** — no one ever wanted to sell rotten flesh or buy paper. - **Sell-to-villager trades exist but are bad** — `64 wheat → 1 spur`, `16 leather → 1 spur`. Farms still possible, but ~10× slower than any other income source. ### Price guidance (initial calibration — tune via playtesting) #### Buy-from-villager (player pays spurs) | Item | Tier-1 | Tier-3 | Tier-5 | |---|---|---|---| | Standard enchanted book (random) | 50 spurs | 120 spurs | 250 spurs | | Mending book | — | — | 800 spurs | | Diamond tool (unenchanted) | — | 80 spurs | — | | Diamond tool (enchanted) | — | — | 350 spurs | | Bookshelf | 15 spurs | — | — | #### Sell-to-villager (player gets spurs) | Item | Spurs per unit | |---|---| | Wheat | 1 per 64 | | Paper | 1 per 32 | | Leather | 1 per 16 | | Iron ingot | 2 per 1 | | Diamond | 8 per 1 *(matches sell-shop fixed price — players use whichever is closer)* | ### Scope ~60 trades × 5 profession tiers = ~300 trade entries to redesign. One-time data-design pass, implemented via `ServerEvents.villagerTrades` in KubeJS. --- ## 6. Money inflow sources (detailed) | Source | Cap / DR mechanism | Rough rate | |---|---|---| | **Daily bounties** | Per-player per-quest cooldown (24h) | ~200-500 spurs/day for an active player | | **Sell shop (fixed price + DR)** | Lifetime sales tracker per player, 10% price drop per 64 sold | ~50-200 spurs/hr while diamond stockpile lasts | | **Dynamic exchange** | Global supply pool — price drops with sales | Self-balancing | | **First-time milestones** | One-shot per player (`hasFlag`) | 5-50 spurs per milestone, ~10 milestones total | | **FTB Quest progression** | One-time per quest | Varies; cluster rewards at chapter completion | | **Villager sell trades** | Inherently slow + bad prices | ~50 spurs/hr from a serious villager farm | --- ## 7. Money outflow sinks (detailed) | Sink | Cost curve | Why this scales right | |---|---|---| | **Plot claims** | Free up to 9 chunks. 50 spurs/chunk to 25. 200 spurs/chunk beyond. | New players claim free starter areas; ambitious players pay real money | | **Tier upgrades** | Cumulative cost: 500 → 2k → 8k → 30k spurs | Endgame sink, status display | | **Buy-from-villager** | See §5 | Constant outflow as you build up | | **Premium cosmetics** | 100-2k spurs each | Optional sink for cash-flush players | | **Services** | Sethome: 50 spurs, warp: 25 spurs, /back: 10 spurs | Convenience taxes | --- ## 8. Mod stack (locked) ### Already in pack | Need | Mod | Version | |---|---|---| | Currency + wallets + shop blocks | Numismatics | 1.0.20 | | NPC dialog presentation | Easy NPC | 6.16.x (6.17.0 available, minor bump) | | Plot claims | FTB Chunks | 2101.1.14 | | Team management | FTB Teams | 2101.1.9 | | Core library | FTB Library | 2101.1.31 | | Cross-mod compat | Architectury API | 13.0.8 | | Glue, commands, state | KubeJS | 2101.7.2 | ### Adding in Phase 0a (mod-additions PR — blocks everything else) | Mod | Version | Reason | |---|---|---| | **FTB Quests** | 2101.1.25 | Progression quest UI (Phase 5+) | | **FTB XMod Compat** | latest 1.21.1 NeoForge | KubeJS hooks for FTB Chunks claim events + FTB Quests events | | **FTB Ranks** | 2101.1.3 | Tier system (Phase 6). CurseForge only, no Modrinth listing. | ### Custom mod **Not needed for V1.** Re-evaluate only if implementation hits a hard wall on a specific feature. ### Explicitly NOT adding - CustomNPCs (Easy NPC covers it) - Citizens (Bukkit-only) - PMMO / skill mods (out of scope) - Generic "quest giver" mods (FTB Quests + our KubeJS bounty engine cover it) --- ## 9. Phasing Each phase is independently shippable. Earlier phases unlock value without depending on later ones. | Phase | Deliverable | Spawn build required? | |---|---|---| | **0a** | **Mod additions PR:** FTB Quests + FTB XMod Compat + FTB Ranks (+ minor Easy NPC bump). New pack version. | No | | **0b** | Spawn skeleton — 7 guild building blockouts (rough). **User does in-game when time permits.** Non-blocking for everything else. | Yes (user) | | **1** | **Sell shop:** Numismatics shop blocks for diamonds (10 spurs) + netherite ingots (100 spurs). Lifetime-sale tracker for diminishing returns. | No — test anywhere, drop into Bazaar later | | **2** | **Plot claim cost:** `FTBChunksEvents.before('claim')` deducts spurs. Free first 9 chunks, 50/chunk to 25, 200/chunk beyond. | No | | **3** | **Bounty engine:** state machine + `/quests` commands + bossbar HUD + 3-slot cap. Daily quest pool with KubeJS-rotated dailies. NPC dialog hooks ready but inert until guild NPCs exist at spawn. | No (NPC layer activates once spawn is built) | | **4** | **Villager rebalance:** strip vanilla, ship spur-priced trade table via `ServerEvents.villagerTrades`. | No | | **5** | **Progression quest line:** FTB Quests anchor + first chapter. KubeJS rewards dispense spurs via Numismatics API. | No (anchor NPC at Library once spawn exists) | | **6** | **Tier upgrades:** FTB Ranks integrated. `/tier upgrade` deducts spurs and promotes via `ftbranks add`. Rank perms gate FTB Chunks claim limits. | No | | **7** | **Dynamic exchange rates:** global supply-pool pricing for raw mats. | No | | **8+** | Lootr dungeons, Foundry crafting commissions, deeper FTB Quests chapters, player-vs-player bounties (if wanted). | TBD | --- ## 10. Risk register | Risk | Likelihood | Mitigation | |---|---|---| | Bounty engine bugs out, players lose progress | Medium | Atomic state writes; logs of every state transition | | Villager farms still profitable after rebalance | Medium | Track player sell volumes; tune prices in patches | | KubeJS-EasyNPC dialog branching has limits | Low-Med | Verify hooks in a test world before Phase 3; fall back to command-driven NPCs if needed | | FTB Chunks `/claim` command override gotchas | Low | Test in dev world; FTB Chunks API is stable | | Plot cost feels punishing | Med | First 9 chunks free is the safety valve; tune from playtest | | Players bypass the system via creative / `/give` | Op-only | Document op responsibility; consider audit logs for op spur grants | --- ## 11. Decisions made Resolved before implementation: | Question | Decision | |---|---| | Quest UI for bounties | Custom (bossbars + commands + Easy NPC) — NOT FTB Quests | | Quest UI for progression | FTB Quests | | State storage | Per-player JSON in `world/data/bns_economy/` | | Numismatics access | Direct Java via `Java.loadClass` | | Chunk claim refusal | `FTBChunksEvents.before('claim')` with `event.setResult(...)` | | Tier system | FTB Ranks + permission-driven chunk limits | | Custom mod for V1 | No — only if a hard wall appears | | Sethomes / warps | Paid services (`/sethome` 50 spurs, `/warp` 25 spurs, `/back` 10 spurs) | | Daily quest count | 5 active at a time, refresh per-player 24h after last accept | | Emeralds | Retired as currency, retain as a trade material for villager-redirect trades | ### Still to decide (non-blocking — pick during implementation) - **PvP bounties** — out of scope for V1, revisit after V1 ships - **Lootr** — add only when structured dungeons exist (Phase 8+) - **Default-rank starting permissions** — to be set with the first FTB Ranks config pass --- ## 12. Implementation notes - KubeJS scripts organized under `pack/overrides/kubejs/server_scripts/economy/`: - `01_numismatics_api.js` — Java.loadClass wrapper helpers (read/deposit/deduct) - `02_quest_state.js` — load/save `world/data/bns_economy/.json` - `03_bossbar_hud.js` — per-player bossbar lifecycle - `04_commands.js` — `/quests`, `/bns_quest`, `/tier` registrations - `05_bounty_engine.js` — Phase 3 quest engine - `06_sell_shop.js` — Phase 1 shop block tracking + DR - `07_plot_costs.js` — Phase 2 FTB Chunks event handler - `08_villager_trades.js` — Phase 4 rebalance - `09_tier_upgrades.js` — Phase 6 rank upgrades - `99_dynamic_exchange.js` — Phase 7 - State data under `world/data/bns_economy/.json` per player. Readable, easy to debug. Migration to SQLite considered if dataset grows beyond ~100 players. - All NPC dialog trees authored in Easy NPC's in-game editor, exported as preset JSONs into `pack/overrides/world/datapacks/bns-fuel/data/easy_npc/`. Dialog `Command` actions call into `/bns_quest` namespace which routes to KubeJS handlers. - Trade table for villager rebalance: single source file `08_villager_trades.js`. Each profession × tier has its own block. One file = one diff to review during balance passes. --- ## See also - `MEMORY.md` entries: - `project-mc-player-economy.md` — earlier higher-level notes on Numismatics + plot system - `feedback-kubejs-rhino-gotchas.md` — KubeJS scripting constraints - `feedback-bns-pack-version-sync.md` — pack version + sync workflow - Existing tasks #20-#26 — to be reshuffled to mirror the phase plan above