Files
brass-and-sigil/docs/bnstoolkit-architecture.md
Matt 9eb8f540c4 pack: v0.32.0 — FTB Quests + XMod Compat + Ranks; restrictive travel design
Mods added (Phase 0a substrate for the bnstoolkit economy work):
  - FTB Quests 2101.1.25 (progression quest UI for Phase 5+)
  - FTB XMod Compat 21.1.8 (KubeJS hooks for FTB Chunks/Quests events)
  - FTB Ranks 2101.1.3 (tier system via permission nodes)

Design revision: restrictive travel.

This is a Create Aeronautics pack — the journey is the point. Removed
all instant-teleport perks from the tier ladder. Players get one
starter Waystone and use it for spawn↔base; further travel means
building airships, trains, vehicles. Pressure on real movement is
intentional.

Removed perks: sethomes, /back after death.
Removed system: the full sethome subsystem (commands, SavedData,
cooldowns) from the bnstoolkit mod scope.

Tier table reduced from 10 columns to 8: cost, wilderness chunks,
plot slots, daily bounties, shop fee, chat colour, join broadcast.

Mod phasing collapsed from 7 phases to 6 (M2 sethome system removed,
later phases shift up).

Future perk dimensions list updated with two waystone-related
candidates (slot cap, XP cost reduction) as the natural travel-
progression hooks for later tier-perk revisions.

Verified live: all 3 mods discovered + loaded, port 25565 binds,
no FATAL/Crashed/Exception lines in startup log. Server resumed
to 0.32.0 cleanly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 23:22:47 +00:00

26 KiB

bnstoolkit — Architecture & Perk Specification

Status: design draft (v0.1). Not yet implemented. Owner: matt Last updated: 2026-06-06

bnstoolkit is the custom companion mod for the Brass and Sigil economy system. It provides the UI layer (GUI screens, HUD), the FTB Chunks map mixins for spawn plot integration, and the data models the economy needs. All gameplay logic (bounty rotation, state machine, villager trades, Numismatics integration, etc.) lives in KubeJS and is called into by the mod via network packets.

This doc is the source of truth for the mod design. It must be reviewed before any Java is written.

See also: economy-system.md for the gameplay side of the design.


1. Purpose & scope

In scope

  • GUI screens built on FTB Library's UI framework (matches FTB Quests / FTB Chunks visual language)
  • Mixins into FTB Chunks' map UI for spawn-plot cost overlay, hover tooltips, and click-to-purchase popups
  • Custom HUD render layer for quest progress (replaces stacked vanilla bossbars)
  • Data persistence for plot ownership (server-side, saved with world)
  • Network packets for client↔server state sync
  • Admin commands: /bnstoolkit reload, debugging commands
  • Config-driven tier perks (tiers.json) and plot definitions (plots.json)

Out of scope

  • Bounty quest engine (lives in KubeJS)
  • Villager trade rebalance (lives in KubeJS)
  • Numismatics direct integration (lives in KubeJS — mod calls KubeJS commands to debit/credit)
  • FTB Quests integration (FTB Quests handles its own UI for the progression campaign)
  • FTB Ranks admin (FTB Ranks handles permissions directly; mod just reads them)
  • Sethome / /back / fast-travel features — explicitly removed. The pack uses Waystones as the only travel mechanic. Restrictive travel is a design feature for a Create Aeronautics pack: airships, trains, and vehicles are the intended way to cover distance.

2. High-level architecture

                 ┌──────────────────────┐
                 │   Player (client)    │
                 └──────────┬───────────┘
                            │ keybind / NPC click / chunk click
                            ▼
            ┌───────────────────────────────────┐
            │       bnstoolkit (client)          │
            │   • FTB Library UI screens         │
            │   • FTB Chunks map mixin           │
            │   • Quest progress HUD             │
            └───────────────┬───────────────────┘
                            │ network packets
                            ▼
            ┌───────────────────────────────────┐
            │       bnstoolkit (server)          │
            │   • Plot ownership data            │
            │   • Sethome data                   │
            │   • Network packet handlers        │
            │   • /bnstoolkit reload command     │
            └─────┬───────────────────────┬─────┘
                  │                       │
       reads      │                       │ calls KubeJS commands /
   FTB Ranks /    │                       │ fires events for KubeJS
   FTB Chunks /   │                       │ to react to
   Numismatics    ▼                       ▼
            ┌───────────────┐    ┌──────────────────────┐
            │ FTB / NeoForge │    │     KubeJS scripts   │
            │     stack      │    │ • Bounty engine      │
            └───────────────┘    │ • Quest state         │
                                  │ • Numismatics calls   │
                                  │ • Villager trades    │
                                  │ • Tier promotion     │
                                  └──────────────────────┘

