Cobblemon Survival Pack: phases 0-2 and Phase 3 zones and hazards
Free NeoForge 1.21.1 modpack on Cobblemon 1.8.4 built with packwiz, plus the Kotlin companion mod (cobblesurvival) and its built-in data pack. - Phase 0: packwiz pack with Modrinth/CurseForge cross-links, build and export tooling, Prism instance sync, test server with smoke checks. - Phase 1: Poke Ball shaped island (WorldGen Editor preset, seed isle-26), Adrian's biome list, no natural ore, structures without ore or vanilla mobs, no Nether portals, world template builder and world report. - Phase 2: permadeath (CobbleHardcoreMon plus the companion mod's gap fixes), Revival Charm, expensive Healing Machine, Lootr refreshing loot, in-game self-tests on Minecraft's GameTest framework. - Phase 3 so far: 14 zones with their own spawn pools and level bands (tools/zone_layout.py, tools/zone_spawns.py, "zone" spawn condition), Cold Sweat heat and cold as gates with boss-drop insulation recipes, spawn survey and hazard self-tests in tools/quick_check.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
398301f0be
232 files changed
+72665
No files matched your search
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""The pack's own recipes, as KubeJS really applies them: read from the running test server.
|
||||
|
||||
pack/kubejs/server_scripts are code (event.remove with filters, event.shaped, replaceInput, ...),
|
||||
so the guide does not parse them. Instead this boots the test server (build/server) once on a
|
||||
throwaway void world and runs `/kubejs export debug` (KubeJS 2101), which reloads the data and
|
||||
writes local/kubejs/export/:
|
||||
|
||||
recipes/<ns>/<path>.json every recipe after the scripts ran; the ones a script added or
|
||||
changed carry "_kubejs_changed_marker": {source, line}
|
||||
removed_recipes/<ns>/<path>.json every recipe a script removed (from mod jars and data packs alike)
|
||||
registries/item.json the item registry, including items KubeJS startup scripts register
|
||||
|
||||
The changed recipes, the removed ids and the item ids are copied to build/fieldguide-cache/kubejs-export/
|
||||
with a hash of the inputs (pack/kubejs scripts and data, pack/mods, datapacks/core).
|
||||
tools/fieldguide/build.py runs this by itself when that hash no longer matches the pack.
|
||||
|
||||
Usage: tools/fieldguide/kubejs_export.py [--accept-eula] (run tools/build.py first)
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
import mcserver # noqa: E402
|
||||
|
||||
ROOT = mcserver.ROOT
|
||||
SERVER = mcserver.SERVER
|
||||
EXPORT = SERVER / "local/kubejs/export"
|
||||
CACHE = ROOT / "build/fieldguide-cache/kubejs-export"
|
||||
LEVEL = "fieldguide-export"
|
||||
MARKER = "_kubejs_changed_marker"
|
||||
# Inputs that decide the recipe set KubeJS ends up with. Assets (textures, lang) do not.
|
||||
INPUTS = [ROOT / "pack/kubejs/server_scripts", ROOT / "pack/kubejs/startup_scripts", ROOT / "pack/kubejs/data",
|
||||
ROOT / "pack/mods", ROOT / "datapacks/core"]
|
||||
|
||||
|
||||
def input_hash():
|
||||
h = hashlib.sha256()
|
||||
for base in INPUTS:
|
||||
for p in sorted(base.rglob("*")) if base.exists() else []:
|
||||
if p.is_file():
|
||||
h.update(p.relative_to(ROOT).as_posix().encode() + b"\0" + p.read_bytes() + b"\0")
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def load():
|
||||
"""The cached export, or None: {"recipes": {id: json}, "removed": [id], "items": [id], "stale": bool, ...}."""
|
||||
path = CACHE / "export.json"
|
||||
if not path.exists():
|
||||
return None
|
||||
data = json.loads(path.read_text())
|
||||
data["stale"] = data.pop("inputs") != input_hash()
|
||||
return data
|
||||
|
||||
|
||||
def server_busy():
|
||||
"""True if some process is already running in build/server (another tool's test server)."""
|
||||
for proc in Path("/proc").glob("[0-9]*"):
|
||||
try:
|
||||
if Path(os.readlink(proc / "cwd")) == SERVER.resolve():
|
||||
return True
|
||||
except OSError:
|
||||
continue
|
||||
return False
|
||||
|
||||
|
||||
def check_scripts():
|
||||
"""The server runs build/stage/full's copy of pack/kubejs; refuse to export an older one."""
|
||||
differ = []
|
||||
for sub in ("server_scripts", "startup_scripts", "data"):
|
||||
base = ROOT / "pack/kubejs" / sub
|
||||
for p in sorted(base.rglob("*")) if base.exists() else []:
|
||||
if p.is_file():
|
||||
rel = p.relative_to(ROOT / "pack")
|
||||
served = SERVER / rel
|
||||
if not served.is_file() or served.read_bytes() != p.read_bytes():
|
||||
differ.append(rel.as_posix())
|
||||
if differ:
|
||||
raise SystemExit("build/server has an older copy of " + ", ".join(differ) + "; run tools/build.py first")
|
||||
|
||||
|
||||
def export(log=print):
|
||||
"""Boot the test server, run the KubeJS export, keep the recipe part in the cache. Returns load()."""
|
||||
if not mcserver.eula_accepted():
|
||||
raise SystemExit("build/server/eula.txt does not accept the Minecraft EULA yet "
|
||||
"(tools/selftest.py --accept-eula writes it if you accept it)")
|
||||
if server_busy():
|
||||
raise SystemExit("a test server is already running in build/server; try again when it has stopped")
|
||||
inputs = input_hash()
|
||||
args_file = mcserver.prepare()
|
||||
check_scripts()
|
||||
shutil.rmtree(SERVER / LEVEL, ignore_errors=True)
|
||||
shutil.rmtree(EXPORT, ignore_errors=True)
|
||||
flat = '{"layers":[{"block":"minecraft:bedrock","height":1}],"biome":"minecraft:the_void"}'
|
||||
props = {"level-name": LEVEL, "level-type": "minecraft:flat", "generator-settings": flat,
|
||||
"generate-structures": "false", "spawn-monsters": "false", "spawn-animals": "false"}
|
||||
started = time.time()
|
||||
with mcserver.Server(args_file, props, stdout_name="fieldguide-export-stdout.log") as server:
|
||||
log(f"test server started in {server.wait_ready():.0f}s")
|
||||
reply = server.command("kubejs export debug").strip()
|
||||
if "exporting" not in reply.lower():
|
||||
raise SystemExit(f"/kubejs export debug answered {reply!r}")
|
||||
_wait_for_export(server)
|
||||
server.stop()
|
||||
shutil.rmtree(SERVER / LEVEL, ignore_errors=True)
|
||||
|
||||
recipes = {}
|
||||
for p in sorted((EXPORT / "recipes").rglob("*.json")):
|
||||
d = json.loads(p.read_text())
|
||||
if MARKER in d:
|
||||
recipes[_id(p, "recipes")] = d
|
||||
removed = sorted(_id(p, "removed_recipes") for p in (EXPORT / "removed_recipes").rglob("*.json"))
|
||||
items = sorted(json.loads((EXPORT / "registries/item.json").read_text()))
|
||||
kubejs = next((p.name for p in sorted((SERVER / "mods").glob("kubejs-*.jar"))), None)
|
||||
CACHE.mkdir(parents=True, exist_ok=True)
|
||||
(CACHE / "export.json").write_text(json.dumps({
|
||||
"inputs": inputs, "kubejs": kubejs, "exported": time.strftime("%Y-%m-%d %H:%M"),
|
||||
"removed": removed, "recipes": recipes, "items": items}, indent=1) + "\n")
|
||||
log(f"KubeJS recipes: {len(recipes)} added or changed, {len(removed)} removed ({time.time() - started:.0f}s)")
|
||||
return load()
|
||||
|
||||
|
||||
def _id(path, sub):
|
||||
"""recipes/cobblemon/foo/bar.json -> cobblemon:foo/bar"""
|
||||
return path.relative_to(EXPORT / sub).as_posix()[:-5].replace("/", ":", 1)
|
||||
|
||||
|
||||
def _wait_for_export(server, timeout=300):
|
||||
"""The export reloads first and writes the files afterwards; wait for index.json and every file it lists
|
||||
(the index names them by id, cobblemon:foo.json, and the file is cobblemon/foo.json)."""
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
listed = json.loads((EXPORT / "index.json").read_text())
|
||||
if all((EXPORT / f.replace(":", "/")).exists() for f in listed):
|
||||
return
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
if server.proc.poll() is not None:
|
||||
raise SystemExit(f"the test server stopped during the export; see {server.log}")
|
||||
time.sleep(1)
|
||||
raise SystemExit(f"/kubejs export debug wrote nothing in {timeout}s; see {server.log}")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
||||
ap.add_argument("--accept-eula", action="store_true", help="write eula=true for the test server")
|
||||
args = ap.parse_args()
|
||||
if args.accept_eula:
|
||||
(SERVER / "eula.txt").parent.mkdir(parents=True, exist_ok=True)
|
||||
(SERVER / "eula.txt").write_text("eula=true\n")
|
||||
data = export()
|
||||
for rid in data["removed"]:
|
||||
print(f" - {rid}")
|
||||
for rid, d in sorted(data["recipes"].items()):
|
||||
print(f" + {rid} ({d[MARKER].get('source')}:{d[MARKER].get('line')})")
|
||||
print(f"Wrote {CACHE / 'export.json'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user