Phase 9: crafted summons for the legendaries

Adrian, 2026-11-09: legendaries, mythicals, Ultra Beasts and Paradox Pokemon
come from crafted summons. 22 Sigils (Naganadel evolves from Poipole), one
item told apart by custom data, from world/summons.json through
tools/summons.py: late-game recipes in three tiers (Lv 70, 80, 90), each used
in a fitting zone. A summoned legend fights as a boss (area attacks by level
and type, arena 32, the Guardian's Ward) but can be caught; on capture it
loses every boss mark. One at a time within 96 blocks.

Quest book: a Legends chapter, one quest per Sigil (97 quests). JEI lists each
Sigil as its own item, and Island Drops says where a legend is summoned; drops
Cobblemon names but doesn't have (Ho-Oh's Sacred Ash) get no slot.

Self-tests every_implemented_pokemon_is_obtainable (spawn, fossil or Sigil for
every implemented species) and sigil_summons_a_catchable_legend. Self-tests
and the island check pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-11-09 13:03:50 +01:00
1 parent e206664b3d
commit f7ea371862
92 files changed
+4081 -21

No files matched your search

+18 -4
View File
@@ -230,6 +230,15 @@ def chapter_quests(chapter, book, zones):
for t in q.get("tasks", []):
tasks += [{**t, "item": o, "each": None} for o in fields["ores"]] if t.get("each") == "ores" else [t]
q["tasks"] = [{k: v for k, v in t.items() if k != "each"} for t in tasks]
# One quest per Sigil: craft it, use it, catch what comes (world/summons.json; names from the JEI data).
if chapter.get("summons"):
sigils = json.loads((ROOT / "mod/src/main/resources/assets/cobblesurvival/jei/drop_sources.json").read_text())["summons"]
for s in sigils:
quests.append({
"id": s["species"].split(":")[1], "title": s["name"], "icon": "cobblesurvival:summon_sigil", "after": ["about"],
"text": f"Craft the {s['name']} Sigil and use it in {zones[s['zone']]['name']}. Catch the Lv {s['level']} {s['name']} that answers.",
"tasks": [{"cobblemon": "catch", "pokemon": [s["species"]]}],
})
# "Catch Pokémon of a zone": its spawn pool's species (Cobblemon Quests' own biome filter knows no zones).
for q in quests:
for t in q.get("tasks", []):
@@ -264,8 +273,9 @@ def task_nbt(task, name, events):
raise ValueError(f"{name}: unknown task {task}")
def auto_layout(quests, chapter_id):
"""Left to right by dependency depth inside the chapter, stacked within a column."""
def auto_layout(quests, chapter_id, grid=None):
"""Left to right by dependency depth inside the chapter, stacked within a column; with `grid` columns, what depends
on something goes into a grid to the right instead of one tall column."""
local = {q["id"] for q in quests}
depth = {}
@@ -279,6 +289,10 @@ def auto_layout(quests, chapter_id):
for q in quests:
columns.setdefault(d(q), []).append(q["id"])
pos = {}
if grid:
for i, qid in enumerate(q["id"] for q in quests if depth[q["id"]] > 0):
pos[f"{chapter_id}/{qid}"] = [2.0 + (i % grid) * 1.5, (i // grid) * 1.5]
columns = {c: ids for c, ids in columns.items() if c == 0}
for col, ids in columns.items():
for row, qid in enumerate(ids):
pos[f"{chapter_id}/{qid}"] = [col * 2.0, (row - (len(ids) - 1) / 2) * 1.5]
@@ -296,7 +310,7 @@ def build(book):
for index, chapter in enumerate(book["chapters"]):
cid = chapter["id"]
quests = expanded[cid]
auto = auto_layout(quests, cid)
auto = auto_layout(quests, cid, chapter.get("columns"))
chapter_id = object_id(f"chapter:{cid}")
lang[f"chapter.{chapter_id}.title"] = chapter["title"]
if chapter.get("subtitle"):
@@ -369,7 +383,7 @@ def pull(book, folder):
auto, by_id = {}, {}
for c in book["chapters"]:
quests = chapter_quests(c, book, zones)
auto |= auto_layout(quests, c["id"])
auto |= auto_layout(quests, c["id"], c.get("columns"))
by_id |= {object_id(f"quest:{c['id']}/{q['id']}"): f"{c['id']}/{q['id']}" for q in quests}
layout = dict(book.get("layout") or {})
for path in sorted((folder / "chapters").glob("*.snbt")):