Key invariant: the mod owns UI + plot data + sethomes. KubeJS owns gameplay logic. They communicate via network packets and KubeJS commands. The mod never directly mutates Numismatics balances or quest state — it asks KubeJS to do that via commands.


3. Package layout

bnstoolkit/
├── build.gradle                       NeoForge 1.21.1 + FTB Library dep
├── src/main/java/uk/sijbers/bnstoolkit/
│   ├── BnsToolkit.java                Mod entry, lifecycle
│   ├── client/
│   │   ├── ClientEvents.java          Keybinds, client setup
│   │   ├── screen/
│   │   │   ├── QuestLogScreen.java
│   │   │   ├── BountyBoardScreen.java
│   │   │   ├── TierUpgradeScreen.java
│   │   │   ├── PlotPurchaseScreen.java
│   │   │   ├── ShopBrowserScreen.java
│   │   │   └── widgets/               (subclass FTB Library widgets)
│   │   ├── hud/
│   │   │   └── QuestProgressHud.java  IGuiOverlay
│   │   └── mixin/
│   │       ├── ChunkScreenPanelMixin.java
│   │       └── ChunkButtonMixin.java
│   ├── server/
│   │   ├── command/
│   │   │   └── BnsToolkitCommand.java   /bnstoolkit reload, debug
│   │   ├── data/
│   │   │   ├── PlotRegistry.java        loads plots.json
│   │   │   └── PlotOwnership.java       SavedData, plot owner UUIDs
│   │   ├── event/
│   │   │   ├── PlayerJoinHandler.java   tier-based join broadcast
│   │   │   └── ChatColourHandler.java   tier-based chat name colouring
│   │   └── net/
│   │       └── (packet implementations — see §6)
│   ├── common/
│   │   ├── data/
│   │   │   ├── TierConfig.java          loads tiers.json
│   │   │   ├── BountyDef.java           DTO mirrored from KubeJS
│   │   │   ├── QuestStateView.java      snapshot for UI display
│   │   │   └── PlotDef.java             plot definition (region, price)
│   │   └── net/
│   │       └── BnsPacket.java           network channel + payload base
│   └── resources/
│       ├── META-INF/neoforge.mods.toml
│       ├── bnstoolkit.mixins.json
│       └── assets/bnstoolkit/lang/en_us.json
└── src/main/resources/
    └── data-config/                     packed defaults, copied on first run
        ├── tiers.json
        └── plots.json

4. Screen specifications

All screens extend FTB Library's BaseScreen. They share a common header (player name, current tier, spur balance) so navigation feels consistent.

4.1 Quest Log

Opened by: keybind (default Q+B?) or right-clicking a Quest Log block placed at any guild building.

Layout: three tabs across the top — Active / Available / Completed.

  • Active tab: list of currently-accepted bounties with progress bars. Each row clickable; click expands a detail panel with description, source guild, reward, and a Cancel quest button. Cancel triggers a confirmation modal warning about cooldown.
  • Available tab: list of bounties currently offered (rotated daily by the KubeJS engine). Each row shows the bounty source, reward, cooldown remaining (if any). Click → detail panel with Accept button.
  • Completed tab: historical log of completed bounties this week, for satisfaction value. No interaction beyond viewing.

Network calls:

  • On open: client requests current quest state from server (S2CQuestSnapshot)
  • On accept/cancel click: C2SAcceptBounty(id) / C2SCancelQuest(id)

4.2 Bounty Board

Opened by: right-clicking the Adventurer's Guild Quartermaster NPC (Easy NPC dialog action runs /bnstoolkit openboard adventurers_guild) OR clicking an in-world bnstoolkit:bounty_board block.

Layout: poster-board style — the day's available bounties as visual "cards" laid out in a grid, each with mob icon, name, description, reward, accept button. Friendly to non-tech users.

