Commit Graph

9 Commits

Author SHA1 Message Date
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