Skip to content

Commit 98c8923

Browse files
Merge pull request #7 from greptileai/feature/hand-drawn-fight-graphic
Feature/hand drawn fight graphic
2 parents 5377415 + 7a7c279 commit 98c8923

32 files changed

Lines changed: 191 additions & 84 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ greptile-game-images/_frames/
2424

2525
# Verification screenshots
2626
.verify-shots/
27+
28+
# Local Claude Code config
29+
.claude/

build_assets.py

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ def validate_victory_screen(system_def=SYSTEM_DEF):
681681
spr = parse_num_pair(victory.get("p1.spr", "-1,0"), int)
682682
required = {
683683
"enabled": "1",
684-
"p1.anim": "-1",
684+
"p1.anim": "9400",
685685
"p1.num": "1",
686686
"p1.layerno": "2",
687687
"p1.applypal": "0",
@@ -1069,6 +1069,84 @@ def build_vs_screen(dst=KOMODO_VS_SFF):
10691069
print(f"[vs] {display_path(dst)}: packed {len(sprites)} VS screen sprites")
10701070

10711071

1072+
FIGHT_TEXT_PNG = "fight-text.png"
1073+
1074+
1075+
def fight_text_sprites(filename=FIGHT_TEXT_PNG):
1076+
"""Slice one FIGHT image into the 4 corner-assembling quadrants used by the
1077+
round-start "FIGHT" callout (anims 520-523).
1078+
1079+
The resting pieces are groups 510,0-3 and the fly-in copies are 511,0-3
1080+
(identical pixels). Each quadrant's axis is the shared center point so all
1081+
four reassemble seamlessly when drawn at fight.offset. Quadrant->number
1082+
mapping matches the fly-in directions: 0=top-left, 1=top-right,
1083+
2=bottom-left, 3=bottom-right.
1084+
"""
1085+
path = ART / "ui/hud" / filename
1086+
if not path.exists():
1087+
return []
1088+
img = Image.open(path).convert("RGBA")
1089+
w, h = img.size
1090+
cx, cy = w // 2, h // 2
1091+
quads = [
1092+
(0, (0, 0, cx, cy), (cx, cy)), # top-left
1093+
(1, (cx, 0, w, cy), (0, cy)), # top-right
1094+
(2, (0, cy, cx, h), (cx, 0)), # bottom-left
1095+
(3, (cx, cy, w, h), (0, 0)), # bottom-right
1096+
]
1097+
sprites = []
1098+
for number, box, (ax, ay) in quads:
1099+
tile = img.crop(box)
1100+
sprites.append(image_sprite(510, number, tile, ax, ay)) # resting
1101+
sprites.append(image_sprite(511, number, tile, ax, ay)) # fly-in
1102+
return sprites
1103+
1104+
1105+
KO_TEXT_PNG = "ko.png"
1106+
# fight.def [Round] draws the KO halves at these fixed bg offsets; we center the
1107+
# reassembled image on screen-x KO_CENTER_X.
1108+
KO_LEFT_OFFSET_X = 430
1109+
KO_RIGHT_OFFSET_X = 830
1110+
KO_CENTER_X = 640
1111+
1112+
1113+
def ko_text_sprites(filename=KO_TEXT_PNG):
1114+
"""Slice one K.O. image into the left/right halves used by the round-end KO
1115+
callout (anims 530/531).
1116+
1117+
The callout layers two sprite groups: 520 (solid letters) and 522 (the
1118+
additive glow flash, drawn scaled 1.25). Each is split at center into ,0
1119+
(left, shown at bg offset 430) and ,1 (right, shown at 830). The axes are
1120+
chosen so the two halves reassemble into the original image centered on
1121+
screen at KO_CENTER_X, so no fight.def/animation changes are needed.
1122+
"""
1123+
path = ART / "ui/hud" / filename
1124+
if not path.exists():
1125+
return []
1126+
img = Image.open(path).convert("RGBA")
1127+
w, h = img.size
1128+
cx = w // 2
1129+
ay = h // 2
1130+
left = img.crop((0, 0, cx, h))
1131+
right = img.crop((cx, 0, w, h))
1132+
# left half: screen-left edge = KO_CENTER_X - w/2, drawn at KO_LEFT_OFFSET_X
1133+
left_ax = KO_LEFT_OFFSET_X - KO_CENTER_X + cx
1134+
# right half: screen-left edge = KO_CENTER_X, drawn at KO_RIGHT_OFFSET_X
1135+
right_ax = KO_RIGHT_OFFSET_X - KO_CENTER_X
1136+
sprites = []
1137+
for group in (520, 522): # solid letters + additive glow share the art
1138+
sprites.append(image_sprite(group, 0, left, left_ax, ay))
1139+
sprites.append(image_sprite(group, 1, right, right_ax, ay))
1140+
# Group 521 is the original black drop shadow (anims 532/533). The custom
1141+
# art carries its own shadow, so blank 521 with transparent tiles to drop
1142+
# the stale shadow behind the new letters.
1143+
blank_left = Image.new("RGBA", left.size, (0, 0, 0, 0))
1144+
blank_right = Image.new("RGBA", right.size, (0, 0, 0, 0))
1145+
sprites.append(image_sprite(521, 0, blank_left, left_ax, ay))
1146+
sprites.append(image_sprite(521, 1, blank_right, right_ax, ay))
1147+
return sprites
1148+
1149+
10721150
def build_hud(dst=FIGHT_SFF):
10731151
"""Pack custom fight HUD art into fight.sff."""
10741152
sprites, palettes = read_sff_v2(dst)
@@ -1082,12 +1160,27 @@ def build_hud(dst=FIGHT_SFF):
10821160
(13, 3, "health-fill-flash.png", (435, 24), 435, 0),
10831161
(530, 1, "result-lizard-wins.png", (707, 102), 353, 51),
10841162
(530, 2, "result-bug-wins.png", (646, 102), 323, 51),
1163+
(40, 0, "powerbar-empty.png", (206, 18), 206, 0),
1164+
(41, 0, "powerbar-frame.png", (210, 22), 210, 0),
1165+
(43, 0, "powerbar-fill.png", (202, 14), 202, 0),
10851166
]
10861167
for group, number, filename, size, ax, ay in replacements:
10871168
img = load_hud_png(filename, size)
10881169
replace_sprite(sprites, image_sprite(group, number, img, ax, ay))
1170+
fight_text = fight_text_sprites()
1171+
for sprite in fight_text:
1172+
replace_sprite(sprites, sprite)
1173+
ko_text = ko_text_sprites()
1174+
for sprite in ko_text:
1175+
replace_sprite(sprites, sprite)
10891176
write_sff_v2(dst, sprites, palettes)
1090-
print(f"[hud] {display_path(dst)}: packed {len(replacements)} HUD sprites")
1177+
extras = []
1178+
if fight_text:
1179+
extras.append(f"FIGHT text ({len(fight_text)} tiles)")
1180+
if ko_text:
1181+
extras.append(f"KO text ({len(ko_text)} tiles)")
1182+
note = (" + " + " + ".join(extras)) if extras else ""
1183+
print(f"[hud] {display_path(dst)}: packed {len(replacements)} HUD sprites{note}")
10911184

