docs: economy architecture lockdown (v0.2) #62

Merged
Matt merged 1 commits from docs/economy-architecture-lockdown into main 2026-06-06 21:09:45 +00:00
+126 -51
View File
@@ -1,6 +1,6 @@
# Brass and Sigil — Economy & Quest System Design # Brass and Sigil — Economy & Quest System Design
> Status: design draft (v0.1). Not yet implemented. > Status: architecture locked (v0.2). Implementation ready to begin.
> Owner: matt > Owner: matt
> Last updated: 2026-06-06 > Last updated: 2026-06-06
@@ -120,27 +120,31 @@ The same accept → complete → return pattern works for every guild:
| **Library** | "Recover this lost tome" | find the book in a structure | NPC pays + grants reputation | | **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 | | **Foundry** | "Craft 10 Andesite Casings" | craft them | NPC pays commission |
### Player-facing tracking & cancellation ### Player-facing tracking & cancellation (locked)
A quest engine that players can't inspect is worthless. The design ships with three layers of visibility: Three surfaces, **all shipping together in Phase 3.** Not iterated, not optional.
#### V1 — command-driven (Phase 3, with the engine) | 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 <quest_id> <npc_tag>` (KubeJS-registered command). Dialog branching uses scoreboard objectives that KubeJS mirrors quest state into. |
| Command | Effect | | Command | Effect |
|---|---| |---|---|
| `/quests` | Lists all of your ACTIVE and READY quests with progress, source guild, and reward | | `/quests` | Lists your ACTIVE / READY quests with progress, source guild, reward |
| `/quests info <id>` | Detail view for one quest — description, objective, progress, reward | | `/quests info <id>` | Detail view |
| `/quests cancel <id>` | Drops an active quest, freeing your slot. The quest goes on cooldown as if completed (prevents accept-cancel-accept RNG farming) | | `/quests cancel <id>` | Drops an active quest, frees the slot. Applies the same cooldown as if completed (prevents accept-cancel-accept RNG-shopping). |
Cheap to ship. Works on mobile via the Telegram bridge. Doesn't require any extra mods. ### Why NOT FTB Quests for bounties
#### V2 — Quest Log book item (Phase 3.5) FTB Quests is the right tool for **progression** (Phase 5+), not for daily bounties. Confirmed via source review:
Every new player joins with a **Quest Log** book in their inventory (KubeJS `PlayerEvents.loggedIn` grants it once). Right-click to open. Dynamic Patchouli-style pages list active/ready quests, with a "Cancel" button per quest. Same backend as the `/quests` commands — just a nicer surface. - **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.
#### V3 — Sidebar HUD (later) For long-form progression questing, all three are non-issues. So: FTB Quests for the campaign, custom KubeJS system for daily bounties.
Optional scoreboard sidebar showing 2-3 most recent quests + progress, always-on. Implemented via vanilla scoreboard objectives driven by KubeJS. Players can toggle on/off via `/quests sidebar`.
### Quest slot limit ### Quest slot limit
@@ -152,9 +156,9 @@ Players have a **maximum of 3 active quests at a time.** This is the friction th
A quest moving to READY frees the slot for accepting a new one even before turn-in. A quest moving to READY frees the slot for accepting a new one even before turn-in.
### KubeJS implementation sketch ### KubeJS implementation — locked specifics
- **State storage:** per-player JSON in `world/data/bns_quests/<uuid>.json`, written via KubeJS persistent-data hooks. Schema: - **State storage:** per-player JSON in `world/data/bns_quests/<uuid>.json`. Schema:
```json ```json
{ {
"active": [ "active": [
@@ -164,7 +168,8 @@ A quest moving to READY frees the slot for accepting a new one even before turn-
"progress": 7, "progress": 7,
"target": 10, "target": 10,
"rewardSpurs": 50, "rewardSpurs": 50,
"source": "adventurers_guild" "source": "adventurers_guild",
"bossbarId": "bns:quest_advg_plunderer_10_<uuid>"
} }
], ],
"completed": { "completed": {
@@ -172,10 +177,41 @@ A quest moving to READY frees the slot for accepting a new one even before turn-
} }
} }
``` ```
- **Event tracking:** standard KubeJS event handlers (`EntityEvents.death`, `ItemEvents.pickedUp`, `BlockEvents.broken`) read the player's active quests and increment.
- **Dialog branching:** Easy NPC dialog with conditions that call KubeJS to check quest state and route the dialog appropriately. - **Event tracking:** KubeJS event handlers (`EntityEvents.death`, `ItemEvents.pickedUp`, `BlockEvents.broken`) read the player's active quests and increment progress + update bossbar value.
- **Reward delivery:** call Numismatics' wallet API to deposit spurs.
- **Commands:** registered via `ServerEvents.commandRegistry` in KubeJS, scoped to the player who runs them. - **Bossbar lifecycle:** on quest accept, KubeJS creates a unique per-player bossbar via `server.runCommandSilent('bossbar add <id> <name>')`, 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.<npc_tag>` that KubeJS mirrors quest state into. Dialog `Command` action triggers `/bns_quest accept <id> <npc_tag>` 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')`.
--- ---
@@ -243,18 +279,34 @@ A quest moving to READY frees the slot for accepting a new one even before turn-
--- ---
## 8. Mod stack (we have what we need) ## 8. Mod stack (locked)
| Need | Mod | Status | ### Already in pack
| Need | Mod | Version |
|---|---|---| |---|---|---|
| Currency + wallets + shop blocks | Numismatics | ✅ In pack | | Currency + wallets + shop blocks | Numismatics | 1.0.20 |
| NPC quest-givers + dialog | Easy NPC | ✅ In pack | | NPC dialog presentation | Easy NPC | 6.16.x (6.17.0 available, minor bump) |
| Quest engine (progression) | FTB Quests | ⏳ Planned, not added | | Plot claims | FTB Chunks | 2101.1.14 |
| Plot claims | FTB Chunks | ✅ In pack | | Team management | FTB Teams | 2101.1.9 |
| Tier system | FTB Ranks | ❓ Not in pack — add when we get to tiers | | Core library | FTB Library | 2101.1.31 |
| Glue & quest state | KubeJS | ✅ In pack | | Cross-mod compat | Architectury API | 13.0.8 |
| Glue, commands, state | KubeJS | 2101.7.2 |
**Not adding:** CustomNPCs (Easy NPC covers it), Citizens (Bukkit-only), PMMO (out of scope), generic "quest giver" mods (FTB Quests + KubeJS do it). ### 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)
--- ---
@@ -262,18 +314,18 @@ A quest moving to READY frees the slot for accepting a new one even before turn-
Each phase is independently shippable. Earlier phases unlock value without depending on later ones. Each phase is independently shippable. Earlier phases unlock value without depending on later ones.
| Phase | Deliverable | Why this order | | Phase | Deliverable | Spawn build required? |
|---|---|---| |---|---|---|
| **0** | Spawn skeleton built — 7 guild buildings placed (rough). | Locations have to exist before NPCs can stand in them. | | **0a** | **Mod additions PR:** FTB Quests + FTB XMod Compat + FTB Ranks (+ minor Easy NPC bump). New pack version. | No |
| **1** | **Merchant's Bazaar V1:** Numismatics sell shop (fixed prices, no DR yet) for diamonds & netherite. | Cheapest to ship, immediate economy unlock. | | **0b** | Spawn skeleton — 7 guild building blockouts (rough). **User does in-game when time permits.** Non-blocking for everything else. | Yes (user) |
| **2** | **Civic Office V1:** Plot claim cost via KubeJS `/ftbchunks claim` override. | Real money sink. Tests FTB Chunks integration depth. | | **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 |
| **3** | **Adventurer's Guild V1:** Bounty board engine. 5 daily quests, accept→complete→return flow, `/quests` commands, 3-slot cap. | The biggest gameplay feature. Validates the universal quest pattern. | | **2** | **Plot claim cost:** `FTBChunksEvents.before('claim')` deducts spurs. Free first 9 chunks, 50/chunk to 25, 200/chunk beyond. | No |
| **3.5** | Quest Log book item (V2 of tracking surface). | Nicer UX surface on top of the same engine — non-blocking. | | **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 the spur-priced trade table. | Closes the exploit. Big JSON pass. | | **4** | **Villager rebalance:** strip vanilla, ship spur-priced trade table via `ServerEvents.villagerTrades`. | No |
| **5** | **Library / Academy:** FTB Quests anchor + first chapter of progression quests. | Long-form content begins. | | **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** | **Civic Office V2:** FTB Ranks integration + tier upgrades. | Endgame money sink. | | **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** (Bazaar V2). | Most engineering, biggest anti-farm payoff, ship last. | | **7** | **Dynamic exchange rates:** global supply-pool pricing for raw mats. | No |
| **8+** | Foundry, Tavern, deeper Library quests, dungeons-with-Lootr. | Beyond V1. | | **8+** | Lootr dungeons, Foundry crafting commissions, deeper FTB Quests chapters, player-vs-player bounties (if wanted). | TBD |
--- ---
@@ -290,24 +342,47 @@ Each phase is independently shippable. Earlier phases unlock value without depen
--- ---
## 11. Open decisions ## 11. Decisions made
These need an actual call before implementation: Resolved before implementation:
1. **Keep emeralds as a secondary collectible?** Or fully retire them now that villagers don't use them? | Question | Decision |
2. **Sethomes / warps as paid services?** Or free baseline? |---|---|
3. **PvP bounties** (players posting bounties on other players)? Probably no for V1, revisit later. | Quest UI for bounties | Custom (bossbars + commands + Easy NPC) — NOT FTB Quests |
4. **Lootr** — add for shared dungeon loot, or skip until structured dungeons exist? | Quest UI for progression | FTB Quests |
5. **Daily quest count** — start with 5? 8? Refresh window — true daily (00:00 UTC) or per-player 24h? | 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 ## 12. Implementation notes
- Document each phase's KubeJS scripts under `pack/overrides/kubejs/server_scripts/economy/<phase>.js` - KubeJS scripts organized under `pack/overrides/kubejs/server_scripts/economy/`:
- State data under `world/data/bns_economy/` — readable JSON, easy to debug - `01_numismatics_api.js` — Java.loadClass wrapper helpers (read/deposit/deduct)
- All NPC dialog trees authored in Easy NPC's editor, exported as preset JSONs into `pack/overrides/world/datapacks/bns-fuel/data/easy_npc/` - `02_quest_state.js` — load/save `world/data/bns_economy/<uuid>.json`
- Trade table for villager rebalance lives in a single `villager_trades.js` so one file is the source of truth - `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/<uuid>.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.
--- ---