Big, tough bosses; working machines show it; JEI drop pages on the roadmap
Bosses (Adrian, 2026-11-02: Eternatus too small and too easy; guardians way bigger and tougher; only defensive stats up; no attack takes more than 25 %): - Guardians scale to about 8 blocks tall (1.5x-12x), Eternatus to 15 blocks (2.5x, was 0.2x). Cave guardians look for a spot with room above them. Bosses already in a world grow on the next check outside battle. - Perfect IVs and 170 EVs each in HP, Defense and Sp. Def, nothing offensive. - The Guardian's Ward, a Showdown-only held item (held_items/guardiansward.js) handed to every boss by a HeldItemProvider: Defense and Sp. Def x2 while defending, and one attack (all hits together) takes at most 25 % of max HP. - Area attacks that start at the boss reach half its width further. Machines (Adrian, 2026-11-02): a working core is active: it glows, its front animates (flickering fire on the furnace core, pulsing emblems elsewhere), and particles show in the chamber and at the core (client side). The Pokemon inside flinches about every 15 s (red flash, Cobblemon's hit sound, no damage). Roadmap Phase 8: every item that can drop gets a JEI page naming its sources. Self-tests: practice_bosses_spawn checks size and training, boss_takes_a_quarter_per_attack runs a real battle (each Lv 100 Bullet Seed takes exactly a quarter), heat_core_cooks_with_hoppers checks the active state and the flinch. Self-tests and the island check pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4f0bc03919
commit
120c664cb0
58 files changed
+681
-86
No files matched your search
+58
-8
@@ -5,10 +5,15 @@ Each core is an "orientable" block (front, side, top) facing the player who plac
|
||||
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.
|
||||
A working core (block state active=true; Adrian, 2026-11-02: show that something is happening, like fire in a furnace)
|
||||
switches to an animated front: the emblem glows and pulses, sparks drift up the glass, and the furnace's flame
|
||||
flickers. The core also gives off light then, and particles show (mod: MachineEffects).
|
||||
|
||||
Usage: tools/machine_assets.py
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
@@ -87,6 +92,10 @@ EMBLEMS = {
|
||||
|
||||
GLASS_ALPHA = 70
|
||||
EMBLEM_ALPHA = 150
|
||||
# The working front: this many frames, each shown this many ticks, blended into one another.
|
||||
FRAMES = 8
|
||||
FRAME_TICKS = 3
|
||||
FLAME_COLORS = [(255, 70, 20), (255, 140, 30), (255, 200, 60), (255, 240, 150)]
|
||||
|
||||
|
||||
def frame(image, dark, mid, light):
|
||||
@@ -136,6 +145,43 @@ def front(dark, mid, light, accent, emblem):
|
||||
return image
|
||||
|
||||
|
||||
def lerp(a, b, t):
|
||||
return tuple(round(x + (y - x) * t) for x, y in zip(a, b))
|
||||
|
||||
|
||||
def working_front(dark, mid, light, accent, emblem, name):
|
||||
"""The animated front of a working core: FRAMES frames stacked top to bottom."""
|
||||
rng = random.Random(name)
|
||||
rows = EMBLEMS[emblem]
|
||||
top_row = (16 - len(rows)) // 2
|
||||
glow = tuple(min(255, c + 60) for c in accent)
|
||||
white = (255, 255, 240)
|
||||
strip = Image.new("RGBA", (16, 16 * FRAMES), (0, 0, 0, 0))
|
||||
sparks = [(rng.randint(3, 12), rng.randint(0, 15)) for _ in range(3)]
|
||||
for f in range(FRAMES):
|
||||
pulse = 0.5 + 0.5 * math.sin(2 * math.pi * f / FRAMES)
|
||||
image = glass(dark, mid, light, accent)
|
||||
for y, row in enumerate(rows):
|
||||
for x, c in enumerate(row):
|
||||
if not (2 <= x <= 13 and 2 <= top_row + y <= 13 and c in "#+"):
|
||||
continue
|
||||
if emblem == "flame":
|
||||
# Flicker: each pixel picks a flame colour, hotter towards the core; the tips come and go.
|
||||
if y < 3 and rng.random() < 0.35:
|
||||
continue
|
||||
heat = (1 if c == "+" else 0) + rng.choice((0, 0, 1))
|
||||
color = FLAME_COLORS[min(len(FLAME_COLORS) - 1, heat + (1 if pulse > 0.6 else 0))]
|
||||
else:
|
||||
color = lerp(accent if c == "#" else glow, white, pulse * (0.6 if c == "+" else 0.35))
|
||||
image.putpixel((x, top_row + y), color + (round(EMBLEM_ALPHA + (255 - EMBLEM_ALPHA) * (0.5 + 0.5 * pulse)),))
|
||||
# Sparks drifting up the glass, two pixels a frame.
|
||||
for sx, sy in sparks:
|
||||
y = 13 - (sy + 2 * f) % 12
|
||||
image.putpixel((sx, y), glow + (230,))
|
||||
strip.paste(image, (0, 16 * f))
|
||||
return strip
|
||||
|
||||
|
||||
def write_json(path, data):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data, indent=2) + "\n")
|
||||
@@ -148,17 +194,21 @@ def main():
|
||||
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"},
|
||||
})
|
||||
working_front(dark, mid, light, accent, emblem, name).save(textures / f"{name}_front_on.png")
|
||||
write_json(textures / f"{name}_front_on.png.mcmeta", {"animation": {"frametime": FRAME_TICKS, "interpolate": True}})
|
||||
for model, front_texture in ((name, f"{name}_front"), (f"{name}_on", f"{name}_front_on")):
|
||||
write_json(ASSETS / f"models/block/{model}.json", {
|
||||
"parent": "minecraft:block/orientable",
|
||||
"render_type": "minecraft:translucent",
|
||||
"textures": {"top": f"cobblesurvival:block/{name}_top", "front": f"cobblesurvival:block/{front_texture}",
|
||||
"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()},
|
||||
"variants": {f"active={str(active).lower()},facing={facing}": {
|
||||
"model": f"cobblesurvival:block/{name}{'_on' if active else ''}", **({"y": y} if y else {})}
|
||||
for active in (False, True) for facing, y in rotation.items()},
|
||||
})
|
||||
print(f"wrote {len(CORES)} cores to {ASSETS.relative_to(ROOT)}")
|
||||
|
||||
|
||||
Reference in new issue
Block a user