10921185

10931186
def build_winner_screen(dst=KOMODO_WINNER_SFF):
@@ -1184,10 +1277,19 @@ def build_character(variant=DEFAULT_VARIANT, dst=None):
11841277
)
11851278

11861279

1187-
def build_stage(scale=1.2):
1188-
"""Build the Greptile city stage SFF from Screen.png."""
1280+
def build_stage(source="Screen-2x.jpg", overfill=1.2):
1281+
"""Build the Greptile city stage SFF from the city background image.
1282+
1283+
Ensures the result overfills the 1920x1080 stage space by at least
1284+
`overfill` (min width 2304) so camera panning never exposes black edges and
1285+
the .def positioning stays valid. A source already larger than the overfill
1286+
minimum (e.g. a 2x 2560x1440 image) is kept at full resolution; only smaller
1287+
sources are upscaled.
1288+
"""
11891289
dst = ROOT / "extracted/stages/greptile_city.sff"
1190-
img = Image.open(ART / "Screen.png").convert("RGBA")
1290+
img = Image.open(ART / source).convert("RGBA")
1291+
min_w = round(1920 * overfill)
1292+
scale = max(1.0, min_w / img.width)
11911293
if scale != 1:
11921294
img = img.resize((round(img.width * scale), round(img.height * scale)), Image.NEAREST)
11931295
buf = io.BytesIO()

0 commit comments

Comments
 (0)