Files
cobblemon-survival-pack/tools/machine_assets.py
T
claudeandClaude Opus 5.5 0877a6a179 Phase 6: Pokémon machines (five cores, shells as data)
Adrian (2026-10-31): a Large Furnace where a Fire Pokémon heats real
furnaces (hoppers in and out), our own Electric generator, no upkeep;
then a drop synthesizer (FE into full drop rolls, looting tiers), a
sacrifice altar (level tiers, rare candies only from Lv 80) and a
rebirth chamber (Lv 100 -> shiny Lv 1 first stage, three perfect IVs,
moves kept, Ability Patch so the hidden ability is optional).

- machine/: data-driven 3x3x3 shells (window spots for glass, free
  spot under the core), checked in four rotations; the Pokémon walks in
  from the party and lives in the core; breaking core or shell sends it
  home (party, else PC, offline too); a real Cobblemon entity copy is
  shown in the chamber (no AI, invulnerable, uncatchable, never saved).
- Heat Core keeps vanilla furnaces lit and faster; Dynamo Core makes
  and pushes FE; Synthesis Core takes FE and rolls drops into a hopper-
  extractable buffer; Altar Core sacrifices on a confirmed sneak-use;
  Rebirth Core runs on server time.
- Recipes, casing tags, block loot, textures (tools/machine_assets.py),
  models, blockstates, names.
- Self-tests: in/out, refusal, shell and core breaks (PC when full),
  chunk unload, server restart, and each machine's work.
- Docs: design, balance table, Phase 6 checklist; Phase 5 approved.

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

162 lines
5.4 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).
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": [
".......#........",
".......#........",
"......###.......",
"..###########...",
"....#######.....",
".....#####......",
"....###.###.....",
"...##.....##....",
],
}
def frame(image, dark, light):
for i in range(16):
image.putpixel((i, 0), light)
image.putpixel((0, i), light)
image.putpixel((i, 15), dark)
image.putpixel((15, i), dark)
def side(dark, mid, light):
image = Image.new("RGBA", (16, 16), mid + (255,))
for y in range(16):
for x in range(16):
# Plates of 8x4 with a seam, offset every other row, like bricks or panels.
if y % 4 == 0 or (x + (4 if (y // 4) % 2 else 0)) % 8 == 0:
image.putpixel((x, y), dark + (255,))
elif (x + y) % 7 == 0:
image.putpixel((x, y), light + (255,))
frame(image, dark + (255,), light + (255,))
return image
def top(dark, mid, light, accent):
image = side(dark, mid, light)
for y in range(5, 11):
for x in range(5, 11):
edge = x in (5, 10) or y in (5, 10)
image.putpixel((x, y), (dark if edge else accent) + (255,))
return image
def front(dark, mid, light, accent, emblem):
image = Image.new("RGBA", (16, 16), dark + (255,))
for y in range(2, 14):
for x in range(2, 14):
image.putpixel((x, y), (mid if (x + y) % 2 else dark) + (255,))
frame(image, dark + (255,), light + (255,))
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 c == "#":
image.putpixel((x, top_row + y), accent + (255,))
elif c == "+":
image.putpixel((x, top_row + y), glow + (255,))
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).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",
"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()