Commit Graph

11 Commits

Author SHA1 Message Date
Matt 8cc2e45011 0.6.2: Adventurer's Journal — bounties/rates/plots + smart Back button
Build / build (push) Has been cancelled
QuestLogScreen reworked from a generic status summary into a proper
adventure journal. Three sections, top to bottom:
- Active Bounties (N / cap) with progress bars + reward preview
- Counting House Rates — every wealth-token's effective per-unit spur
  payout for the player's tier fee + lifetime DR. Lets players check
  rates from the field instead of travelling to the Counting House.
- Owned Plots (N / cap)
- Footer: "FTB Quests is a separate mod — open your Quest Book item"

Lang title changed to "Adventurer's Journal" so it's distinct from
the FTB Quests system. Internal class + key stay QuestLog* for
backward-compat of /bns open questlog.

BaseBnsScreen.makeBackOrClose(screen) — labels the close button "Back"
with a back-arrow when FTB Library's previous-screen is set, "Close"
otherwise. Hub now passes the current screen wrapper as the previous
screen when opening the Journal, so close-from-Journal returns to Hub.

Bug fix: sub-panel children now pass `this` correctly to their parent
(was missing — same FTB Library latent bug).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 16:19:43 +00:00
Matt 2888cf1814 0.6.1: pre-size sub-panels in addWidgets to fix blank panel content
Build / build (push) Has been cancelled
FTB Library's Panel.refreshWidgets runs in this order:
  1. clearWidgets
  2. addWidgets    (parent creates sub-panels, default size 0x0)
  3. for each Panel child: refreshWidgets()  ← recurses HERE
  4. alignWidgets  (parent sizes children, but it's too late)

Step 3 means sub-panels' own addWidgets/alignWidgets run with
width=height=0. Children sized via setSize(width, 12) end up 0 wide,
visible content disappears.

Fix is mechanical: every screen now sizes its sub-panels in
addWidgets() BEFORE the add() call. By the time the recursive
refreshWidgets hits the sub-panel, its dimensions are correct.

Caught when HubScreen rendered blank (status panel was set up to fill
the body but its Row children were 0px wide). Same latent issue in
TierUpgrade, CountingHouse, Bounty, Plot, Quest Log — all fixed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 15:51:08 +00:00
Matt 1d38d86a2e 0.6.0: Counting House refactor — bank-credited wealth exchange
Build / build (push) Has been cancelled
Major economic-design pivot per design conversation:

* Bazaar renamed to "Counting House" — wealth exchange, not general shop
* Players bring wealth-tokens (diamond, netherite_ingot, ancient_debris,
  diamond_block); transactions credit their Numismatics bank account
  directly (not inventory)
* SellCatalogue cut from 9 items to 4; iron/gold/coal/lapis/redstone/
  emerald removed (those belong on player-run Numismatics Vendor blocks)
* Diminishing-returns floor tightened 50% -> 30% so deep-grinders feel
  the friction

Numismatics integration via reflection (NumismaticsBridge):
* Auto-creates personal bank account on PlayerLoggedInEvent
* SpurBalance.totalSpurs() now sums inventory coins + bank balance
* TierStateUpdatePacket carries cashOnHand + bankBalance separately so
  the hub can break them out
* HUD overlay shows three lines: tier / cash / bank — no card lookup,
  always shows total wealth regardless of what's on the player
* /bns sell handled in Java (CountingHouseCommands); KubeJS 02_sell_
  shop.js retired to .retired
* /bns wallet (+ deposit/withdraw) for managing cash<->bank flow
* Wire-version bumped 0.4 -> 0.5

Hub becomes status-only:
* Removed Civic Tier / Bazaar / Bounty / Plot transactional buttons
* Kept Quest Log, Refresh, Close
* Status panel shows tier, cash on hand, bank balance, total wealth,
  fee%, plot slots, bounty slots
* Footer: "Visit spawn NPCs for trades, bounties, plots, tier upgrades"

Screen rename: ShopBrowserScreen -> CountingHouseScreen.
OpenScreenDispatcher accepts both "counthouse" and "shop" keys
(latter as backward-compat alias).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 15:32:10 +00:00
Matt 1630a8370f 0.5.1: fix UI widget coords — relative not absolute
Build / build (push) Has been cancelled
FTB Library's Widget.setPos(x, y) takes coords RELATIVE to the parent
panel. My code passed (getX() + offset, getY() + offset), so widgets
ended up at panel-pos * 2 + offset, far off-screen to the bottom-right.

Fix: drop getX()/getY() everywhere in alignWidgets. Refresh button,
close button, header bar, sub-panels — all relative now. Plus
BaseBnsScreen.contentTop()/contentBottom() are now plain offsets
(20 and getHeight() - 24) instead of getY()-prefixed.

Visual bug surfaced with user screenshot of HubScreen showing all
buttons stacked on the right edge of the screen with empty dark panel
on the left.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 14:42:52 +00:00
Matt 0b90e94079 0.5.0: per-tile village supply/demand throttle for spur-paying trades
Build / build (push) Has been cancelled
Anti-farm system that limits how much income players can extract from
villager trading in one location. Designed to make 50-librarian halls
diminish into futility without disabling villager trading altogether.

Model:
- World is partitioned into 64×64 block tiles (no POI lookups,
  villager-position rounded by integer division)
- Each tile has a CompoundTag of {item_id -> remaining_demand} at
  server.persistentData.bnsVillageDemand[dim:tileX:tileZ]
- Demand caps per item are set in VillageDemand.java; paper=20,
  wheat=25, rotten flesh=15, iron=6, diamond=1, etc.
- Demand refills linearly: full cap restored over 1 hour of real time
  (no scheduled tick — refresh computed lazily on each query from
  lastUpdated timestamp)
- A 1-tile multi-profession hall earns ~150 spurs/hour at saturation.
  Scaling income requires building halls in distant tiles — that's
  overworld logistics, not micro-exploit.

Implementation:
- VillageDemand.java: per-item cap config
- VillageDemandTracker.java: lazy-refresh + atomic decrement against
  the KubeJS server-scope NBT (existing reflection accessor)
- VillageTrackedMerchantOffer.java: MerchantOffer subclass that
  overrides increaseUses() to decrement demand on trade completion
- VillagerTradeRebalance.java: at offer-build time, queries current
  tile demand for the cost item. If saturated (<1 left), the offer
  comes back with uses=maxUses (vanilla "out of stock" UI). Otherwise
  wrapped in VillageTrackedMerchantOffer so post-trade decrement fires.
- Wandering trader: uses whatever tile he's wandered into. No special
  case — it's just whichever tile he stands in at trade time.

Wandering traders, modded merchants (entity is not Villager): same
tracking, same caps. Anything that produces a spur as a trade result
is throttled by the tile.

Hooks: zero mixins. MerchantOffer.increaseUses() is vanilla-public
and fires server-side after the trade settles, so a subclass override
is the right hook point.

Perf: lazy refresh = no scheduled ticks. Per-trade overhead is one
NBT lookup + a few timestamp ops, microseconds. Idle servers do zero
work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:26:48 +00:00
Matt 884a8cea0b 0.4.0: NBT key fix + hub UI + /bns open + network hardening
Build / build (push) Has been cancelled
Post-review fixes from the parallel code/security/hotkey/NPC audits.

## #35 — NBT keys (CRITICAL: screens were showing fictional data)
ServerEconomyBridge.pushBountySnapshot now reads the actual KubeJS keys:
- bnsBountyPool (compound id->1) — was bnsBounties.pool
- bnsBountyActive (compound id->{progress,target,reward,...}) — was bnsBounties.active
- bnsBountyCompleted (compound id->long cooldown-end-ms) — was bnsBounties.completed
- Filters completed by `cooldown-end > now` so old cooldowns drop off

ServerEconomyBridge.pushPlotSnapshot now reads server-scope state via
KubeJS's MinecraftServer mixin (kjs$getPersistentData) using reflection,
no compileOnly dep on KubeJS. Falls back to empty CompoundTag if the
method isn't there. Resolves owner names from PlayerList for online
players, GameProfileCache for offline.

## #36 — Hub UI + key rebind
Single primary key: B opens HubScreen. Direct shortcut keys (K/J/N/H/M)
all default to UNBOUND so they no longer compete with the ~100 other
pack mods. Audit found M = vanilla Social Interactions + FTB Chunks
fullscreen map (hard conflict, removed); H/N collided with Voice Chat
toggles (minor, removed); K/J were safe (kept rebindable for power users).

New HubScreen routes to the 5 sub-screens via labelled buttons + shows
tier/balance/fee at a glance in the header.

## #37 — /bns open <screen>
Java-registered Brigadier subcommand at RegisterCommandsEvent time,
merges with KubeJS's /bns root. New OpenScreenPacket (S2C) pushes a
screen open to the calling player. Screens: hub, tier, bounty, plot,
shop, questlog. Suggests valid screens via tab-complete.

This unlocks:
- Easy NPC dialogue COMMAND actions calling /bns open shop, etc.
- FTB Quests reward commands
- Command blocks / /trigger objectives
- Future scripted intros / tutorials

Also: patched /srv/fast/brass-sigil-server/server/config/easy_npc/
security.cfg to allow `bns` in executeAsUserCommandAllowList.ALL.

## #38 — Network hardening
- Per-player token bucket on C2S packets (10/s sustain, 20 burst).
- All STRING_UTF8 codecs capped: kind=8, args=80, which=16.
- Verb whitelist replaced with structured parse: args -> tokens ->
  per-verb regex validators -> rebuilt command. No more prefix-match
  surprises if /bns sell syntax expands.
- Per-player in-flight guard on tier upgrades (500ms) closes the
  double-spend race window.
- Bridge logs sanitised (control-char-stripped) so a hostile client
  can't inject newlines/ANSI into server logs.

## Review-driven nits also folded in
- Per-player tickCount (was shared static — N-player skew)
- VillagerTradeRebalance: try/catch around inner.getOffer; null cost-A
  passthrough; round-to-nearest denom split (was floor, ~10% price haircut)
- SpurHud caches Component refs (avoid 120/s allocation)
- ClientBnsState.completedIds now HashSet (O(1) per-frame lookups)
- BountyBoardScreen: deleted the empty `if (isActive)` dead branch
- Wire-format version bumped 0.2 -> 0.4 (added OpenScreenPacket)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 09:24:23 +00:00
Matt 14dc69f22f 0.3.1: drop ToastPacket — client-only imports broke server-side load
Build / build (push) Has been cancelled
ToastPacket imported net.minecraft.client.gui.components.toasts.* and
net.minecraft.client.Minecraft at class scope. When BnsNetwork.onRegister
Payloads referenced ToastPacket::handle on the dedicated server,
NoClassDefFoundError fired (the client-only classes don't exist on
the dedicated server side).

Toast feedback wasn't load-bearing — KubeJS already chat-prints the
result of every /bns verb. Players see "Sold 8x diamond for 60 spurs"
in the chat as before, and the screens re-render off the snapshot
the bridge pushes after each routed verb.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 02:41:13 +00:00
Matt 10e9980bfd 0.3.0: villager trade rebalance via VillagerTradesEvent
Build / build (push) Has been cancelled
Hooks NeoForge's VillagerTradesEvent + WandererTradesEvent at data-pack
reload time and wraps every existing ItemListing so the resulting
MerchantOffer has its emerald-cost-or-result items swapped to
Numismatics coin equivalents:

  1..7  emeralds  -> spurs (1:1)
  8..63 emeralds  -> bevels (1 bevel = 8 spurs)
  64+   emeralds  -> cogs (1 cog = 64 spurs)

Lives in bnstoolkit instead of a third-party mod because the obvious
choice — Modrinth's 'numismatics-villager-currency' — requires NeoForge
21.1.230+ and we're on 21.1.228. The two-page Java wrapper here is
simpler than the alternatives (writing every trade override JSON via
'data-trades') AND doesn't push us into a runtime upgrade.

Includes wanderer trades. Modded villager professions are picked up
automatically since we just wrap the event's trade list as-is.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 02:38:49 +00:00
Matt afe7d38827 0.2.0: feature-complete UI — 5 FTB-Library screens + HUD overlay
Build / build (push) Has been cancelled
All five screens implemented with FTB Library widgets (Panel/Widget/
SimpleTextButton). Layout matches FTB Quests/Chunks for visual
consistency with the rest of the pack.

Screens:
- TierUpgradeScreen   — 13-row ladder, current tier highlighted,
                        Upgrade button to next tier with cost/affordability check
- ShopBrowserScreen   — 9-cell item grid + detail pane, quick-sell
                        buttons (1/16/64) with live DR-aware price preview
- BountyBoardScreen   — daily pool + active set, accept/cancel/turn-in
                        per state, slot-cap indicator
- PlotPurchaseScreen  — plot list + detail pane, buy/release with
                        ownership colour-coding and tier-gated buy
- QuestLogScreen      — at-a-glance tier+balance summary, active
                        bounties with progress bars, owned plots

HUD overlay: top-left tier name + spurs balance, registered as a
NeoForge GUI layer above the experience bar. 1Hz server push keeps
the balance current without polling client-side inventory.

Network: replaces single smoke-test packet with 7-packet contract:
- C2S: RequestEconomySnapshot, RunBnsCommand (whitelisted)
- S2C: TierStateUpdate, SellSnapshot, BountySnapshot, PlotSnapshot, Toast

Server-side bridge (ServerEconomyBridge) reads player NBT and
Numismatics-by-item-id-string for balance, routes write verbs
through the existing KubeJS /bns chat commands so KubeJS remains the
source of truth for economy logic.

Key bindings (rebindable in vanilla controls menu):
- K  Civic tier ladder
- J  Bounty board
- N  Plot office
- H  Bazaar shop
- M  Quest log

FTB Library + Architectury maven repos wired in build.gradle as
compileOnly; the jar stays small since FTB Library is already in pack.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 02:33:53 +00:00
Matt 9892b0e737 0.1.1: drop NeoForge.EVENT_BUS.register(this) with no @SubscribeEvent methods
Build / build (push) Has been cancelled
The empty-class registration was rejected by NeoForge 21.1 at mod-loading
time with:
  IllegalArgumentException: class uk.sijbers.bnstoolkit.BnsToolkit
  has no @SubscribeEvent methods, but register was called anyway.

That tanked the dedicated server during construct-mods. Caught only after
deploying 0.1.0 to the live server — the local /gradlew build doesn't
exercise mod-loading. Will add a gameTestServer run later to catch this
class of bug pre-deploy.

When the first @SubscribeEvent listener lands (likely PlayerLoggedInEvent
in M1), the register call comes back together with it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 00:17:58 +00:00
Matt e99b5b1e0e M0: scaffold bnstoolkit mod (NeoForge 1.21.1)
Build / build (push) Has been cancelled
Empty mod that loads cleanly on client + dedicated server. Sets up the
package layout and milestone TODOs for the BNS in-game UI work.

- uk.sijbers.bnstoolkit.{BnsToolkit, BnsToolkitClient} mod entries
- 5 placeholder screens extending a shared BaseBnsScreen
  (TierUpgrade, BountyBoard, QuestLog, PlotPurchase, ShopBrowser)
- 3 key bindings (T/B/P) registered, handlers TODO M1+
- SpurHud overlay placeholder for M4
- Network: payload registrar + one smoke-test C2S/S2C pair
  (RequestTierState / TierStateUpdate)
- en_us.json for screen + keybind labels
- neoforge.mods.toml pinned to neo_version 21.1.228 (live server match)

Build verified: ./gradlew build -> bnstoolkit-0.1.0.jar (15.6 KB).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 00:10:54 +00:00