From 1b2f0fbc15131d073fbc75b7f76c27b428134a59 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Thu, 30 Dec 2021 10:16:15 +0100 Subject: [PATCH] add support for palette variations of sprites --- CMakeLists.txt | 3 + assets-cg/converters.py | 81 +++++++++++++++--- assets-cg/enemies/fire_slime_left.txt | 0 assets-cg/enemies/fire_slime_palette.aseprite | Bin 0 -> 328 bytes assets-cg/enemies/fire_slime_right.txt | 0 assets-cg/enemies/fxconv-metadata.txt | 14 +++ assets-cg/levels/lv1.txt | 4 +- src/anim.c | 4 + src/anim.h | 4 + src/enemies.c | 20 +++++ src/enemies.h | 3 +- src/main.c | 1 + 12 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 assets-cg/enemies/fire_slime_left.txt create mode 100644 assets-cg/enemies/fire_slime_palette.aseprite create mode 100644 assets-cg/enemies/fire_slime_right.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 9705ab7..47bce2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,9 @@ set(ASSETS # Enemies: Gunslinger assets-cg/enemies/gunslinger_left.aseprite assets-cg/enemies/gunslinger_right.aseprite + # Enemies: Fire Slime + assets-cg/enemies/fire_slime_left.txt + assets-cg/enemies/fire_slime_right.txt # Misc assets-cg/font_damage_red.png assets-cg/font_damage_white.png diff --git a/assets-cg/converters.py b/assets-cg/converters.py index 89198d9..48e383a 100644 --- a/assets-cg/converters.py +++ b/assets-cg/converters.py @@ -14,6 +14,8 @@ def convert(input, output, params, target): o = convert_animation(input, params) elif params["custom-type"] == "aseprite-anim": o = convert_aseprite_anim(input, output, params) + elif params["custom-type"] == "aseprite-anim-variation": + o = convert_aseprite_anim_variation(input, output, params) elif params["custom-type"] == "tiled-tileset": o = convert_tiled_tileset(input, output, params) elif params["custom-type"] == "tiled-map": @@ -32,7 +34,7 @@ def convert_level(input, params): "map": re.compile(r'[a-zA-Z_][a-zA-Z0-9-]*'), "spawner": re.compile(r'\d+,\d+'), "player_spawn": re.compile(r'\d+,\d+'), - "wave": re.compile(r'\d+s(\s+\d+\*[a-z]+/\d+)+'), + "wave": re.compile(r'\d+s(\s+\d+\*[a-z_]+/\d+)+'), "delay": re.compile(r'\d+s') } with open(input, "r") as fp: @@ -95,7 +97,7 @@ def convert_level(input, params): identity = 1 elif identity == "bat/2": identity = 2 - elif identity == "slime/4": + elif identity == "fire_slime/4": identity = 3 elif identity == "gunslinger/8": identity = 4 @@ -231,26 +233,49 @@ def convert_animation(input, params): return o -def _aseprite_render_cel(c): +def _aseprite_render_cel(c, indexed=False, palette=None, newpalette=None): if c.chunk_type != 0x2005: # Cel - raise fxconv.FxconvError("Rendering a Cel without a Cel chunk") + raise fxconv.FxconvError("Rendering a Cel but it's not a Cel chunk") if c.cel_type not in [0,2]: raise fxconv.FxconvError("Rendering a Cel that is linked or tilemap") + colors = None + + if indexed: + assert palette is not None and palette.chunk_type == 0x2019 # Palette + # py_aseprite swaps blue and green!! + colors = [(c["red"], c["blue"], c["green"], c["alpha"]) + for c in palette.colors] + if newpalette is not None: + assert indexed and newpalette.chunk_type == 0x2019 # Palette + newcolors = [(c["red"], c["blue"], c["green"], c["alpha"]) + for c in newpalette.colors] + img = Image.new("RGBA", (c.data['width'], c.data['height'])) pixels = [] offset = 0 for y in range(img.height): for x in range(img.width): - r, g, b, a = c.data['data'][offset:offset+4] + if indexed: + i = c.data['data'][offset] + offset += 1 + else: + r, g, b, a = c.data['data'][offset:offset+4] + offset += 4 + + # Remap palette if requested + if newpalette is not None: + r, g, b, a = newcolors[i] + elif indexed: + r, g, b, a = colors[i] + pixels.append((r, g, b, a)) - offset += 4 img.putdata(pixels) return img -def convert_aseprite_anim(input, output, params): +def convert_aseprite_anim(input, output, params, newpalette=None): from aseprite import AsepriteFile #--- @@ -260,8 +285,12 @@ def convert_aseprite_anim(input, output, params): with open(input, "rb") as fp: ase = AsepriteFile(fp.read()) - if ase.header.color_depth != 32: - raise fxconv.FxconvError("Only RGBA supported yet, sorry x_x") + if ase.header.color_depth == 8: + indexed = True + elif ase.header.color_depth == 32: + indexed = False + else: + raise fxconv.FxconvError("Only indexed/RGBA supported yet, sorry x_x") # Find tags that give names to animations tags = None @@ -283,6 +312,16 @@ def convert_aseprite_anim(input, output, params): durations = [ase.frames[i].frame_duration for i in range(from_, to+1)] print(" (" + ", ".join(f"{d} ms" for d in durations) + ")") + #--- + # Find palette + #--- + + palette_chunk = None + + for c in ase.frames[0].chunks: + if c.chunk_type == 0x2019: # Palette + palette_chunk = c + #--- # Generate PIL images for each frame #--- @@ -302,7 +341,8 @@ def convert_aseprite_anim(input, output, params): x = [c for c in x if c.chunk_type == 0x2005] chunk = x[chunk.layer_index] - cel = _aseprite_render_cel(chunk) + cel = _aseprite_render_cel(chunk, indexed, palette_chunk, + newpalette) img.paste(cel, (chunk.x_pos, chunk.y_pos)) pil_frames.append(img) @@ -382,6 +422,27 @@ def convert_aseprite_anim(input, output, params): return o +def convert_aseprite_anim_variation(input, output, params): + if "base" not in params or "palette" not in params: + raise fxconv.FxconvError("aseprite anim variation needs base/palette") + + from aseprite import AsepriteFile + + base = os.path.join(os.path.dirname(input), params["base"]) + palette = os.path.join(os.path.dirname(input), params["palette"]) + + with open(palette, "rb") as fp: + ase = AsepriteFile(fp.read()) + + newpalette = None + for c in ase.frames[0].chunks: + if c.chunk_type == 0x2019: # Palette + newpalette = c + if newpalette is None: + raise fxconv.FxconvError("f{input} has no palette chunk") + + return convert_aseprite_anim(base, output, params, newpalette) + def print_xml_tree(node, indent=0): print(indent*" " + f"<{node.tag}> {node.attrib}") for child in node: diff --git a/assets-cg/enemies/fire_slime_left.txt b/assets-cg/enemies/fire_slime_left.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets-cg/enemies/fire_slime_palette.aseprite b/assets-cg/enemies/fire_slime_palette.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..16de21d001ee31e538a79e26f58d89635309902a GIT binary patch literal 328 zcmeZZWMFu(l#zjh0fHGAQW%f{P#ma&0VvObEKD`_37|>8KC*yqWdX9ifLKxiW*0ja zd*nEI|1&WBKT--L4>(u?$#1caK(f|197tv>C;-X-K%fNF%L4Qj*x?{eM@oUpfog#Y zL8_oC6!?+s0BU96VsJ`K&Q34NFU?EQ0`gfEAPW97L1>VHj0_cXk`q#r(h^gXfo_ap GkOcrI3oM`j literal 0 HcmV?d00001 diff --git a/assets-cg/enemies/fire_slime_right.txt b/assets-cg/enemies/fire_slime_right.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets-cg/enemies/fxconv-metadata.txt b/assets-cg/enemies/fxconv-metadata.txt index 21d45d6..68a698d 100644 --- a/assets-cg/enemies/fxconv-metadata.txt +++ b/assets-cg/enemies/fxconv-metadata.txt @@ -20,3 +20,17 @@ gunslinger_left.aseprite: gunslinger_right.aseprite: center: 8, 22 next: Idle=Idle, Walking=Walking, Hit=Idle, Fire=Reloading + +*.txt: + custom-type: aseprite-anim-variation + name_regex: (.*)\.txt frames_\1 + profile: p8 + next: Idle=Idle, Walking=Walking, Hit=Idle + +fire_slime_left.txt: + base: slime_left.aseprite + palette: fire_slime_palette.aseprite + +fire_slime_right.txt: + base: slime_right.aseprite + palette: fire_slime_palette.aseprite diff --git a/assets-cg/levels/lv1.txt b/assets-cg/levels/lv1.txt index 26ede15..82ee39b 100644 --- a/assets-cg/levels/lv1.txt +++ b/assets-cg/levels/lv1.txt @@ -13,4 +13,6 @@ wave: 4s 8*bat/2 delay: 2s wave: 8s 16*slime/1 8*bat/2 delay: 6s -wave: 2s 8*slime/1 8*bat/2 2*slime/4 +wave: 2s 8*slime/1 8*bat/2 2*fire_slime/4 +delay: 2s +wave: 8s 12*bat/2 4*fire_slime/4 1*gunslinger/8 diff --git a/src/anim.c b/src/anim.c index 6ced6df..f26a08f 100644 --- a/src/anim.c +++ b/src/anim.c @@ -57,6 +57,10 @@ ANIM2(gunslinger, Reloading); ANIM2(gunslinger, Fire); ANIM2(gunslinger, Hit); ANIM2(gunslinger, Death); +ANIM2(fire_slime, Idle); +ANIM2(fire_slime, Walking); +ANIM2(fire_slime, Hit); +ANIM2(fire_slime, Death); /* Player */ ANIM4(player, Idle); diff --git a/src/anim.h b/src/anim.h index c128f18..6937a54 100644 --- a/src/anim.h +++ b/src/anim.h @@ -86,6 +86,10 @@ extern anim_t anims_gunslinger_Reloading; extern anim_t anims_gunslinger_Fire; extern anim_t anims_gunslinger_Hit; extern anim_t anims_gunslinger_Death; +extern anim_t anims_fire_slime_Idle; +extern anim_t anims_fire_slime_Walking; +extern anim_t anims_fire_slime_Hit; +extern anim_t anims_fire_slime_Death; /* Player */ extern anim_t anims_player_Idle; diff --git a/src/enemies.c b/src/enemies.c index 9f3233f..6a666c2 100644 --- a/src/enemies.c +++ b/src/enemies.c @@ -52,6 +52,25 @@ static enemy_t const bat_2 = { .z = fix(0.75), }; +static enemy_t const fire_slime_4 = { + .name = "Fire slime", + .level = 4, + ANIMS(fire_slime, Idle, Walking, Idle, Hit, Death), + .hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 }, + .limits = { + .max_speed = fix(1), + .friction = fix(0.6), + }, + .stats = { + .HP = fix(1.4), + .ATK = fix(1.2), + .MAG = fix(1.4), + .DEF = fix(1.4), + }, + .shadow_size = 4, + .z = 0, +}; + static enemy_t const gunslinger_8 = { .name = "Gunslinger", .level = 8, @@ -74,6 +93,7 @@ static enemy_t const gunslinger_8 = { static enemy_t const * const enemies[] = { [ENEMY_SLIME_1] = &slime_1, [ENEMY_BAT_2] = &bat_2, + [ENEMY_FIRE_SLIME_4] = &fire_slime_4, [ENEMY_GUNSLINGER_8] = &gunslinger_8, }; diff --git a/src/enemies.h b/src/enemies.h index e43ba8b..249a6b8 100644 --- a/src/enemies.h +++ b/src/enemies.h @@ -37,7 +37,8 @@ enum { /* ID 0 is used for the player! */ ENEMY_SLIME_1 = 1, ENEMY_BAT_2 = 2, - ENEMY_GUNSLINGER_8 = 3, + ENEMY_FIRE_SLIME_4 = 3, + ENEMY_GUNSLINGER_8 = 4, }; /* Get enemy data by ID. */ diff --git a/src/main.c b/src/main.c index 4a35be7..f286340 100644 --- a/src/main.c +++ b/src/main.c @@ -525,6 +525,7 @@ int main(void) game_update_animations(&game, dt); game_update_aoes(&game, dt); game_update_particles(&game, dt); + // TODO: Kill out-of-bounds entities game_remove_dead_entities(&game); player_f->combo_delay -= dt;