add a fire charge attack to fire slimes

This commit is contained in:
Lephenixnoir 2022-05-21 20:48:36 +01:00
parent 0b0a667ce0
commit 6e79618784
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
10 changed files with 93 additions and 22 deletions

View File

@ -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}")

View File

@ -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];

View File

@ -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) {

View File

@ -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;

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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));

View File

@ -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)

View File

@ -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);