Phase 7: boss fights with area attacks, guardians reviewed, End boss
Adrian (2026-11-02): every zone guardian gets area attacks (harder in harder zones); a boss for the Basalt Furnace; a Pokémon replaces the Ender Dragon; dying or leaving the arena loses; the Deep Dark gets a legendary and every guardian should fit its zone. Groudon, Giratina and Meloetta have no model in Cobblemon 1.8.4: Moltres, Eternatus and Regigigas stand in. - Guardians: Mudsdale, Flygon, Abomasnow, Regigigas (normal size), new Moltres for the Basalt Furnace; zone_spawns.py takes a guardian "scale". - tools/zone_bosses.py writes bosses/<zone>.json and end.json from a tier table (circle, line, cone, rings; telegraph, damage, triggers, fire/ice/poison effects, arena). - boss/: BossFights runs patterns during a guardian or End boss battle (particle telegraphs, hits players inside, stops on battle end), enforces the arena and death as a loss (battle ends, boss heals); EndBoss keeps the dragon out (allowlist emptied), spawns Eternatus on the main island with the dragon's boss bar, and on defeat opens the exit portal, gateway and egg via the vanilla dragon fight, plus XP. - Self-tests: coverage (incl. Cobblemon models), shapes, landing and stopping, arena loss, End boss. Docs, balance, Phase 7 checklist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dc4568e023
commit
cd232bdf6a
32 files changed
+1420
-24
No files matched your search
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Write the boss fights (Phase 7; Adrian, 2026-11-02: every zone guardian gets area attacks, more and harder in
|
||||
harder zones; Eternatus replaces the Ender Dragon).
|
||||
|
||||
For every guardian in datapacks/core/data/cobblesurvival/guardians/ (tools/zone_spawns.py) this writes
|
||||
datapacks/core/data/cobblesurvival/bosses/<zone>.json, and bosses/end.json for the End. The tier comes from the
|
||||
guardian's level (TIERS): which patterns it uses, how long they telegraph, how often they come; damage grows with
|
||||
level; a Fire, Ice or Poison guardian burns, freezes or poisons. Placeholder numbers, see docs/balance.md.
|
||||
|
||||
Needs the Cobblemon jar in build/server/mods (any test-server run) for the guardians' types.
|
||||
|
||||
Usage: tools/zone_bosses.py
|
||||
"""
|
||||
import json
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
DATA = ROOT / "datapacks/core/data/cobblesurvival"
|
||||
OUT = DATA / "bosses"
|
||||
MODS = ROOT / "build/server/mods"
|
||||
|
||||
ARENA = 24
|
||||
ARENA_BY_ZONE = {"basalt": 32}
|
||||
END = {"species": "eternatus", "level": 80, "scale": 0.2, "respawn_hours": 5, "xp_first_win": 12000, "xp": 500}
|
||||
END_ARENA = 40
|
||||
|
||||
# Up to this level: these patterns.
|
||||
TIERS = [
|
||||
(20, ["circle"]),
|
||||
(35, ["circle", "line"]),
|
||||
(50, ["circle", "line", "cone"]),
|
||||
(65, ["circle", "line", "cone", "ring"]),
|
||||
(999, ["circle", "line", "cone", "ring", "wide_ring"]),
|
||||
]
|
||||
|
||||
|
||||
def pattern(name, tier, damage, effect):
|
||||
telegraph = 50 - 5 * tier
|
||||
base = {"telegraph_ticks": telegraph, "damage": damage, "effect": effect}
|
||||
if name == "circle":
|
||||
return {"shape": "circle", "radius": 2.5 + 0.25 * tier, "aim": "player", **base,
|
||||
"trigger": {"every_ticks": 160 - 12 * tier, "offset": 40}}
|
||||
if name == "line":
|
||||
return {"shape": "line", "length": 14, "width": 2, "aim": "player", **base,
|
||||
"trigger": {"every_ticks": 220 - 15 * tier, "offset": 100}}
|
||||
if name == "cone":
|
||||
return {"shape": "cone", "length": 10, "angle": 70, "aim": "player", **base,
|
||||
"trigger": {"every_ticks": 260 - 15 * tier, "offset": 160}}
|
||||
if name == "ring":
|
||||
return {"shape": "ring", "inner": 3, "outer": 9, "aim": "boss", **base, "trigger": {"below_health": 0.5}}
|
||||
if name == "wide_ring":
|
||||
return {"shape": "ring", "inner": 2, "outer": 12, "aim": "boss", **base, "trigger": {"below_health": 0.25}}
|
||||
raise ValueError(name)
|
||||
|
||||
|
||||
def boss(level, types, arena, **extra):
|
||||
tier = next(i for i, (top, _) in enumerate(TIERS) if level <= top)
|
||||
effect = "fire" if "fire" in types else "ice" if "ice" in types else "poison" if "poison" in types else "none"
|
||||
damage = max(2, round(level / 6))
|
||||
return {**extra, "arena": arena, "patterns": [pattern(name, tier, damage, effect) for name in TIERS[tier][1]]}
|
||||
|
||||
|
||||
def species_types(jar):
|
||||
types = {}
|
||||
with zipfile.ZipFile(jar) as z:
|
||||
for name in z.namelist():
|
||||
if name.startswith("data/cobblemon/species/") and name.endswith(".json"):
|
||||
d = json.loads(z.read(name))
|
||||
types[d["name"].lower()] = {t for t in (d.get("primaryType"), d.get("secondaryType")) if t}
|
||||
return types
|
||||
|
||||
|
||||
def main():
|
||||
jar = next(MODS.glob("Cobblemon-*.jar"))
|
||||
types = species_types(jar)
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
for old in OUT.glob("*.json"):
|
||||
old.unlink()
|
||||
comment = "Generated by tools/zone_bosses.py; do not edit by hand. See docs/design.md (Phase 7) for the format."
|
||||
for path in sorted((DATA / "guardians").glob("*.json")):
|
||||
guardian = json.loads(path.read_text())
|
||||
species, *props = guardian["pokemon"].split()
|
||||
level = int(next(p.split("=")[1] for p in props if p.startswith("level=")))
|
||||
zone = guardian["zone"]
|
||||
data = boss(level, types.get(species, set()), ARENA_BY_ZONE.get(zone, ARENA), guardian=zone)
|
||||
(OUT / f"{zone}.json").write_text(json.dumps({"_comment": comment, **data}, indent=1) + "\n")
|
||||
print(f"{zone}: {species} Lv {level}, {len(data['patterns'])} patterns")
|
||||
end = boss(END["level"], types.get(END["species"], set()), END_ARENA, end=END)
|
||||
(OUT / "end.json").write_text(json.dumps({"_comment": comment, **end}, indent=1) + "\n")
|
||||
print(f"end: {END['species']} Lv {END['level']}, {len(end['patterns'])} patterns")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user