Network calls:

  • On open: S2CBountyBoardSnapshot(guild_id) returns today's bounty list
  • On accept: same C2SAcceptBounty(id) as Quest Log

4.3 Tier Upgrade Screen

Opened by: right-clicking the Civic Office Magistrate NPC.

Layout: full ladder shown as a vertical or horizontal track. Your current tier highlighted (glowing badge). Next tier shows cost + delta perks (e.g. "+50 chunks, +1 plot slot, +2 sethomes, +1 bounty, -2% shop fee, chat colour: yellow"). Big Upgrade to <Tier> button at the bottom, greyed out if you don't have the spurs.

Hovering any future tier shows its full perk list in a side panel.

Network calls:

  • On open: S2CTierSnapshot returns current tier + balance
  • On upgrade click: C2SRequestTierUpgrade → server verifies balance, calls KubeJS /bnstoolkit_internal upgrade_rank <uuid> which charges spurs and runs ftbranks add <player> <tier>

4.4 Plot Purchase Popup

Opened by: clicking an unowned plot region in the FTB Chunks map UI (via the mod's mixin into ChunkScreenPanel).

Layout: modal overlay on top of the FTB Chunks map. Shows: plot ID, plot type (standard/premium), region size in chunks, owner (none), price. Your current spur balance. Your remaining plot slots (e.g. "2 of 3 used"). Purchase and Cancel buttons. If you're at your slot cap or don't have enough spurs, Purchase is greyed out with the reason shown.

Network calls:

  • On purchase: C2SBuyPlot(plot_id) → server checks slot capacity, asks KubeJS to charge spurs, on success records ownership in PlotOwnership SavedData.

4.5 Shop Browser

Opened by: right-clicking the Bazaar Master NPC at the Merchant's Bazaar (provides a curated view of all currently-listed shop blocks

  • NPC sell shop).

Layout: searchable item list with current prices, sort by category. Click an item to see who's selling it / where the shop block is located. The NPC sell shop (diamonds, netherite etc.) shows as a special "Official Bazaar" category with fixed prices and the current diminishing-return multiplier per item.

Note: individual Numismatics shop blocks keep their native UI when right-clicked — this Bazaar Master is the curated central view, not a replacement for shop block interaction.

Network calls:

  • On open: S2CShopBrowserSnapshot (asks KubeJS for current listings)
  • Selecting an item → S2CShopItemDetail(item_id) for full info

4.6 Quest Progress HUD

Render layer: IGuiOverlay, drawn over the vanilla HUD.

Layout: small panel in top-right (configurable position) showing each active quest as a row — quest name + progress bar + N/M numeric. Fades on inactive (no progress in last 30s) so it doesn't visually clutter when you're not actively bounty-hunting. Re-appears the moment any quest progresses.

State source: KubeJS pushes updates via S2CHudQuestUpdate packet when any quest's progress changes (debounced to once per second maximum per quest). The HUD reads from a client-side cache.


5. Mixin targets

Minimal — we don't fork FTB Chunks, we inject only what we need.

Target Method Purpose Mixin file
ChunkScreenPanel drawBackground (TAIL) Overlay drawing — cost preview for the currently-hovered or selected chunk, "Your plot slots: N of M" text in a corner ChunkScreenPanelMixin.java
ChunkScreenPanel$ChunkButton addMouseOverText (TAIL) Append "Cost: N spurs" or "Plot: " to the per-chunk hover tooltip ChunkButtonMixin.java
ChunkScreenPanel$ChunkButton onClicked (HEAD, with cancellation if applicable) If the clicked chunk is a plot region, open PlotPurchaseScreen instead of triggering a regular claim ChunkButtonMixin.java

That's it. Three injection points across two mixin files. No modification of FTB Library, FTB Quests, FTB Ranks, or FTB Teams.


6. Network packet contract

NeoForge PayloadRegistrar. All packets versioned via a single PROTOCOL_VERSION constant; bump on schema change.

Server → Client

