diff --git a/assets-cg/converters.py b/assets-cg/converters.py index feccfd7..d0dc344 100644 --- a/assets-cg/converters.py +++ b/assets-cg/converters.py @@ -477,10 +477,16 @@ def convert_aseprite_anim_variation(input, output, params): else: raise fxconv.FxconvError(f"{input} lacks a 'palette:' metadata") - base = os.path.join(os.path.dirname(input), base) - palette = os.path.join(os.path.dirname(input), palette) + base_path = os.path.join(os.path.dirname(input), base) + palette_path = os.path.join(os.path.dirname(input), palette) - with open(palette, "rb") as fp: + base_metadata = os.path.join(os.path.dirname(input), + "fxconv-metadata.txt") + base_metadata = fxconv.Metadata(base_metadata).rules_for(base) + + params = { **base_metadata, **params } + + with open(palette_path, "rb") as fp: ase = AsepriteFile(fp.read()) newpalette = None @@ -490,7 +496,7 @@ def convert_aseprite_anim_variation(input, output, params): if newpalette is None: raise fxconv.FxconvError("f{input} has no palette chunk") - return convert_aseprite_anim(base, output, params, newpalette) + return convert_aseprite_anim(base_path, output, params, newpalette) def print_xml_tree(node, indent=0): print(indent*" " + f"<{node.tag}> {node.attrib}") diff --git a/src/anim.c b/src/anim.c index bb79b5b..107f3eb 100644 --- a/src/anim.c +++ b/src/anim.c @@ -95,7 +95,7 @@ fixed_t (anim_duration)(anim_t const *anim) bool anim_in(anim_frame_t const *frame, anim_t const *anim, int index) { - if(index >= anim->directions) + if(index >= anim->directions || !frame || !anim) return false; anim_frame_t *f = anim->start[index]; diff --git a/src/aoe.c b/src/aoe.c index f4e36df..29d7884 100644 --- a/src/aoe.c +++ b/src/aoe.c @@ -86,6 +86,13 @@ entity_t *aoe_make_attack(uint16_t type, entity_t *origin, vec2 dir) distance = fix(0.375); lifetime = fix(999.0); } + else if(type == AOE_FIRE_CHARGE) { + anim = NULL; + hitbox = (rect){ -fix(12)/16, fix(12)/16, -fix(13)/16, fix(2)/16 }; + distance = fix(0); + lifetime = fix(1.1); + rotate = false; + } p->x += fmul(distance, dir.x); p->y += fmul(distance, dir.y); @@ -94,7 +101,13 @@ entity_t *aoe_make_attack(uint16_t type, entity_t *origin, vec2 dir) v->sprite_plane = plane; - aoe->lifetime = lifetime ? lifetime : anim_duration(anim); + if(lifetime) + aoe->lifetime = lifetime; + else if(anim) + aoe->lifetime = anim_duration(anim); + else + aoe->lifetime = fix(1.0); + aoe->repeat_delay = 0; aoe->origin = origin; @@ -127,6 +140,12 @@ entity_t *aoe_make_attack(uint16_t type, entity_t *origin, vec2 dir) v->z = fix(0.5); v->shadow_size = 4; } + else if(type == AOE_FIRE_CHARGE) { + aoe->data.charge.element = 0; + aoe->data.charge.power = f->MAG; + aoe->data.charge.origin = physical_pos(origin); + aoe->repeat_delay = fix(0.2); + } visible_set_anim(e, anim, 1); return e; @@ -208,6 +227,14 @@ static bool attack_apply(game_t *game, aoe_t *aoe, entity_t *target) /* TODO: Sideways knockback */ damage = aoe->data.bullet.strength * 2; } + else if(aoe->type == AOE_FIRE_CHARGE) { + dir.x = target_p->x - aoe->data.charge.origin.x; + dir.y = target_p->y - aoe->data.charge.origin.y; + dir = fnormalize(dir); + damage = aoe->data.charge.power * 3 / 2; + r /= 4; + fighter_stun(target, fix(0.1)); + } /* Inflict damage */ damage = fighter_damage(target, damage); @@ -254,7 +281,8 @@ void aoe_apply(game_t *game, entity_t *entity, entity_t *e) || aoe->type == AOE_SLASH || aoe->type == AOE_IMPALE || aoe->type == AOE_SHOCK - || aoe->type == AOE_BULLET) + || aoe->type == AOE_BULLET + || aoe->type == AOE_FIRE_CHARGE) was_hit = attack_apply(game, aoe, e); if(aoe->type == AOE_JUDGEMENT) { diff --git a/src/aoe.h b/src/aoe.h index 5746696..4da0a47 100644 --- a/src/aoe.h +++ b/src/aoe.h @@ -23,6 +23,7 @@ enum { AOE_SHOCK, AOE_JUDGEMENT, AOE_BULLET, + AOE_FIRE_CHARGE, /* Spawn effect */ EFFECT_SPAWN, }; @@ -63,6 +64,8 @@ typedef struct struct { int strength; vec2 origin; } shock; /* AOE_BULLET: magic strengh, speed parameters */ struct { int strength; int dir; fixed_t v; fixed_t final_v; } bullet; + /* AOE_*_CHARGE: element type, origin and MAG power */ + struct { int element; int power; vec2 origin; } charge; } data; } aoe_t; diff --git a/src/comp/visible.c b/src/comp/visible.c index 0d70fa4..1c63308 100644 --- a/src/comp/visible.c +++ b/src/comp/visible.c @@ -9,6 +9,11 @@ void visible_set_anim(entity_t *e, anim_t const *anim, int priority) if(priority < v->anim_priority) return; + if(anim == NULL) { + v->anim.frame = NULL; + return; + } + int anim_index = 0; physical_t *p = getcomp(e, physical); @@ -22,7 +27,7 @@ void visible_set_anim(entity_t *e, anim_t const *anim, int priority) if(anim_in(v->anim.frame, anim, anim_index)) return; - v->anim.frame = anim->start[anim_index]; + v->anim.frame = anim ? anim->start[anim_index] : NULL; v->anim.elapsed = 0; v->anim_priority = priority; } @@ -30,6 +35,9 @@ void visible_set_anim(entity_t *e, anim_t const *anim, int priority) void visible_update(entity_t *e, fixed_t dt) { visible_t *v = getcomp(e, visible); + if(v->anim.frame == NULL) + return; + anim_state_update(&v->anim, dt); if(v->anim.frame == NULL) diff --git a/src/enemies.c b/src/enemies.c index 13c485c..b6d824a 100644 --- a/src/enemies.c +++ b/src/enemies.c @@ -68,7 +68,7 @@ static enemy_t const bat_2 = { static enemy_t const fire_slime_4 = { .name = "Fire slime", .level = 4, - ANIMS(fire_slime, Idle, Walking, Idle, Hit, Death), + ANIMS(fire_slime, Idle, Walking, Attack, Hit, Death), .hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 }, .limits = { .max_speed = fix(1), @@ -176,6 +176,9 @@ entity_t *enemy_make(int enemy_id) struct bat_ai *ai_data = f->enemy->ai_data; ai_data->retreat_period = 0; } + else if(enemy_id == ENEMY_FIRE_SLIME_4) { + f->skills[1] = AOE_FIRE_CHARGE; + } else if(enemy_id == ENEMY_GUNSLINGER_8) { f->skills[0] = AOE_PROJECTILE; } @@ -287,8 +290,7 @@ static bool use_skill_towards_player(game_t *g, entity_t *e, int slot) vec2 dir = { player_p->x - p->x, player_p->y - p->y }; dir = fnormalize(dir); - skill_use(g, e, slot, dir); - return true; + return skill_use(g, e, slot, dir); } void enemy_ai(game_t *g, entity_t *e, fixed_t dt) @@ -318,7 +320,13 @@ void enemy_ai(game_t *g, entity_t *e, fixed_t dt) else if(f->enemy->id == &fire_slime_4) { if(move_within_range_of_player(g, e, fix(1.0), dt)) { - contact_attack(g, e); + /* Use the fire charge attack; if it fails, normal attack */ + if(use_skill_towards_player(g, e, 1)) { + visible_set_anim(e, &anims_fire_slime_Fire, 2); + } + else { + contact_attack(g, e); + } } } diff --git a/src/main.c b/src/main.c index eea2417..db56d6a 100644 --- a/src/main.c +++ b/src/main.c @@ -360,7 +360,7 @@ int main(void) } if(ev.key == KEY_F4 && debug.dev_menu) { player_add_xp(game.player, 1000); - fighter_invulnerability(game.player, fix(999.0)); + // fighter_invulnerability(game.player, fix(999.0)); player_f->skills[1] = AOE_SHOCK; player_f->skills[2] = AOE_JUDGEMENT; player_f->skills[3] = SKILL_DASH; diff --git a/src/render.c b/src/render.c index 5cbed1e..4182b88 100644 --- a/src/render.c +++ b/src/render.c @@ -279,7 +279,7 @@ static void render_entities(game_t const *g, camera_t const *camera, anim_frame_render(scr.x, elevated_y, v->anim.frame); } /* Show entity hitbox in the map coordinate system */ - if(v && (!v->anim.frame || show_hitboxes)) { + if(v && show_hitboxes) { rect r = p->hitbox; r = rect_scale(r, camera_ppu(camera)); r = rect_translate(r, vec_i2f(scr)); diff --git a/src/skills.c b/src/skills.c index b0aeff6..0827948 100644 --- a/src/skills.c +++ b/src/skills.c @@ -18,17 +18,19 @@ fixed_t skill_cooldown(int skill) return fix(3.0); else if(skill == AOE_BULLET) return fix(4.0); + else if(skill == AOE_FIRE_CHARGE) + return fix(3.0); return fix(0.0); } -void skill_use(game_t *game, entity_t *e, int slot, vec2 dir) +bool skill_use(game_t *game, entity_t *e, int slot, vec2 dir) { fighter_t *f = getcomp(e, fighter); int skill = f->skills[slot]; if(skill < 0 || f->actions_cooldown[slot] > 0) - return; + return false; f->actions_cooldown[slot] = skill_cooldown(skill); if(skill == SKILL_DASH) { @@ -36,7 +38,7 @@ void skill_use(game_t *game, entity_t *e, int slot, vec2 dir) } else if(skill == AOE_PROJECTILE) { if(f->current_attack) - return; + return false; entity_t *aoe = aoe_make_attack(AOE_PROJECTILE, e, dir); game_add_entity(game, aoe); @@ -47,7 +49,7 @@ void skill_use(game_t *game, entity_t *e, int slot, vec2 dir) } else if(skill == AOE_SHOCK) { if(f->current_attack) - return; + return false; entity_t *aoe = aoe_make_attack(AOE_SHOCK, e, dir); game_add_entity(game, aoe); @@ -63,7 +65,7 @@ void skill_use(game_t *game, entity_t *e, int slot, vec2 dir) } else if(skill == AOE_JUDGEMENT) { if(f->current_attack) - return; + return false; entity_t *aoe = aoe_make_attack(AOE_JUDGEMENT, e, dir); game_add_entity(game, aoe); @@ -76,7 +78,7 @@ void skill_use(game_t *game, entity_t *e, int slot, vec2 dir) } else if(skill == AOE_BULLET) { if(f->current_attack) - return; + return false; entity_t *aoe = aoe_make_attack(AOE_BULLET, e, dir); game_add_entity(game, aoe); @@ -85,6 +87,21 @@ void skill_use(game_t *game, entity_t *e, int slot, vec2 dir) else visible_set_anim(e, f->enemy->id->anim_attack, 2); } + else if(skill == AOE_FIRE_CHARGE + /* || skill == AOE_WATER_CHARGE */) { + if(f->current_attack) + return false; + entity_t *aoe = aoe_make_attack(skill, e, dir); + game_add_entity(game, aoe); + + f->current_attack = aoe; + f->attack_follows_movement = true; + + /* This skill is used by enemies, the AI will set the animation + themselves on a per-enemy basis */ + } + + return true; } void skill_render(int x, int y, int skill, int bg, int color) diff --git a/src/skills.h b/src/skills.h index 8ee94e0..abc575b 100644 --- a/src/skills.h +++ b/src/skills.h @@ -15,13 +15,14 @@ enum { // AOE_SHOCK, // AOE_JUDGEMENT, // AOE_BULLET, + // AOE_FIRE_CHARGE, }; /* Fixed cooldown for a skill */ fixed_t skill_cooldown(int skill); -/* Have [e] use its skill in the specified slot. */ -void skill_use(game_t *g, entity_t *e, int slot, vec2 dir); +/* Have [e] use its skill in the specified slot. Returns true on success. */ +bool skill_use(game_t *g, entity_t *e, int slot, vec2 dir); /* Render a skill's image */ void skill_render(int x, int y, int skill, int bg, int color);