Compare commits

...

8 Commits

Author SHA1 Message Date
Matt f269cc4861 Merge pull request 'pack: v0.39.0 — Counting House + remove Create: Encased' (#78) from feature/v0.39.0-counting-house into main 2026-06-08 15:34:48 +00:00
Matt ae183f27de pack: v0.39.0 — Counting House refactor + remove Create: Encased
Major economy / spawn-design pivot.

## Removed: Create: Encased
The mod's `chorium_ingot` → `creative_casing` chain unlocked 60+
infinite-output creative-tier blocks via fully renewable inputs (rose
quartz + chorus fruit + lava). Single creative_mechanical_drill in a
player base would have produced enough diamonds to trivialize the
civic ladder. Removing the mod was simpler than KubeJS recipe overrides
since none of the mod's other content (wood-variant encased shafts,
coloured casings) was load-bearing for our pack.

Parallel audit of 14 other Create-family mods confirmed none have
similar exploits — only Create: Encased was the breaker.

## Counting House refactor
What used to be "Bazaar" became "Counting House" — a wealth exchange,
not a general shop. Conceptual model:
- Players accumulate diamonds / netherite / ancient debris through
  adventure and mining (the wealth-store axis)
- Counting House converts these to spurs, credited directly to the
  player's Numismatics bank account
- Iron / gold / coal / lapis / redstone are no longer here — those
  belong on player-run Numismatics Vendor blocks at market prices

SellCatalogue cut from 9 items to 4 (diamond, diamond_block,
netherite_ingot, ancient_debris). Diminishing-returns floor tightened
50% -> 30%.

## Numismatics integration
NumismaticsBridge (reflection — no hard dep) wires up:
- ensureAccount on PlayerLoggedInEvent (every player gets a personal
  account on first login)
- balanceOf / deposit / deduct for bank operations
- SpurBalance.totalSpurs = inventory coins + own bank balance, no card
  lookups required
- HUD overlay now shows three lines: tier / cash on hand / bank
  balance — total wealth visible regardless of cards

## /bns commands migrated to Java
`/bns sell` and `/bns wallet` are now Java-handled
(CountingHouseCommands.java). Bank credit happens atomically with
inventory pull. KubeJS `02_sell_shop.js` retired (.retired suffix
preserved in the repo as documentation).

## Hub status-only
Removed the Civic Tier / Bazaar / Bounty / Plot routing buttons.
Transactional screens are now only reachable via NPC dialogue
(`/bns open <screen>` from an Easy NPC COMMAND action). The hub
shows tier + cash + bank + total + slot caps; footer reads "Visit
spawn NPCs for trades, bounties, plots, tier upgrades."

This locks the spawn-as-civic-centre design: players have to travel.

## bnstoolkit jar
0.5.1 -> 0.6.0 (network version 0.4 -> 0.5; TierStateUpdatePacket
now carries cashOnHand + bankBalance separately).

## Pack manifest
102 -> 104 mods after Encased removal. Manifest republished on glados
(publish-pack.py) before restart so the wrapper's sync sees the right
target state. Without that step, the sync was reverting our changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 15:34:25 +00:00
Matt ec8f2b83cf Merge pull request 'pack: v0.38.1 — bnstoolkit 0.5.1 UI coords hotfix' (#77) from hotfix/bnstoolkit-ui-coords into main 2026-06-08 14:43:10 +00:00
Matt 28c816990a pack: v0.38.1 — bnstoolkit 0.5.1 UI coords hotfix
Every screen positioned its child widgets with setPos(getX() + offset,
getY() + offset) under the assumption that FTB Library uses absolute
screen coordinates. It uses PARENT-RELATIVE coordinates — the panel's
own position is added back at render time — so all widgets rendered
at panel-position * 2 + offset, shoving them off the right edge.

Fix: drop the getX()/getY() from every setPos. Now using plain
parent-relative offsets, which is what FTB Library docs + FTB Quests/
Chunks source actually do.

Affects: HubScreen, TierUpgradeScreen, BountyBoardScreen,
PlotPurchaseScreen, ShopBrowserScreen, QuestLogScreen.
Also fixed BaseBnsScreen.contentTop()/contentBottom() to return
relative offsets.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 14:42:41 +00:00
Matt 73f55768c3 scripts: add publish-pack.py — Linux port of Build-Pack.ps1 + deploy
Regenerates /srv/services/sites/bns/pack/manifest.json from
pack.lock.json + pack/overrides/, mirrors changed override files into
the Caddy-served web root, and atomically swaps in the new manifest.

Resolves a class of bug where the published manifest gets stuck at the
last Windows-side build and the launcher errors with "invalid hash for
<file>" on its next sync. Caught today because the manifest had been
serving v0.33.0 since v0.34.0 shipped — five missed publishes.

Run after every pack PR with sudo on glados:
    sudo -n python3 scripts/publish-pack.py

Idempotent. Skips files whose hash already matches the served copy.

Special-cases bnstoolkit-*.jar (side: both — has client UI) so the
launcher doesn't refuse to install it on clients; brassandsigil_tweaks
stays side: server as the existing convention dictates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 14:35:54 +00:00
Matt 48a70326d2 Merge pull request 'pack: v0.38.0 — Create: Tracks+ (tank tread addon)' (#76) from feature/v0.38.0-create-tracks into main 2026-06-07 22:30:37 +00:00
Matt 5f6a5aacdd pack: v0.38.0 — Create: Tracks+ for tank-tread contraptions
Create: Tracks+ 1.0.5 (Modrinth slug create-tracks+) adds Trackwork-
style caterpillar/tank tracks to Create contraptions. Fork of qwxon's
original Create:Tracks, the only 1.21.1 NeoForge port. Required deps
(Create, Create Aeronautics, Sable) are all already in the pack.

Use case: tracked vehicles / tanks built with Create — caterpillar
treads for moving contraptions over uneven terrain, military
aesthetic, or just heavy industrial movers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 22:30:08 +00:00
Matt 8d9c04dc05 Merge pull request 'pack: v0.37.0 — village supply/demand + Structory structure mods' (#75) from feature/v0.37.0-villages-and-structures into main 2026-06-07 16:27:53 +00:00
6 changed files with 182 additions and 13 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+13 -13
View File
@@ -1,13 +1,13 @@
{ {
"$schema": "Brass-and-Sigil pack.lock.json - generated, do not edit by hand unless you know what you are doing", "$schema": "Brass-and-Sigil pack.lock.json - generated, do not edit by hand unless you know what you are doing",
"name": "Brass and Sigil", "name": "Brass and Sigil",
"version": "0.37.0", "version": "0.39.0",
"minecraft": "1.21.1", "minecraft": "1.21.1",
"loader": { "loader": {
"type": "neoforge", "type": "neoforge",
"version": "21.1.228" "version": "21.1.228"
}, },
"lockedAt": "2026-06-07T16:26:12Z", "lockedAt": "2026-06-08T15:30:12Z",
"mods": [ "mods": [
{ {
"source": "modrinth", "source": "modrinth",
@@ -130,17 +130,6 @@
"size": 38629, "size": 38629,
"side": "both" "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", "source": "modrinth",
"slug": "bellsandwhistles", "slug": "bellsandwhistles",
@@ -1152,6 +1141,17 @@
"sha1": "ed22fe659cb3020f2e86b7619ce721714b9546be", "sha1": "ed22fe659cb3020f2e86b7619ce721714b9546be",
"size": 491545, "size": 491545,
"side": "both" "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": { "defaultServer": {
+169
View File
@@ -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()