Packet Payload When sent
S2CQuestSnapshot List<QuestStateView> On Quest Log open, on any quest-state change
S2CBountyBoardSnapshot List<BountyDef> (5-10 entries) On Bounty Board open
S2CTierSnapshot (currentTier, balance, perksByTier) On Tier Upgrade Screen open
S2CShopBrowserSnapshot List<ShopListing> On Shop Browser open
S2CHudQuestUpdate (questId, progress, target) On quest progress (debounced 1s)
S2CPlotPurchaseResult (success, message) After C2SBuyPlot resolves
S2CTierUpgradeResult (success, message) After C2SRequestTierUpgrade resolves

Client → Server

Packet Payload What server does
C2SAcceptBounty questId Call /bnstoolkit_internal accept_bounty <uuid> <questId> (KubeJS)
C2SCancelQuest questId Call /bnstoolkit_internal cancel_quest <uuid> <questId> (KubeJS)
C2SBuyPlot plotId Verify slot capacity, ask KubeJS to debit spurs, record ownership
C2SRequestTierUpgrade (none — server knows player) Verify balance, ask KubeJS to debit + promote
C2SOpenBoard guildId (optional context) Reply with appropriate snapshot

All C2S packets include the player implicitly (NeoForge passes the ServerPlayer in the handler).


7. Data persistence

Data Storage Lifetime
Plot ownership bnstoolkit:plots — vanilla NeoForge SavedData attached to overworld Persists with world. Authoritative source.
Quest state KubeJS-managed JSON at world/data/bns_economy/quests/<uuid>.json Not the mod's concern — the mod only reads via packets
Tier configuration config/bnstoolkit/tiers.json (hot-reloadable) Until edited
Plot definitions config/bnstoolkit/plots.json (hot-reloadable via /bnstoolkit reload) Until edited

Plot ownership is server-authoritative. Clients only get views via packets — they never write to these stores directly.


8. Configuration files

config/bnstoolkit/tiers.json

Defines the 13-tier ladder. Hot-reloadable.

{
  "tiers": [
    {
      "id": "peasant",
      "displayName": "Peasant",
      "cost": 0,
      "perks": {
        "wildernessChunks": 9,
        "plotSlots": 0,
        "dailyBountySlots": 3,
        "shopFeeDiscount": 0,
        "chatColour": "grey",
        "joinBroadcast": "none"
      }
    },
    /* ... 12 more tiers ... */
  ]
}

config/bnstoolkit/plots.json

Defines spawn plot regions. Each plot has a unique ID, region bounds (chunk coordinates), type (standard/premium), and price.

{
  "plots": [
    {
      "id": "bazaar_stall_01",
      "displayName": "Bazaar Stall 1",
      "type": "standard",
      "price": 2000,
      "chunks": [[10, 20], [10, 21]],   // chunk coords, inclusive
      "world": "minecraft:overworld"
    },
    {
      "id": "bazaar_corner_premium",
      "displayName": "Premium Corner Plot",
      "type": "premium",
      "price": 5000,
      "chunks": [[15, 25], [15, 26], [16, 25], [16, 26]],
      "world": "minecraft:overworld"
    }
  ]
}

9. KubeJS integration surface

The mod calls into KubeJS via custom commands registered by KubeJS (ServerEvents.commandRegistry). All have the bnstoolkit_internal prefix to mark them as not-for-player-use:

Command Purpose
/bnstoolkit_internal accept_bounty <uuid> <questId> Mark quest as ACTIVE in KubeJS state
/bnstoolkit_internal cancel_quest <uuid> <questId> Cancel quest, apply cooldown
/bnstoolkit_internal complete_quest <uuid> <questId> Mark quest READY (for testing/manual)
/bnstoolkit_internal turn_in_quest <uuid> <questId> Pay reward via Numismatics
/bnstoolkit_internal debit <uuid> <amount> <reason> Charge spurs (used by plot purchase, tier upgrade)
/bnstoolkit_internal credit <uuid> <amount> <reason> Pay spurs
/bnstoolkit_internal upgrade_rank <uuid> <tierId> Promote via FTB Ranks
/bnstoolkit_internal get_balance <uuid> Returns current spur balance (used for screen header)

All commands return a JSON result via the command output that the mod parses. KubeJS handles the actual Numismatics + FTB Ranks calls (it already has the Java.loadClass helpers from the economy KubeJS scripts).

KubeJS in turn pushes events to the mod via custom payload packets when quest state changes (so the HUD can update without polling).


