Files
claudeandClaude Opus 5.5 4083791944 Phase 9: reproducible builds, balance from the server, spark check, play feedback
Build: every tool pinned (JDK, packwiz, Gradle plugins, Python packages), exports
normalised, tools/check_reproducible.py proves two clean builds are byte-identical.

Server-side balance: a server can load config/cobblesurvival/datapack.zip in place of
the jar's data, /reload applies it, tools/server_update.py packages it and refuses
when a client change is needed. JEI drop pages are sent by the server. Code-held
balance numbers moved to tuning/server.json.

Performance: spark in the pack (server only), tools/perf_test.py with fake players
whose chunks really are active, docs/performance.md.

Adrian's feedback: default keys R, U, J, §; drop pages link to Cobblemon Info and show
real chances; bosses only hurt in battles; battle XP halved; channels between the
islands low level; temperature ×3 with insulation doubled; hazards start at once; no
item dots on the map; a wild attack starts a battle; the starter only faints; quest
book on join with a basics page and an HMs page; regional forms obtainable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 16:26:30 +01:00

107 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Write the crafted summons (Phase 9; Adrian, 2026-11-09) from world/summons.json.
Legendaries, mythicals, Ultra Beasts and Paradox Pokémon have no spawn data, so each gets a Sigil (one item,
cobblesurvival:summon_sigil, told apart by its custom data), used in a fitting zone to summon a catchable wild one. This
writes, under datapacks/core/data/cobblesurvival/:
summons/<species>.json species, level, zone, read by the companion mod (summon/Summons.kt)
recipe/summon_<species>.json material in the corners, ring on the sides, core in the middle; the result carries
the species, its name and where to use it, so the client needs no server data
and the sigil's texture and item model in the companion mod. The fights are written by tools/zone_bosses.py, so run that
afterwards, and tools/jei_sources.py and tools/quests.py after it.
Usage: tools/summons.py
"""
import json
import re
import zipfile
from pathlib import Path
from PIL import Image
import lock
ROOT = Path(__file__).resolve().parent.parent
DATA = ROOT / "datapacks/core/data/cobblesurvival"
ASSETS = ROOT / "mod/src/main/resources/assets/cobblesurvival"
SIGIL = "cobblesurvival:summon_sigil"
# The sigil: a dark disc with a gold ring and a violet eye.
TEXTURE = [
"................",
".....######.....",
"...##gggggg##...",
"..#gg......gg#..",
"..#g..vvvv..g#..",
".#g..vv++vv..g#.",
".#g.vv++++vv.g#.",
".#g.vv+WW+vv.g#.",
".#g.vv+WW+vv.g#.",
".#g.vv++++vv.g#.",
".#g..vv++vv..g#.",
"..#g..vvvv..g#..",
"..#gg......gg#..",
"...##gggggg##...",
".....######.....",
"................",
]
COLORS = {"#": (40, 30, 60, 255), "g": (230, 185, 70, 255), ".": (0, 0, 0, 0), "v": (130, 60, 200, 255),
"+": (190, 120, 245, 255), "W": (250, 235, 255, 255)}
def names(jar):
found = {}
with zipfile.ZipFile(jar) as z:
for name in z.namelist():
m = re.fullmatch(r"data/cobblemon/species/[^/]+/([^/]+)\.json", name)
if m:
found[m[1]] = json.loads(z.read(name))["name"]
return found
def main():
table = json.loads((ROOT / "world/summons.json").read_text())
plan = json.loads((ROOT / "world/zones.json").read_text())
zones = {z["id"]: z["name"] for z in plan["zones"] + plan["caves"] + plan.get("dimensions", [])}
species_names = names(lock.mod_jar("cobblemon"))
summons, recipes = DATA / "summons", DATA / "recipe"
summons.mkdir(parents=True, exist_ok=True)
for old in list(summons.glob("*.json")) + list(recipes.glob("summon_*.json")):
old.unlink()
comment = "Generated by tools/summons.py from world/summons.json; do not edit by hand."
for species, s in table["summons"].items():
tier = table["tiers"][str(s["tier"])]
form = s.get("form")
name = f"{form.title()} {species_names[s['species']]}" if form else species_names[species]
(summons / f"{species}.json").write_text(json.dumps(
{"_comment": comment, "species": species, "pokemon": f"{s['species']} {form}" if form else species, "level": tier["level"], "zone": s["zone"]}, indent=1) + "\n")
lore = f"Use in {zones[s['zone']]} to summon a Lv {tier['level']} {name}"
(recipes / f"summon_{species}.json").write_text(json.dumps({
"_comment": comment, "type": "minecraft:crafting_shaped", "category": "misc",
"pattern": ["MRM", "RCR", "MRM"],
"key": {"M": {"item": s["material"]}, "R": {"item": tier["ring"]}, "C": {"item": tier["core"]}},
"result": {"id": SIGIL, "count": 1, "components": {
"minecraft:custom_data": {"cobblesurvival_summon": species},
"minecraft:item_name": json.dumps(f"{name} Sigil"),
"minecraft:lore": [json.dumps({"text": lore, "color": "gray", "italic": False})],
}},
}, indent=1) + "\n")
image = Image.new("RGBA", (16, 16))
for y, row in enumerate(TEXTURE):
for x, c in enumerate(row):
image.putpixel((x, y), COLORS[c])
(ASSETS / "textures/item").mkdir(parents=True, exist_ok=True)
image.save(ASSETS / "textures/item/summon_sigil.png")
(ASSETS / "models/item/summon_sigil.json").write_text(json.dumps(
{"parent": "minecraft:item/generated", "textures": {"layer0": "cobblesurvival:item/summon_sigil"}}, indent=2) + "\n")
print(f"wrote {len(table['summons'])} summons")
if __name__ == "__main__":
main()