docs: economy architecture lockdown (v0.2)
Final architecture for the economy + quest system. No more V1/V2/V3
iteration cycles. Resolved against actual mod source code and verified
APIs via research spike.
Key decisions locked:
- Bounty UI: custom (bossbar + /quests commands + Easy NPC dialog).
NOT FTB Quests -- it lacks per-player visibility, has no abandon
event, and cooldown is per-team. Confirmed via FTBQ source review.
- Progression UI: FTB Quests (right tool for long-form content where
per-team cooldowns and one-shot rewards are fine).
- Numismatics: direct Java.loadClass access (no events shipped, no
KubeJS bindings). Documented exact API surface and call patterns.
- FTB Chunks claim cost: FTBChunksEvents.before('claim') with
event.setResult() to refuse. Requires adding FTB XMod Compat.
- Tier system: FTB Ranks (CurseForge only) + permission-driven chunk
limits + KubeJS-mediated upgrades via runCommandSilent('ftbranks add').
- Per-player HUD: bossbars, not scoreboards. Vanilla scoreboards are
global; bossbars natively per-player.
- Custom mod for V1: NOT NEEDED. Re-evaluate if blocked.
Mods to add in Phase 0a (one PR before any implementation):
- FTB Quests 2101.1.25
- FTB XMod Compat (latest)
- FTB Ranks 2101.1.3
Phasing updated: Phase 0a (mod additions) blocks the rest. Phase 0b
(spawn build) is non-blocking and user-paced; all systems can be
built and verified before spawn exists.
KubeJS file layout documented for clean implementation:
pack/overrides/kubejs/server_scripts/economy/{01..99}_*.js
11 open decisions resolved. 3 remain non-blocking.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+126
-51
@@ -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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user