Files
cobblemon-survival-pack/tools/machine_assets.py
T
claudeandClaude Opus 5.5 dc4568e023 Phase 6 done: see-through cores, faint on sacrifice and rebirth; Phase 7 design
Adrian (2026-11-02): the cores should be transparent so the Pokémon
shows; the Pokémon's death sound when a sacrifice is made or a rebirth
ends; then start the next phase (Phase 6 approved).

- Cores drawn as a solid frame around tinted glass, rendered
  translucent, no occlusion (tools/machine_assets.py).
- The shown Pokémon plays Cobblemon's faint animation and its cry
  pitched down (Cobblemon has no death sounds), vanishes after 2 s; a
  reborn Pokémon then shows in its new form. Tests check the new form.
- Phase 7 decisions (every guardian gets area attacks, Groudon for the
  Basalt Furnace, Giratina replaces the Ender Dragon, dying or leaving
  the arena loses) and design in docs/design.md; README status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:58:01 +01:00

168 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""Writes the machine cores' textures, block models, item models and blockstates (Phase 6).
Each core is an "orientable" block (front, side, top) facing the player who placed it. The 16x16 textures are drawn
here, from a palette and an emblem per machine, so they can be redrawn after a change. Our own art (pack license).
The cores are see-through (Adrian, 2026-11-02: the Pokémon in the chamber behind the core must be visible): a solid
frame around tinted glass, the emblem faint on the front; the models render translucent.
Usage: tools/machine_assets.py
"""
import json
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parent.parent
ASSETS = ROOT / "mod/src/main/resources/assets/cobblesurvival"
# name: (dark, mid, light, accent, emblem)
CORES = {
"heat_core": ((92, 34, 26), (150, 62, 44), (186, 92, 66), (255, 160, 40), "flame"),
"dynamo_core": ((112, 58, 36), (184, 104, 70), (224, 150, 108), (255, 230, 70), "bolt"),
"synthesis_core": ((70, 74, 80), (128, 134, 142), (180, 186, 194), (90, 220, 120), "drop"),
"altar_core": ((22, 18, 26), (52, 44, 58), (84, 72, 92), (190, 60, 220), "eye"),
"rebirth_core": ((70, 40, 110), (130, 90, 190), (186, 150, 236), (255, 200, 245), "star"),
}
EMBLEMS = {
"flame": [
"......#.........",
".....##.........",
".....###..#.....",
"....####.##.....",
"...#########....",
"...##########...",
"..###++######...",
"..##++++#####...",
"..##+++++####...",
"...##+++++##....",
"....########....",
],
"bolt": [
"........####....",
".......####.....",
"......####......",
".....####.......",
"....#######.....",
".......####.....",
"......####......",
".....###........",
"....##..........",
"...#............",
],
"drop": [
".......#........",
"......###.......",
".....#####......",
"....###+###.....",
"...###+++###....",
"...##+++++##....",
"...###+++###....",
"....#######.....",
".....#####......",
],
"eye": [
"................",
".....######.....",
"...##......##...",
"..#...####...#..",
".#...##++##...#.",
"..#...####...#..",
"...##......##...",
".....######.....",
],
"star": [
".......#........",
".......#........",
"......###.......",
"..###########...",
"....#######.....",
".....#####......",
"....###.###.....",
"...##.....##....",
],
}
GLASS_ALPHA = 70
EMBLEM_ALPHA = 150
def frame(image, dark, mid, light):
"""A two-pixel frame: light top-left edge, dark bottom-right edge, mid inner ring."""
for i in range(16):
for a, b in ((i, 0), (0, i)):
image.putpixel((a, b), light + (255,))
for a, b in ((i, 15), (15, i)):
image.putpixel((a, b), dark + (255,))
for i in range(1, 15):
for a, b in ((i, 1), (1, i), (i, 14), (14, i)):
image.putpixel((a, b), mid + (255,))
def glass(dark, mid, light, accent):
"""Tinted glass inside the frame, with a few lighter streaks so the face still reads as a surface."""
image = Image.new("RGBA", (16, 16), (0, 0, 0, 0))
tint = tuple((m + a) // 2 for m, a in zip(mid, accent))
for y in range(2, 14):
for x in range(2, 14):
streak = (x - y) in (3, 4) or (x - y) == -6
image.putpixel((x, y), (light if streak else tint) + (GLASS_ALPHA + (50 if streak else 0),))
frame(image, dark, mid, light)
return image
def side(dark, mid, light, accent):
return glass(dark, mid, light, accent)
def top(dark, mid, light, accent):
image = glass(dark, mid, light, accent)
for x, y in ((2, 2), (13, 2), (2, 13), (13, 13)):
image.putpixel((x, y), accent + (255,))
return image
def front(dark, mid, light, accent, emblem):
image = glass(dark, mid, light, accent)
rows = EMBLEMS[emblem]
top_row = (16 - len(rows)) // 2
glow = tuple(min(255, c + 60) for c in accent)
for y, row in enumerate(rows):
for x, c in enumerate(row):
if 2 <= x <= 13 and 2 <= top_row + y <= 13 and c in "#+":
image.putpixel((x, top_row + y), (accent if c == "#" else glow) + (EMBLEM_ALPHA,))
return image
def write_json(path, data):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2) + "\n")
def main():
textures = ASSETS / "textures/block"
textures.mkdir(parents=True, exist_ok=True)
for name, (dark, mid, light, accent, emblem) in CORES.items():
side(dark, mid, light, accent).save(textures / f"{name}_side.png")
top(dark, mid, light, accent).save(textures / f"{name}_top.png")
front(dark, mid, light, accent, emblem).save(textures / f"{name}_front.png")
write_json(ASSETS / f"models/block/{name}.json", {
"parent": "minecraft:block/orientable",
"render_type": "minecraft:translucent",
"textures": {"top": f"cobblesurvival:block/{name}_top", "front": f"cobblesurvival:block/{name}_front",
"side": f"cobblesurvival:block/{name}_side"},
})
write_json(ASSETS / f"models/item/{name}.json", {"parent": f"cobblesurvival:block/{name}"})
rotation = {"north": 0, "east": 90, "south": 180, "west": 270}
write_json(ASSETS / f"blockstates/{name}.json", {
"variants": {f"facing={facing}": {"model": f"cobblesurvival:block/{name}", **({"y": y} if y else {})}
for facing, y in rotation.items()},
})
print(f"wrote {len(CORES)} cores to {ASSETS.relative_to(ROOT)}")
if __name__ == "__main__":
main()