Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35cbd84146 | |||
| c9f57dae9e | |||
| 83949d63b5 | |||
| 68aae5c917 | |||
| 0818fe8eab | |||
| f269cc4861 | |||
| ae183f27de | |||
| ec8f2b83cf | |||
| 28c816990a | |||
| 73f55768c3 | |||
| 48a70326d2 | |||
| 5f6a5aacdd | |||
| 8d9c04dc05 | |||
| ea9326819d | |||
| f2c9cabefe | |||
| ad9a261627 | |||
| bae44d0178 | |||
| 016985a5fc | |||
| a7f95691bb | |||
| 23efdee804 | |||
| 1859613f1f | |||
| a41d8cb531 | |||
| e770cb55cb | |||
| 338617e855 | |||
| ecd5933f9f | |||
| 38fbf80bc1 | |||
| 61dd3d3754 | |||
| 61a923cbef | |||
| 57b6cd8dea |
@@ -0,0 +1,114 @@
|
||||
# bnstoolkit feature-complete — testing notes for 2026-06-07 evening
|
||||
|
||||
> Shipped: pack v0.35.0 / bnstoolkit 0.3.1.
|
||||
> Repo: http://10.16.5.102:3000/minecraft/bnstoolkit
|
||||
> Server status: ✅ live + verified at 02:41 UTC.
|
||||
|
||||
## What's in the build
|
||||
|
||||
### 5 screens (FTB Library widgets — matches FTB Quests look)
|
||||
|
||||
All opened via key bindings. Rebindable in vanilla controls menu under
|
||||
"Brass and Sigil" category.
|
||||
|
||||
| Key | Screen | What it does |
|
||||
|---|---|---|
|
||||
| **K** | Civic Tier ladder | 13 rows, current tier highlighted. Right pane shows next tier's cost + perks + Upgrade button. Disabled if you can't afford it or are at Sovereign. |
|
||||
| **J** | Bounty Board | Today's 5-bounty pool + any active set. Detail pane: target, count, reward, progress. Accept / Cancel / Turn In buttons by state. |
|
||||
| **N** | Plot Office | Plot list with ownership state-coding (yours / taken / free). Detail pane: size, chunks, price. Buy / Release buttons. |
|
||||
| **H** | Bazaar shop | 9-cell sell item grid. Click an item, see DR-aware effective price for current tier. Sell 1 / 16 / 64 quick buttons. |
|
||||
| **M** | Quest Log | At-a-glance status: tier + balance + active bounties (with progress bars) + owned plots. |
|
||||
|
||||
### HUD overlay
|
||||
|
||||
Top-left of the screen, always visible: `<tier-colour><tier-name>` on
|
||||
one line, `<balance> spurs` on the second. Refreshes 1Hz from server
|
||||
push — picks up coin pickups immediately, no inventory polling.
|
||||
|
||||
Auto-hides when F1 is pressed or chat is open (standard NeoForge HUD
|
||||
layer behavior).
|
||||
|
||||
### Villager trade rebalance
|
||||
|
||||
Every emerald cost or result in vanilla + modded villager trades is
|
||||
now Numismatics:
|
||||
- 1..7 emeralds → spurs
|
||||
- 8..63 → bevels (1 bevel = 8 spurs)
|
||||
- 64+ → cogs (1 cog = 64 spurs)
|
||||
|
||||
Works automatically on:
|
||||
- All 14 vanilla villager professions
|
||||
- Wandering trader (generic + rare)
|
||||
- Any modded villager professions (we wrap the event's list, no enum
|
||||
of professions needed)
|
||||
|
||||
This was the "blocked on API" item. Built it into bnstoolkit instead
|
||||
of pulling a third-party mod, because the cleanest Modrinth option
|
||||
needed NeoForge 21.1.230+ and we're on 228.
|
||||
|
||||
## Testing path
|
||||
|
||||
```
|
||||
/bns me # confirm Peasant tier 1
|
||||
/give @s numismatics:spur 500 # give spurs for upgrades
|
||||
K # open tier ladder
|
||||
# click Upgrade — should pay 200 and bump you to Farmer
|
||||
|
||||
/bns admin tier set <you> 5 # jump to Knight for plot testing
|
||||
K # confirm new tier visible
|
||||
H # open bazaar
|
||||
# click diamond, click Sell 1 (you'll need a diamond in inventory)
|
||||
J # open bounty board, accept one
|
||||
# kill the target mobs — progress shows in chat AND the screen
|
||||
# turn in via the J screen
|
||||
N # open plot office, buy p001
|
||||
M # quest log shows everything
|
||||
|
||||
# Test villager trades:
|
||||
/summon villager ~ ~ ~
|
||||
# Right-click the villager once it picks a profession (place a workstation)
|
||||
# Trades should show spurs/bevels/cogs instead of emeralds
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
KubeJS server_scripts/economy/ ← single source of truth for state + logic
|
||||
↑
|
||||
│ (chat commands via server.getCommands)
|
||||
│
|
||||
ServerEconomyBridge.java ← reads NBT, sums Numismatics coins,
|
||||
routes write verbs through /bns
|
||||
↑↓
|
||||
4 snapshot + 2 command packets
|
||||
↑↓
|
||||
ClientBnsState.java ← single static state holder
|
||||
↑
|
||||
Screens read this each frame
|
||||
```
|
||||
|
||||
Source of truth stays in KubeJS. Java is just the UI layer and a thin
|
||||
write-path proxy. If you want to rebalance prices or add bounties, you
|
||||
do it in KubeJS — Java auto-reflects.
|
||||
|
||||
## Known gaps for future iteration
|
||||
|
||||
| Gap | Where to fix |
|
||||
|---|---|
|
||||
| No client toast notifications — feedback comes via KubeJS chat messages | Add a dist-isolated `ClientToastDispatcher` class (0.4.0) |
|
||||
| Bazaar grid doesn't tooltip the per-unit price | One `addMouseOverText` per `ItemCell` |
|
||||
| Plot office doesn't show a minimap | Render bluemap PNG snippet as background or use FTB Chunks' map widget |
|
||||
| Quest log doesn't show FTB Quests progression | Hook into FTB Quests' API once first chapter ships |
|
||||
| Numismatics coins as bunch — no fractional change | Bazaar pays in highest denom that fits; player converts at the Bank |
|
||||
| Wanderer rare-trade rebalance is greedy (every rare item costs are converted) | Profession-aware exclusion list if anything weird sneaks through |
|
||||
|
||||
## File map
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `pack/overrides/mods/bnstoolkit-0.3.1.jar` | The mod itself (95 KB) |
|
||||
| `pack/overrides/kubejs/server_scripts/economy/*.js` | State + business logic (unchanged from v0.33.0) |
|
||||
| `pack/overrides/world/serverconfig/ftbranks/ranks.snbt` | 13-tier rank config (unchanged) |
|
||||
| `docs/bnstoolkit-architecture.md` | Original architecture spec |
|
||||
|
||||
Source repo: http://10.16.5.102:3000/minecraft/bnstoolkit (main = 14dc69f at time of pack ship).
|
||||
@@ -0,0 +1,215 @@
|
||||
# Economy V1 — overnight build handoff
|
||||
|
||||
> Built 2026-06-06 / 23:00-23:55 UTC. All shipped via PR #67 (v0.33.0).
|
||||
> Server running clean at port 25565. All scripts verified loaded with 0 errors.
|
||||
|
||||
## TL;DR — what to test tomorrow
|
||||
|
||||
A full command-driven economy is live. Pure-server (no client mod yet), works in chat and over the Telegram bridge equally. Open the in-game chat or telnet and try:
|
||||
|
||||
```
|
||||
/bns help ← top of the iceberg
|
||||
/bns me ← your status
|
||||
```
|
||||
|
||||
That alone surfaces every command available. Below is the deeper walkthrough.
|
||||
|
||||
---
|
||||
|
||||
## What ships
|
||||
|
||||
### 1. 13-tier civic ladder (`/bns tier upgrade`)
|
||||
|
||||
Every player has a tier integer (1-13) in their NBT. New joiners are auto-set to tier 1 (Peasant). Upgrading costs spurs and runs `ftbranks add <player> <rank>` to apply the rank's permission nodes (chunk allowance, name format).
|
||||
|
||||
| # | Title | Cost to reach | Chunk allowance | Plot slots | Bounty slots | Shop fee |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 1 | Peasant | 0 | 9 | 0 | 3 | 25% |
|
||||
| 2 | Farmer | 200 | 18 | 0 | 3 | 25% |
|
||||
| 3 | Citizen | 750 | 35 | 1 | 3 | 25% |
|
||||
| 4 | Merchant | 2,500 | 60 | 1 | 4 | 23% |
|
||||
| 5 | Knight | 6,500 | 100 | 2 | 4 | 20% |
|
||||
| 6 | Baron | 15,000 | 150 | 2 | 5 | 18% |
|
||||
| 7 | Viscount | 35,000 | 220 | 3 | 5 | 16% |
|
||||
| 8 | Earl | 75,000 | 300 | 3 | 6 | 14% |
|
||||
| 9 | Marquess | 150,000 | 400 | 4 | 6 | 12% |
|
||||
| 10 | Duke | 300,000 | 525 | 5 | 7 | 10% |
|
||||
| 11 | Archduke | 700,000 | 700 | 6 | 7 | 8% |
|
||||
| 12 | Grand Duke | 1,500,000 | 900 | 7 | 8 | 6% |
|
||||
| 13 | Sovereign | 10,000,000 | 1,500 | 10 | 10 | 0% |
|
||||
|
||||
**Test it:**
|
||||
- `/bns me` shows your tier + balance + everything else
|
||||
- `/bns admin tier set <player> <1-13>` to jump anyone instantly
|
||||
- `/bns admin tier list` shows the full table in-chat
|
||||
- `/give @s numismatics:spur 64` for testing money
|
||||
- `/bns tier upgrade` pays + promotes
|
||||
|
||||
### 2. Bazaar sell shop (`/bns sell`)
|
||||
|
||||
Sell raw commodities for spurs. Tier discount reduces a flat 25% Bazaar fee. Diminishing returns lower the per-unit payout as you sell more of the same item over a lifetime (10% per 64 sold, floor 50%).
|
||||
|
||||
| Item | Base price |
|
||||
|---|---|
|
||||
| Diamond | 10 spurs |
|
||||
| Netherite ingot | 100 |
|
||||
| Emerald | 1 |
|
||||
| Gold ingot | 2 |
|
||||
| Iron ingot | 1 |
|
||||
| Lapis | 1 |
|
||||
| Redstone | 1 |
|
||||
| Coal | 1 |
|
||||
| Ancient debris | 500 |
|
||||
|
||||
**Test it:**
|
||||
- `/bns sell list` — your effective prices right now (after fee + DR)
|
||||
- `/bns sell minecraft:diamond 8` sells 8 diamonds
|
||||
- Sell ~70 diamonds and watch the per-unit price drop as the DR kicks in
|
||||
|
||||
### 3. Bounty engine (`/bns bounty`)
|
||||
|
||||
20 bounties in the catalogue. Each player gets a random 5 from the pool, refreshed every 24h per-player. Slot cap = your tier (3 at Peasant, up to 10 at Sovereign).
|
||||
|
||||
States: AVAILABLE → ACTIVE → READY → COMPLETED → (24h cooldown) → AVAILABLE.
|
||||
|
||||
- Kill bounties auto-track via `EntityEvents.death` — you'll get progress messages in chat
|
||||
- Cancelling applies the same 24h cooldown as completing (no RNG-shopping)
|
||||
|
||||
Highlights from the pool:
|
||||
- Slay 10 Zombies — 15 spurs
|
||||
- Hunt 5 Plunderers — 75
|
||||
- Slay 3 Wither Skeletons — 150
|
||||
- Slay a Skreecher — 400
|
||||
- Slay the Warden — 800
|
||||
- Slay a Warped Mosco — 700
|
||||
- Slay the Farseer — 900
|
||||
- Destroy a Void Worm — 1,200
|
||||
|
||||
**Test it:**
|
||||
- `/bns bounty list` — what's on offer for you today
|
||||
- `/bns bounty accept b_zombie_10` — accept
|
||||
- Kill 10 zombies — progress chat messages every 5, ready notification at 10
|
||||
- `/bns bounty active` — see your accepted with progress
|
||||
- `/bns bounty turnin b_zombie_10` — claim 15 spurs
|
||||
|
||||
### 4. Plot system (multi-slot) (`/bns plot`)
|
||||
|
||||
Existing plot system (3 plots at chunks 10,10 / 12,10-11 / 14-15,10-11) now supports tier-based multi-ownership. Slot cap from your tier (0 at Peasant, up to 10 at Sovereign).
|
||||
|
||||
- `/bns plot list` — all plots, their state, prices
|
||||
- `/bns plot buy <id>` — claim it (must be Citizen+ for at least 1 slot)
|
||||
- `/bns plot release <id>` — 50% refund, opens slot
|
||||
- `/bns plot mine` — list your owned plots
|
||||
|
||||
You'll need at least Citizen (tier 3) for any plot. **You can't buy a plot at Peasant.**
|
||||
|
||||
### 5. Waystones + welcome (pre-existing, untouched)
|
||||
|
||||
- One starter Waystone given on first login (per existing `welcome.js`)
|
||||
- Waystone placement cap of 1 per player (per existing `waystones_policy.js`)
|
||||
- Sharestones / Portstones banned via existing config
|
||||
|
||||
### 6. FTB Ranks 13-rank config
|
||||
|
||||
Live at `world/serverconfig/ftbranks/ranks.snbt`. Each rank has:
|
||||
- `ftbchunks.max_claimed_chunks` (sets the FTB Chunks claim cap)
|
||||
- `ftbchunks.max_force_loaded_chunks` (set to ~15% of claim cap)
|
||||
- `ftbranks.name_format` (chat colour)
|
||||
|
||||
FTB Ranks auto-applies `peasant` to all players via `condition: always_active`. Tier-up commands set the rank explicitly. The `admin` rank with `condition: op` overrides everyone — you'll appear as Admin in chat regardless of your civic tier, useful for op-mode actions.
|
||||
|
||||
---
|
||||
|
||||
## What's deferred
|
||||
|
||||
### Villager trade rebalance
|
||||
|
||||
**Blocked.** KubeJS 2101.7.2 doesn't ship a villager-trade event. I verified by extracting the KubeJS jar — `ServerEvents` only exposes `command`, `loaded`, `recipes`, `registry`, `tags`, `tick`, `unloaded`. No `villagerTrades`. No KubeJS-villager-trade addon on Modrinth for NeoForge 1.21.1.
|
||||
|
||||
Options to unblock later:
|
||||
1. **Add a third-party trade-overrides mod** — needs research (Villager Trade Tables-style mod for 1.21.1 NeoForge)
|
||||
2. **Build into the custom mod** — KubeJS calls into Java mixin
|
||||
3. **Mixin via a tiny standalone mod** — minimal Java project that just rebalances trades from a JSON
|
||||
|
||||
For now: vanilla villager trades remain. The 60+ trades I drafted live in the commit history (deleted file `pack/overrides/kubejs/server_scripts/economy/01_villager_rebalance.js`, restore from git when we unblock the API).
|
||||
|
||||
### Custom UI mod (bnstoolkit)
|
||||
|
||||
**Deferred to a session where you can iterate visually.** No Java/Gradle toolchain on glados, and the screens (QuestLog, BountyBoard, TierUpgrade, PlotPurchase, ShopBrowser, HUD, FTB Chunks mixin) all need visual feedback I can't get blind overnight.
|
||||
|
||||
The complete architecture spec is in `docs/bnstoolkit-architecture.md` — when you're ready to build, that's the source of truth. The KubeJS substrate I shipped tonight is exactly the backend the mod will sit on top of.
|
||||
|
||||
### Spawn build
|
||||
|
||||
Your manual task. The economy works from anywhere right now — no spawn buildings needed.
|
||||
|
||||
### FTB Quests progression chapters
|
||||
|
||||
Needs your arc design (themes, milestones). Mod is installed; the campaign content isn't.
|
||||
|
||||
---
|
||||
|
||||
## Files added/changed (PR #67)
|
||||
|
||||
```
|
||||
pack/overrides/kubejs/server_scripts/economy/
|
||||
00_tier_system.js NEW (170 lines)
|
||||
02_sell_shop.js NEW (197 lines)
|
||||
03_bounty_engine.js NEW (347 lines)
|
||||
04_player_commands.js NEW (146 lines)
|
||||
pack/overrides/kubejs/server_scripts/
|
||||
plots.js MODIFIED (+86 lines — multi-slot)
|
||||
pack/overrides/world/serverconfig/ftbranks/
|
||||
ranks.snbt NEW (122 lines — 13 ranks)
|
||||
pack/pack.lock.json version bump → 0.33.0
|
||||
```
|
||||
|
||||
## Quick recovery if something goes wrong
|
||||
|
||||
If a script errors at startup, KubeJS logs to:
|
||||
```
|
||||
/srv/fast/brass-sigil-server/server/logs/kubejs/server.log
|
||||
```
|
||||
|
||||
To hot-reload KubeJS server scripts without a server restart:
|
||||
```
|
||||
/kubejs reload server_scripts
|
||||
```
|
||||
|
||||
To revert this PR entirely if it bricks something:
|
||||
```
|
||||
cd /home/matt/dev/brass-and-sigil
|
||||
git revert <PR-67-merge-commit-hash>
|
||||
git push origin main
|
||||
```
|
||||
|
||||
The next pack-version-sync at MC server restart will undo all the changes.
|
||||
|
||||
## Suggested testing path
|
||||
|
||||
1. `/bns me` — sanity check, you should be Peasant tier 1
|
||||
2. `/give @s numismatics:spur 500` — give yourself spurs
|
||||
3. `/bns tier upgrade` — should pay 200 and bump you to Farmer
|
||||
4. `/bns tier upgrade` again — should pay 550 (the difference up to Citizen at 750) … actually wait — the cost is per-tier-to-reach. Each upgrade costs the NEXT tier's listed cost. So Peasant → Farmer = 200, Farmer → Citizen = 750, Citizen → Merchant = 2,500. **Worth balance-checking in playtest.**
|
||||
5. `/bns admin tier set <you> 13` — jump to Sovereign for testing
|
||||
6. `/bns me` — should show Sovereign + 10 plot slots + unlimited everything
|
||||
7. `/bns plot list` — should show 3 plots available
|
||||
8. Stand in the relevant chunks (e.g. chunk 10,10), `/bns plot buy p001`
|
||||
9. `/bns bounty list` — random 5 from pool
|
||||
10. Accept one, kill enough mobs, turn in
|
||||
11. `/bns sell list` — see effective prices
|
||||
12. `/bns sell minecraft:diamond 4` (if you have diamonds)
|
||||
|
||||
If any step fails, the KubeJS log tells you why. Ping me with the error and I'll patch tomorrow.
|
||||
|
||||
---
|
||||
|
||||
## What I'd build next session
|
||||
|
||||
In priority order:
|
||||
|
||||
1. **bnstoolkit mod scaffolding (M0)** — need to install Java/Gradle on glados first. Empty Gradle project that builds + loads cleanly. Probably 4-6 hours of focused work.
|
||||
2. **TierUpgrade GUI** (M1) — first screen in the mod, replaces the `/bns tier upgrade` text flow with a visual ladder + Upgrade button.
|
||||
3. **BountyBoard GUI** (M3, after M0 is done) — replaces the chat-based `/bns bounty list/accept` with a poster-board screen.
|
||||
|
||||
Or — if you'd rather playtest the command-driven version first and only build the UI once the mechanics feel right — that's a perfectly defensible choice. The CLI version is fully functional.
|
||||
@@ -0,0 +1,50 @@
|
||||
#Easy NPC Security Configuration
|
||||
#
|
||||
#Permission values use enum names: ALL, MODERATORS, GAMEMASTERS, ADMINS, OWNERS.
|
||||
#Legacy numeric Minecraft command permission levels are accepted and rewritten to enum names.
|
||||
#
|
||||
#normalPlayerCommandLevel: Max command level for normal player-owned imports (default: ALL)
|
||||
#creativePlayerCommandLevel: Max command level for creative non-admin imports (default: MODERATORS)
|
||||
#maxAdminImportedCommandLevel: Max command level for admin imports (default: ADMINS)
|
||||
#serverTrustedCommandLevel: Max command level for server-side trusted imports without player context (default: ADMINS)
|
||||
#blockUnsafeNpcCommands: Blocks critical server management commands from NPC command execution (default: true)
|
||||
#npcSpawnRateLimitCreative: Max new NPCs a creative (non-admin) player may spawn via browser per minute (default: 5)
|
||||
#npcSpawnRateLimitAdmin: Max new NPCs an admin player may spawn via browser per minute (default: 20)
|
||||
#Feature values use enum names: NORMAL_PLAYER, CREATIVE_PLAYER, ADMIN, SERVER_TRUSTED.
|
||||
#feature.SPAWN_NPC: Minimum role required to spawn new NPCs from the preset browser (default: CREATIVE_PLAYER)
|
||||
#feature.WORLD_PRESET: Minimum role required to import/export world presets (default: CREATIVE_PLAYER)
|
||||
#feature.CUSTOM_PRESET: Minimum role required to import/export custom server presets (default: CREATIVE_PLAYER)
|
||||
#feature.LOCAL_PRESET: Minimum role required to export presets locally to the client (default: NORMAL_PLAYER)
|
||||
#feature.DEFAULT_PRESET_IMPORT: Minimum role required to import built-in default presets (default: CREATIVE_PLAYER)
|
||||
#feature.URL_RESOURCE: Minimum role required to use URL-based skin loading (default: CREATIVE_PLAYER)
|
||||
#Sat Jun 06 23:34:43 UTC 2026
|
||||
blockUnsafeNpcCommands=true
|
||||
creativePlayerCommandLevel=MODERATORS
|
||||
executeAsUserCommandAllowList.ADMINS=
|
||||
executeAsUserCommandAllowList.ALL=bns
|
||||
executeAsUserCommandAllowList.GAMEMASTERS=
|
||||
executeAsUserCommandAllowList.MODERATORS=
|
||||
executeAsUserCommandAllowList.OWNERS=
|
||||
feature.BASE_ATTRIBUTE=CREATIVE_PLAYER
|
||||
feature.COMBAT_ATTRIBUTE=CREATIVE_PLAYER
|
||||
feature.COMMAND_ACTION=CREATIVE_PLAYER
|
||||
feature.CUSTOM_PRESET=CREATIVE_PLAYER
|
||||
feature.DEFAULT_PRESET_IMPORT=CREATIVE_PLAYER
|
||||
feature.DIALOG=NORMAL_PLAYER
|
||||
feature.INTERACT_BLOCK_ACTION=CREATIVE_PLAYER
|
||||
feature.LOCAL_PRESET=NORMAL_PLAYER
|
||||
feature.MOVEMENT=CREATIVE_PLAYER
|
||||
feature.OBJECTIVE=CREATIVE_PLAYER
|
||||
feature.OPEN_TRADING_ACTION=CREATIVE_PLAYER
|
||||
feature.POSITION=CREATIVE_PLAYER
|
||||
feature.SCOREBOARD_ACTION=CREATIVE_PLAYER
|
||||
feature.SPAWN_NPC=CREATIVE_PLAYER
|
||||
feature.TRADING=CREATIVE_PLAYER
|
||||
feature.URL_RESOURCE=CREATIVE_PLAYER
|
||||
feature.WORLD_PRESET=CREATIVE_PLAYER
|
||||
maxAdminImportedCommandLevel=ADMINS
|
||||
normalPlayerCommandLevel=ALL
|
||||
npcSpawnRateLimitAdmin=20
|
||||
npcSpawnRateLimitCreative=5
|
||||
serverTrustedCommandLevel=ADMINS
|
||||
unsafeNpcCommands=ban,ban-ip,banlist,debug,deop,difficulty,forceload,function,gamerule,kick,op,pardon,reload,save-all,save-off,save-on,schedule,setidletimeout,setworldspawn,spawnpoint,spreadplayers,stop,whitelist
|
||||
@@ -59,12 +59,14 @@ const BOUNTY_POOL = [
|
||||
{ id: 'b_wither_skel_3', name: 'Slay 3 Wither Skeletons', target: 'minecraft:wither_skeleton', count: 3, type: 'kill', reward: 150, source: "Adventurer's Guild" },
|
||||
{ id: 'b_shulker_3', name: 'Slay 3 Shulkers', target: 'minecraft:shulker', count: 3, type: 'kill', reward: 200, source: "Adventurer's Guild" },
|
||||
|
||||
// ── Boss tier (rare picks) ──
|
||||
{ id: 'b_warden_1', name: 'Slay the Warden', target: 'minecraft:warden', count: 1, type: 'kill', reward: 800, source: "Adventurer's Guild" },
|
||||
{ id: 'b_voidworm_1', name: 'Destroy a Void Worm', target: 'alexsmobs:void_worm', count: 1, type: 'kill', reward: 1200, source: "Adventurer's Guild" },
|
||||
{ id: 'b_warped_mosco_1', name: 'Slay a Warped Mosco', target: 'alexsmobs:warped_mosco', count: 1, type: 'kill', reward: 700, source: "Adventurer's Guild" },
|
||||
{ id: 'b_farseer_1', name: 'Slay the Farseer', target: 'alexsmobs:farseer', count: 1, type: 'kill', reward: 900, source: "Adventurer's Guild" },
|
||||
{ id: 'b_skreecher_1', name: 'Slay a Skreecher', target: 'alexsmobs:skreecher', count: 1, type: 'kill', reward: 400, source: "Adventurer's Guild" },
|
||||
// ── Boss tier (rare picks) — all vanilla 1.21.x ──
|
||||
{ id: 'b_warden_1', name: 'Slay the Warden', target: 'minecraft:warden', count: 1, type: 'kill', reward: 800, source: "Adventurer's Guild" },
|
||||
{ id: 'b_breeze_3', name: 'Hunt 3 Breezes', target: 'minecraft:breeze', count: 3, type: 'kill', reward: 250, source: "Adventurer's Guild" },
|
||||
{ id: 'b_bogged_5', name: 'Slay 5 Bogged', target: 'minecraft:bogged', count: 5, type: 'kill', reward: 120, source: "Adventurer's Guild" },
|
||||
{ id: 'b_evoker_2', name: 'Slay 2 Evokers', target: 'minecraft:evoker', count: 2, type: 'kill', reward: 350, source: "Adventurer's Guild" },
|
||||
{ id: 'b_vindicator_4', name: 'Slay 4 Vindicators', target: 'minecraft:vindicator', count: 4, type: 'kill', reward: 180, source: "Adventurer's Guild" },
|
||||
{ id: 'b_piglin_brute_2', name: 'Slay 2 Piglin Brutes', target: 'minecraft:piglin_brute', count: 2, type: 'kill', reward: 400, source: "Adventurer's Guild" },
|
||||
{ id: 'b_ravager_1', name: 'Hunt down a Ravager', target: 'minecraft:ravager', count: 1, type: 'kill', reward: 500, source: "Adventurer's Guild" },
|
||||
];
|
||||
|
||||
// Index pool by id for O(1) lookup
|
||||
@@ -344,4 +346,14 @@ ServerEvents.commandRegistry(event => {
|
||||
event.register(bountyCmd);
|
||||
});
|
||||
|
||||
// ─── First-login pool roll ─────────────────────────────────────────
|
||||
// Without this, new players see an empty Bounty Board until they type
|
||||
// /bns bounty list (which triggers ensurePoolFresh). The board reads
|
||||
// bnsBountyPool from NBT; bnsBountyPool stays empty until rollDailyPool
|
||||
// runs. This hook closes the gap so the screen is populated the first
|
||||
// time it's opened.
|
||||
PlayerEvents.loggedIn(event => {
|
||||
ensurePoolFresh(event.player);
|
||||
});
|
||||
|
||||
console.info('[bns/bounty_engine] loaded — ' + BOUNTY_POOL.length + ' bounties in catalogue');
|
||||
|
||||
@@ -109,19 +109,20 @@ ServerEvents.commandRegistry(event => {
|
||||
}
|
||||
const nextTier = cur + 1;
|
||||
const nextCfg = bnsTier.config(nextTier);
|
||||
const balance = countCoins(player);
|
||||
if (balance < nextCfg.cost) {
|
||||
|
||||
// Combined cash+bank deduction via bnstoolkit Java bridge.
|
||||
// Pulls inventory coins first, then bank for the remainder.
|
||||
const Bridge = Java.loadClass('uk.sijbers.bnstoolkit.economy.NumismaticsBridge');
|
||||
const ok = Bridge.spend(player, nextCfg.cost);
|
||||
if (!ok) {
|
||||
const cash = countCoins(player);
|
||||
const bank = Bridge.balanceOf(player);
|
||||
player.tell(Text.red(
|
||||
'Upgrade to ' + nextCfg.name + ' costs ' + nextCfg.cost + ' spurs. You have ' + balance + '.'
|
||||
'Upgrade to ' + nextCfg.name + ' costs ' + nextCfg.cost + ' spurs. ' +
|
||||
'You have ' + cash + ' cash + ' + bank + ' bank = ' + (cash + bank) + ' total.'
|
||||
));
|
||||
return 0;
|
||||
}
|
||||
const took = takeCoins(player, nextCfg.cost);
|
||||
if (took < nextCfg.cost) {
|
||||
giveCoins(player, took);
|
||||
player.tell(Text.red('Coin removal failed — refunded. Try again.'));
|
||||
return 0;
|
||||
}
|
||||
// Promote via FTB Ranks
|
||||
const rankId = TIER_RANK_ID[nextTier];
|
||||
Utils.server.runCommandSilent('ftbranks add ' + player.username + ' ' + rankId);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+35
-24
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"$schema": "Brass-and-Sigil pack.lock.json - generated, do not edit by hand unless you know what you are doing",
|
||||
"name": "Brass and Sigil",
|
||||
"version": "0.33.0",
|
||||
"version": "0.40.0",
|
||||
"minecraft": "1.21.1",
|
||||
"loader": {
|
||||
"type": "neoforge",
|
||||
"version": "21.1.228"
|
||||
},
|
||||
"lockedAt": "2026-06-06T23:34:21Z",
|
||||
"lockedAt": "2026-06-08T17:18:54Z",
|
||||
"mods": [
|
||||
{
|
||||
"source": "modrinth",
|
||||
@@ -130,17 +130,6 @@
|
||||
"size": 38629,
|
||||
"side": "both"
|
||||
},
|
||||
{
|
||||
"source": "modrinth",
|
||||
"slug": "create-encased",
|
||||
"versionId": "3sUW27Ho",
|
||||
"version": "1.8.1-ht1",
|
||||
"path": "mods/Create Encased-1.21.1-1.8.1-ht1.jar",
|
||||
"url": "https://cdn.modrinth.com/data/hSSqdyU1/versions/3sUW27Ho/Create%20Encased-1.21.1-1.8.1-ht1.jar",
|
||||
"sha1": "5ca3d7674676dda5dd472b0433505a54d5e920d0",
|
||||
"size": 2574837,
|
||||
"side": "both"
|
||||
},
|
||||
{
|
||||
"source": "modrinth",
|
||||
"slug": "bellsandwhistles",
|
||||
@@ -1065,17 +1054,6 @@
|
||||
"size": 193381,
|
||||
"side": "both"
|
||||
},
|
||||
{
|
||||
"source": "modrinth",
|
||||
"slug": "alexs-mobs(1.21.1)",
|
||||
"versionId": "KSgki4uc",
|
||||
"version": "1.22.17",
|
||||
"path": "mods/alexsmobs-1.22.17.jar",
|
||||
"url": "https://cdn.modrinth.com/data/EmNhnNnt/versions/KSgki4uc/alexsmobs-1.22.17.jar",
|
||||
"sha1": "5070e8070f60650d773bfd9f1d031496f8f50259",
|
||||
"size": 26620546,
|
||||
"side": "both"
|
||||
},
|
||||
{
|
||||
"source": "modrinth",
|
||||
"slug": "item-obliterator",
|
||||
@@ -1130,6 +1108,39 @@
|
||||
"sha1": "07fdfa3a75c3481770ce0118090a347d89e3a560",
|
||||
"size": 88382,
|
||||
"side": "both"
|
||||
},
|
||||
{
|
||||
"source": "modrinth",
|
||||
"slug": "structory",
|
||||
"versionId": "Nvh2sSPX",
|
||||
"version": "1.3.16",
|
||||
"path": "mods/Structory_26.1_v1.3.16.jar",
|
||||
"url": "https://cdn.modrinth.com/data/aKCwCJlY/versions/Nvh2sSPX/Structory_26.1_v1.3.16.jar",
|
||||
"sha1": "ee48f145a3a09fdb3f492576cbc21ed26abc74bb",
|
||||
"size": 1261336,
|
||||
"side": "both"
|
||||
},
|
||||
{
|
||||
"source": "modrinth",
|
||||
"slug": "structory-towers",
|
||||
"versionId": "ioKwCTuD",
|
||||
"version": "1.0.16",
|
||||
"path": "mods/Structory_Towers_26.1_v1.0.16.jar",
|
||||
"url": "https://cdn.modrinth.com/data/j3FONRYr/versions/ioKwCTuD/Structory_Towers_26.1_v1.0.16.jar",
|
||||
"sha1": "ed22fe659cb3020f2e86b7619ce721714b9546be",
|
||||
"size": 491545,
|
||||
"side": "both"
|
||||
},
|
||||
{
|
||||
"source": "modrinth",
|
||||
"slug": "create-tracks+",
|
||||
"versionId": "cju2ayQC",
|
||||
"version": "1.0.5",
|
||||
"path": "mods/tracks_plus-1.0.5.jar",
|
||||
"url": "https://cdn.modrinth.com/data/E8eHF2Yl/versions/cju2ayQC/tracks_plus-1.0.5.jar",
|
||||
"sha1": "87f72a49f53d7cf4202a11e4fc68f9bd18ab2c38",
|
||||
"size": 610174,
|
||||
"side": "both"
|
||||
}
|
||||
],
|
||||
"defaultServer": {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Linux-side port of Build-Pack.ps1 + Deploy-Brass.ps1 (publish step).
|
||||
|
||||
Reads pack/pack.lock.json + walks pack/overrides/, rebuilds manifest.json,
|
||||
mirrors every override file to the live web root, and republishes the
|
||||
manifest. The launcher will then see fresh hashes on its next sync.
|
||||
|
||||
Run on glados with passwordless sudo. Idempotent.
|
||||
|
||||
Web root: /srv/services/sites/bns (mounted into Caddy as /srv/sites/bns).
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path("/home/matt/dev/brass-and-sigil")
|
||||
LOCK_PATH = REPO / "pack" / "pack.lock.json"
|
||||
OVERRIDES = REPO / "pack" / "overrides"
|
||||
WEB_ROOT = Path("/srv/services/sites/bns/pack")
|
||||
WEB_FILES = WEB_ROOT / "files"
|
||||
WEB_MANIFEST = WEB_ROOT / "manifest.json"
|
||||
SELF_HOST_BASE = "https://bns.sijbers.uk/pack/files"
|
||||
|
||||
MANAGED_ROOTS = ["mods", "config", "resourcepacks", "shaderpacks", "kubejs", "defaultconfigs", "patchouli_books"]
|
||||
|
||||
# bnstoolkit ships client+server surface (screens, HUD, network bridge),
|
||||
# so it must NOT inherit the default "server-only" tag that Build-Pack.ps1
|
||||
# stamps on every pack/overrides/mods/*.jar. brassandsigil_tweaks is
|
||||
# data-only (worldgen modifiers / recipe overrides) so server-side is
|
||||
# correct for it.
|
||||
CLIENT_AND_SERVER_LOCAL_MODS = {"bnstoolkit"}
|
||||
|
||||
|
||||
def sha1_of(p: Path) -> str:
|
||||
h = hashlib.sha1()
|
||||
with p.open("rb") as f:
|
||||
for chunk in iter(lambda: f.read(1 << 16), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
with LOCK_PATH.open() as f:
|
||||
lock = json.load(f)
|
||||
|
||||
files = []
|
||||
total_bytes = 0
|
||||
|
||||
# 1. Mods from the lockfile (Modrinth-hosted)
|
||||
for m in lock["mods"]:
|
||||
entry = {
|
||||
"path": m["path"],
|
||||
"url": m["url"],
|
||||
"sha1": m["sha1"],
|
||||
"size": m["size"],
|
||||
}
|
||||
side = m.get("side")
|
||||
if side and side != "both":
|
||||
entry["side"] = side
|
||||
files.append(entry)
|
||||
total_bytes += m["size"]
|
||||
|
||||
# 2. Local overrides
|
||||
for root in MANAGED_ROOTS:
|
||||
root_dir = OVERRIDES / root
|
||||
if not root_dir.exists():
|
||||
continue
|
||||
for p in sorted(root_dir.rglob("*")):
|
||||
if not p.is_file():
|
||||
continue
|
||||
rel = p.relative_to(OVERRIDES).as_posix()
|
||||
sha1 = sha1_of(p)
|
||||
size = p.stat().st_size
|
||||
entry = {
|
||||
"path": rel,
|
||||
"url": f"{SELF_HOST_BASE}/{rel}",
|
||||
"sha1": sha1,
|
||||
"size": size,
|
||||
}
|
||||
if root == "mods":
|
||||
# See CLIENT_AND_SERVER_LOCAL_MODS — bnstoolkit must be loaded
|
||||
# on both sides; the other locally-built mods are server-only.
|
||||
stem = p.name.split("-")[0].split("_")[0] # before first - or _
|
||||
if not any(p.name.startswith(prefix) for prefix in CLIENT_AND_SERVER_LOCAL_MODS):
|
||||
entry["side"] = "server"
|
||||
files.append(entry)
|
||||
total_bytes += size
|
||||
|
||||
# 3. Compose manifest matching the existing schema
|
||||
manifest = {
|
||||
"name": lock["name"],
|
||||
"version": lock["version"],
|
||||
"minecraft": {"version": lock["minecraft"]},
|
||||
"loader": {"type": lock["loader"]["type"], "version": lock["loader"]["version"]},
|
||||
"files": files,
|
||||
}
|
||||
for opt in ("defaultServer", "defaultShader", "panelUrl"):
|
||||
if opt in lock:
|
||||
manifest[opt] = lock[opt]
|
||||
|
||||
# Carry the launcher metadata from the existing published manifest
|
||||
# (we can't read a Windows .exe's FileVersion from Linux easily, and
|
||||
# the launcher version isn't related to pack content).
|
||||
try:
|
||||
with WEB_MANIFEST.open() as f:
|
||||
existing = json.load(f)
|
||||
for k in ("launcherVersion", "launcherUrl"):
|
||||
if k in existing:
|
||||
manifest[k] = existing[k]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 4. Mirror override files to web root (uses sudo + cp -a). Only update
|
||||
# files whose hash differs from what's already published to keep the
|
||||
# rsync footprint tiny.
|
||||
print(f"\nMirroring {sum(1 for r in MANAGED_ROOTS if (OVERRIDES / r).exists())} override roots to {WEB_FILES}\n")
|
||||
changed = 0
|
||||
skipped = 0
|
||||
for root in MANAGED_ROOTS:
|
||||
root_dir = OVERRIDES / root
|
||||
if not root_dir.exists():
|
||||
continue
|
||||
target_root = WEB_FILES / root
|
||||
for p in root_dir.rglob("*"):
|
||||
if not p.is_file():
|
||||
continue
|
||||
rel = p.relative_to(OVERRIDES)
|
||||
target = WEB_FILES / rel
|
||||
try:
|
||||
if target.exists() and sha1_of(target) == sha1_of(p):
|
||||
skipped += 1
|
||||
continue
|
||||
except PermissionError:
|
||||
# Need sudo to read
|
||||
pass
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(p, target)
|
||||
changed += 1
|
||||
print(f" changed: {changed}, skipped (matching hash): {skipped}\n")
|
||||
|
||||
# 5. Write the manifest atomically (write to .new, then move)
|
||||
new_manifest = WEB_MANIFEST.with_suffix(".json.new")
|
||||
bak_manifest = WEB_MANIFEST.with_suffix(".json.bak")
|
||||
if WEB_MANIFEST.exists():
|
||||
shutil.copy2(WEB_MANIFEST, bak_manifest)
|
||||
with new_manifest.open("w") as f:
|
||||
json.dump(manifest, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
new_manifest.replace(WEB_MANIFEST)
|
||||
|
||||
print(f"Manifest written: {WEB_MANIFEST}")
|
||||
print(f" Version: {lock['version']}")
|
||||
print(f" Files: {len(files)}")
|
||||
print(f" Total: {total_bytes / 1024 / 1024:.1f} MB download")
|
||||
print()
|
||||
# Quick verify
|
||||
rel = "kubejs/server_scripts/economy/00_tier_system.js"
|
||||
entry = next((f for f in files if f["path"] == rel), None)
|
||||
if entry:
|
||||
print(f" {rel} -> sha1={entry['sha1'][:12]}... size={entry['size']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user