10. Build & release

  • Repo: new private Gitea repo minecraft/bnstoolkit (separate from the modpack repo for clean release-versioning, but mirrored to the modpack's pack/overrides/mods/ on each release)
  • Build: standard NeoForge 1.21.1 ModDevGradle template
  • Dependencies: Architectury API (provided), FTB Library (provided), Numismatics (compileOnly), FTB Chunks (compileOnly)
  • CI: Gitea Actions runs ./gradlew build on push to main, uploads the jar as a release artifact
  • Modpack integration: the modpack's bump-version flow includes pulling the latest bnstoolkit release jar and updating pack.lock.json / manifest like any other mod
  • Versioning: semantic — 1.0.0 for V1 ship, 1.x.y for patches, 2.0.0 if we ever change the network protocol or data format

11. Perk specifications (detailed)

Every perk listed in the 13-tier ladder, expanded with mechanics, implementation, and edge cases.

11.1 Wilderness chunks

What it does: caps how many wilderness chunks (outside spawn) a player can claim via the FTB Chunks map. Claiming within the cap is free; over the cap is refused with an in-game message.

Implementation: FTB Ranks permission node ftbchunks.max_claimed_chunks set per rank in the FTB Ranks config (written by the mod on first launch from tiers.json). FTB Chunks reads this permission natively — no mod-side enforcement needed.

Edge case: if a player downgrades (which they can't normally, but might happen via op command), they keep already-claimed chunks over the new cap, but can't claim more until they free some.

11.2 Plot slots

What it does: caps how many spawn plots (predefined regions at spawn, see plots.json) a player can own concurrently. Independent of wilderness chunks.

Implementation: stored in PlotOwnership SavedData. On purchase, mod checks playerOwnedPlots.size() < tier.plotSlots. If at cap, the Plot Purchase Popup shows the slot count and disables the Purchase button.

Releasing a plot: players can sell their plot back via a "Release" button in the plot-detail screen (opens when they click their own plot in the map). Releases the slot. Refund: 50% of purchase price.

11.3 Daily bounty slots

What it does: how many bounties a player can have ACTIVE simultaneously. Once the slot is freed (by completion or cancel), the player can accept another from any guild board.

Implementation: enforced in KubeJS bounty engine. The mod's Bounty Board and Quest Log screens read the slot count via packet and grey out the Accept button when at cap.

Edge case: a quest in READY state still counts against your active slots until you turn it in. This is intentional — pushes players to actually visit the NPC to claim.

11.4 Shop fee discount

What it does: reduces the "merchant cut" deducted when you sell items at the Bazaar's NPC sell shop. The Bazaar charges a base 25% fee on all sales; your tier provides a discount that reduces this toward zero.

Worked example: you sell a diamond worth 10 spurs at the Bazaar.

  • Peasant (0% discount): 25% fee → you receive 7.5 spurs (rounded down to 7).
  • Knight (-5% discount): 20% fee → 8 spurs.
  • Sovereign (-25% discount): 0% fee → full 10 spurs.

Implementation: KubeJS sell-shop handler reads the seller's tier and computes effective payout. Rounded down to nearest integer.

Edge case: player-to-player Numismatics shop blocks do NOT apply this fee — those are direct peer transactions, no Bazaar middleman. The fee only applies to the official Bazaar sell shop.

11.5 Chat colour

What it does: the player's name in chat appears in this colour, visible to everyone. Visual rank marker.

Implementation: KubeJS PlayerEvents.chat event modifies the chat component for the player's display name based on their tier permission (read from FTB Ranks). The mod also colours the player's nameplate above their head (via a small client packet on tier change that other clients use to set the display name colour locally).

Colour ladder:

  • 1-2 Peasant/Farmer: grey
  • 3 Citizen: white
  • 4 Merchant: yellow
  • 5 Knight: green
  • 6 Baron: cyan
  • 7 Viscount: blue
  • 8 Earl: purple
  • 9 Marquess: gold
  • 10 Duke: orange
  • 11 Archduke: red
  • 12 Grand Duke: bright red
  • 13 Sovereign: rainbow (animated via colour-cycling text component)

Edge case: the Founder cosmetic [Founder] prefix sits before the coloured name, in a distinct colour (probably bright yellow or white-bold). Sovereign + Founder is visually busy but acceptable — only one player on the server will have both.

11.6 Join broadcast

What it does: when the player logs in, this controls what message goes to everyone else on the server.

Levels:

  • none: standard vanilla <player> joined the game.
  • simple: rank-prefixed message in their chat colour. Example:
    [Knight] Matt has logged in.
    
  • fancy: multi-line, with a separator. Example:
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     [Viscount] Matt has arrived
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    
  • epic: multi-line, with a sound effect (e.g. trumpet fanfare, configurable per-tier). Larger ASCII separator. Possibly an ASCII art banner of the rank.
  • epic + particles: as epic, plus a 5-second particle effect at the player's spawn location (firework spark, end-rod sparkle, etc.) for everyone nearby to see.
  • epic + spectacular: as epic + particles, plus a server-wide effect — actionbar message visible to everyone, brief light-up of the sky if Sovereign joins.

Implementation: KubeJS PlayerEvents.loggedIn reads tier and dispatches the appropriate message. Particle/sound effects are server-side commands (/particle, /playsound).

11.7 Future perk dimensions (not in V1, but easy to add later)

Architecture supports adding new perk columns in tiers.json without code changes (the schema is open-ended). Candidates for later expansion:

  • Waystone slot cap — limit how many waystones a player can place and/or own. Starter waystone is granted; higher tiers unlock 2nd, 3rd, etc. Cleanest in-theme perk for a Create Aeronautics pack where restrictive travel is intentional.
  • Waystone XP cost reduction — vanilla Waystones charges XP per teleport. Tier-based discount on this cost (or eventual exemption for Sovereign).
  • Particle trail — high tiers leave a visible particle wake when walking
  • Custom join sound — different sound effect per tier on login
  • Custom title prefix — text added before chat name beyond the colour (e.g. ⚔ Knight Matt)
  • Priority bounty access — higher tiers see a larger pool of daily bounties to pick from
  • Tier-gated vendor stock — specific Numismatics shops only visible to Baron+ etc.
  • Daily login bonus — small spur reward for logging in, scaling by tier
  • Death keep-inventory chance — RNG % to keep inventory on death (controversial, gate carefully)

12. Open implementation questions

These can be deferred until they become blocking, but worth flagging:

  1. Sovereign tier rainbow chat name — Minecraft text components don't natively support gradient text. Implementation options: (a) cycle through colours character by character, or (b) animate via packet on a tick interval. Choose during Phase 0c scaffolding.
  2. Plot release refund percentage — proposed 50%. Tune via playtest.
  3. Sethome cooldown values per tier — proposed scaling from 60s (low tiers) to 5s (Sovereign). Confirm in tiers.json schema.
  4. /back interaction with dimension changes — does dying in the End and /backing return you to the End or to overworld? Default: returns to wherever you died including the dimension.
  5. Founder cosmetic prefix colour — bright yellow? Bold white? Animated? Settle during implementation.
  6. Sovereign spectacular join effect — exact effects need design pass (sky flash? Server-wide actionbar text?). Tune in tiers.json.

13. Implementation phases (mod-specific)

Aligned with the economy doc's Phase 0c (mod scaffolding):

Mod phase Deliverable
M0 Gradle project, mod metadata, FTB Library dep wired, network channel registered, empty placeholder for each screen class. Builds + loads cleanly into the running pack.
M1 Tier system: TierConfig loads from tiers.json, TierUpgradeScreen works, perks applied via FTB Ranks promotion command. Founder cosmetic prefix. Chat colour. Join broadcast at all tiers.
M2 Plot system: PlotRegistry + PlotOwnership, FTB Chunks mixins, PlotPurchaseScreen. KubeJS integration for spur debit.
M3 Quest UI: QuestLogScreen, BountyBoardScreen, QuestProgressHud. Network packets between KubeJS bounty engine and the mod.
M4 Shop browser: ShopBrowserScreen.
M5 Polish — animations, sound effects, particle effects for high tiers, theming pass on all screens.

Each phase is independently shippable as a 1.x.0 release of the mod jar.


See also

  • economy-system.md — gameplay-side design (currency, quest mechanics, villager rebalance, etc.)
  • MEMORY.md entries — KubeJS Rhino gotchas, defaultconfigs pattern