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>
107 lines
4.4 KiB
Python
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()
|