302 lines
8.2 KiB
C
302 lines
8.2 KiB
C
#include "entities.h"
|
|
#include "game.h"
|
|
#include "enemies.h"
|
|
#include <stdlib.h>
|
|
#include <gint/defs/util.h>
|
|
|
|
//---
|
|
// Entities
|
|
//---
|
|
|
|
fpoint_t entity_pos(entity_t const *e)
|
|
{
|
|
return (fpoint_t){ e->movement.x, e->movement.y };
|
|
}
|
|
|
|
frect_t entity_hitbox(entity_t const *e)
|
|
{
|
|
return rect_translate(e->hitbox, entity_pos(e));
|
|
}
|
|
|
|
frect_t entity_sprite(entity_t const *e)
|
|
{
|
|
return rect_translate(e->sprite, entity_pos(e));
|
|
}
|
|
|
|
static void update_pos(entity_movement_t *m, fixed_t dt)
|
|
{
|
|
m->x += fmul(m->vx, dt);
|
|
m->y += fmul(m->vy, dt);
|
|
}
|
|
|
|
void entity_dash(entity_t *e, int direction)
|
|
{
|
|
entity_movement_params_t const *params = e->movement_params;
|
|
entity_movement_t *move = &e->movement;
|
|
if(move->dash != 0) return;
|
|
|
|
move->dash = params->dash_duration;
|
|
move->dash_facing = direction;
|
|
}
|
|
|
|
bool entity_dashing(entity_t const *e)
|
|
{
|
|
entity_movement_t const *move = &e->movement;
|
|
entity_movement_params_t const *params = e->movement_params;
|
|
|
|
/* True only if the direction of the dash movement is preserved */
|
|
if(move->facing != move->dash_facing)
|
|
return false;
|
|
|
|
/* True during initial propulsion */
|
|
if(move->dash > 0)
|
|
return true;
|
|
|
|
/* Also true as long as 1.5x over-speed is maintained */
|
|
fixed_t cur_v2 = fmul(move->vx, move->vx) + fmul(move->vy, move->vy);
|
|
fixed_t max_v2 = fmul(params->max_speed, params->max_speed);
|
|
|
|
if(move->dash < 0 && 2 * cur_v2 > 3 * max_v2)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
entity_movement_t entity_move(entity_t *e, fpoint_t direction, fixed_t dt)
|
|
{
|
|
entity_movement_params_t const *params = e->movement_params;
|
|
entity_movement_t next = e->movement;
|
|
|
|
direction = fnormalize(direction);
|
|
|
|
/* Determine facing */
|
|
int facing = -1;
|
|
bool horz = abs(direction.x) >= abs(direction.y);
|
|
if(direction.x == 0 && direction.y == 0) facing = -1;
|
|
else if( horz && direction.x >= 0) facing = RIGHT;
|
|
else if( horz && direction.x <= 0) facing = LEFT;
|
|
else if(!horz && direction.y <= 0) facing = UP;
|
|
else if(!horz && direction.y >= 0) facing = DOWN;
|
|
|
|
/* Targeted speed */
|
|
fpoint_t target = { 0, 0 };
|
|
|
|
/* The dash speed is set to one direction and cannot changed */
|
|
if(next.dash > 0) {
|
|
fpoint_t dir = fdir(next.dash_facing);
|
|
target.x += fmul(params->dash_speed, dir.x);
|
|
target.y += fmul(params->dash_speed, dir.y);
|
|
}
|
|
|
|
/* Walking speed can be directed anywhere, anytime */
|
|
if(facing >= 0) {
|
|
next.facing = facing;
|
|
target.x += fmul(params->max_speed, direction.x);
|
|
target.y += fmul(params->max_speed, direction.y);
|
|
}
|
|
|
|
/* Get there exponentially fast */
|
|
next.vx += fmul(target.x - next.vx, fmul(params->propulsion, dt));
|
|
next.vy += fmul(target.y - next.vy, fmul(params->propulsion, dt));
|
|
|
|
/* Round very small speeds (smaller than 1/256 tiles/s) to 0 */
|
|
if(next.vx >= -256 && next.vx <= 255) next.vx = 0;
|
|
if(next.vy >= -256 && next.vy <= 255) next.vy = 0;
|
|
|
|
/* Decrement dash duration or dash cooldown */
|
|
if(next.dash > 0) {
|
|
next.dash -= dt;
|
|
if(next.dash <= 0) next.dash = -params->dash_cooldown;
|
|
}
|
|
else if(next.dash < 0) {
|
|
next.dash += dt;
|
|
if(next.dash >= 0) next.dash = 0;
|
|
}
|
|
|
|
update_pos(&next, dt);
|
|
return next;
|
|
}
|
|
|
|
entity_movement_t entity_move4(entity_t *e, int direction, fixed_t dt)
|
|
{
|
|
return entity_move(e, fdir(direction), dt);
|
|
}
|
|
|
|
void entity_set_normal_anim(entity_t *e, anim_frame_t *frame)
|
|
{
|
|
e->anim.frame = frame;
|
|
e->anim.elapsed = 0;
|
|
}
|
|
|
|
void entity_set_directional_anim(entity_t *e, anim_frame_t *frame_dirs[])
|
|
{
|
|
if(e->movement.facing >= 4) return;
|
|
entity_set_normal_anim(e, frame_dirs[e->movement.facing]);
|
|
}
|
|
|
|
int entity_damage(entity_t *e, int base_damage)
|
|
{
|
|
if(e->HP == 0) return 0;
|
|
|
|
base_damage -= e->DEF;
|
|
if(base_damage <= 0) return 0;
|
|
|
|
int variation = (base_damage >= 4) ? (rand() % (base_damage / 4)) : 0;
|
|
int damage = (base_damage * 7) / 8 + variation;
|
|
|
|
if(e->HP < damage) e->HP = 0;
|
|
else e->HP -= damage;
|
|
|
|
if(e->identity > 0) {
|
|
int index = (e->movement.facing == RIGHT);
|
|
if(e->HP == 0)
|
|
entity_set_anim(e, enemies[e->identity]->anim_death[index]);
|
|
else
|
|
entity_set_anim(e, enemies[e->identity]->anim_damage[index]);
|
|
}
|
|
else {
|
|
entity_set_anim(e, anims_player_Hit[e->movement.facing]);
|
|
}
|
|
|
|
return damage;
|
|
}
|
|
|
|
//---
|
|
// Effect areas
|
|
//---
|
|
|
|
effect_area_t *effect_area_new(uint16_t type)
|
|
{
|
|
effect_area_t *ea = malloc(sizeof *ea);
|
|
if(!ea) return NULL;
|
|
|
|
ea->type = type;
|
|
ea->hits = NULL;
|
|
ea->hit_count = 0;
|
|
|
|
return ea;
|
|
}
|
|
|
|
effect_area_t *effect_area_new_attack(uint16_t type, entity_t *e, int facing)
|
|
{
|
|
effect_area_t *area = effect_area_new(type);
|
|
if(!area) return NULL;
|
|
|
|
frect_t hitbox = { 0 };
|
|
fixed_t distance = fix(0.75);
|
|
fpoint_t dir = fdir(facing);
|
|
anim_frame_t *anim = NULL;
|
|
|
|
if(type == EFFECT_ATTACK_HIT) {
|
|
anim = anim_hit;
|
|
hitbox = (frect_t){ -fix(4)/16, fix(3)/16, -fix(4)/16, fix(3)/16 };
|
|
}
|
|
else if(type == EFFECT_ATTACK_SLASH) {
|
|
anim = anim_swing[facing];
|
|
hitbox = (frect_t){ -fix(10)/16, fix(10)/16, -fix(4)/16, fix(4)/16 };
|
|
}
|
|
else if(type == EFFECT_ATTACK_IMPALE) {
|
|
anim = anim_impale[facing];
|
|
hitbox = (frect_t){ -fix(4)/16, fix(4)/16, -fix(5)/16, fix(5)/16 };
|
|
}
|
|
|
|
area->sprite = rect_rotate(hitbox, UP, facing);
|
|
area->anchor = rect_center(entity_sprite(e));
|
|
area->anchor.x += fmul(distance, dir.x);
|
|
area->anchor.y += fmul(distance, dir.y);
|
|
area->lifetime = anim_duration(anim);
|
|
area->repeat_delay = 0;
|
|
area->origin = e;
|
|
|
|
if(type == EFFECT_ATTACK_HIT
|
|
|| type == EFFECT_ATTACK_SLASH
|
|
|| type == EFFECT_ATTACK_IMPALE) {
|
|
area->data.slash.strength = e->ATK;
|
|
area->data.slash.dir = facing;
|
|
}
|
|
|
|
effect_area_set_anim(area, anim);
|
|
return area;
|
|
}
|
|
|
|
void effect_area_set_anim(effect_area_t *ea, anim_frame_t *frame)
|
|
{
|
|
ea->anim.frame = frame;
|
|
ea->anim.elapsed = 0;
|
|
}
|
|
|
|
/* Existing record of the area hitting this entity */
|
|
static effect_area_record_t *effect_record(effect_area_t *ea, entity_t *e)
|
|
{
|
|
for(int i = 0; i < ea->hit_count; i++) {
|
|
effect_area_record_t *rec = &ea->hits[i];
|
|
if(rec->entity == e) return rec;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool slash_hit_apply(game_t *game, effect_area_t *ea, entity_t *e)
|
|
{
|
|
/* No friendly fire */
|
|
bool origin_is_monster = (ea->origin->identity != 0);
|
|
bool target_is_monster = (e->identity != 0);
|
|
if(origin_is_monster == target_is_monster || e->HP == 0) return false;
|
|
|
|
/* Dash invincibility */
|
|
if(entity_dashing(e)) return false;
|
|
|
|
/* Inflict damage */
|
|
int damage = entity_damage(e, ea->data.slash.strength);
|
|
|
|
/* Knockback */
|
|
fixed_t r = (rand() & (fix(0.25)-1)) + fix(0.875);
|
|
/* Half knockback against players */
|
|
if(e->identity == 0) r /= 2;
|
|
|
|
fpoint_t dir = fdir(ea->data.slash.dir);
|
|
e->movement.vx += fmul(dir.x, fmul(r, KNOCKBACK_SPEED));
|
|
e->movement.vy += fmul(dir.y, fmul(r, KNOCKBACK_SPEED));
|
|
|
|
/* Spawn damage particle */
|
|
particle_damage_t *p = malloc(sizeof *p);
|
|
p->particle.type = PARTICLE_DAMAGE;
|
|
p->particle.age = 0;
|
|
p->particle.pos = (fpoint_t){ e->movement.x, e->movement.y - fix(0.5) };
|
|
p->damage = damage;
|
|
p->color = (e->identity == 0) ? C_RED : C_WHITE;
|
|
game_add_particle(game, &p->particle);
|
|
|
|
return true;
|
|
}
|
|
|
|
void effect_area_apply(game_t *game, effect_area_t *ea, entity_t *e)
|
|
{
|
|
bool was_hit = false;
|
|
effect_area_record_t *rec = effect_record(ea, e);
|
|
|
|
/* Don't hit entities that have been recently hit */
|
|
if(rec && ea->repeat_delay == 0) return;
|
|
if(rec && ea->lifetime > rec->lifetime - ea->repeat_delay) return;
|
|
|
|
if(ea->type == EFFECT_ATTACK_HIT
|
|
|| ea->type == EFFECT_ATTACK_SLASH
|
|
|| ea->type == EFFECT_ATTACK_IMPALE)
|
|
was_hit = slash_hit_apply(game, ea, e);
|
|
|
|
if(!was_hit) return;
|
|
|
|
/* Update record */
|
|
if(!rec) {
|
|
size_t new_size = (ea->hit_count + 1) * sizeof *ea->hits;
|
|
effect_area_record_t *new_hits = realloc(ea->hits, new_size);
|
|
if(!new_hits) return;
|
|
|
|
ea->hits = new_hits;
|
|
rec = &new_hits[ea->hit_count];
|
|
rec->entity = e;
|
|
ea->hit_count++;
|
|
}
|
|
|
|
rec->lifetime = ea->lifetime;
|
|
}
|