switch engine to ECS and rewrite just about everything
This commit is contained in:
parent
228dec623e
commit
cb3f363cfe
|
@ -16,8 +16,8 @@ endif()
|
|||
|
||||
set(SOURCES
|
||||
src/anim.c
|
||||
src/aoe.c
|
||||
src/enemies.c
|
||||
src/entities.c
|
||||
src/game.c
|
||||
src/geometry.c
|
||||
src/level.c
|
||||
|
@ -27,6 +27,12 @@ set(SOURCES
|
|||
src/pathfinding.c
|
||||
src/render.c
|
||||
src/util.c
|
||||
|
||||
src/comp/entity.c
|
||||
src/comp/fighter.c
|
||||
src/comp/mechanical.c
|
||||
src/comp/physical.c
|
||||
src/comp/visible.c
|
||||
)
|
||||
set(ASSETS
|
||||
# Tilesets
|
||||
|
@ -80,6 +86,7 @@ fxconv_declare_converters(assets-cg/converters.py)
|
|||
|
||||
add_executable(addin ${SOURCES} ${ASSETS})
|
||||
target_compile_options(addin PRIVATE -Wall -Wextra -Os)
|
||||
target_include_directories(addin PRIVATE src)
|
||||
target_link_libraries(addin LibProf::LibProf Gint::Gint)
|
||||
target_link_options(addin PRIVATE -Wl,-Map=map)
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
*.aseprite:
|
||||
custom-type: aseprite-anim
|
||||
name_regex: (.*)\.aseprite anims_\1
|
||||
name_regex: (.*)\.aseprite frames_\1
|
||||
profile: p8
|
||||
next: Idle=Idle, Walking=Walking, Hit=Idle
|
||||
|
||||
slime_left.aseprite:
|
||||
center: 12, 17
|
||||
center: 11, 18
|
||||
slime_right.aseprite:
|
||||
center: 13, 17
|
||||
center: 11, 18
|
||||
|
||||
bat_left.aseprite:
|
||||
center: 10, 12
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -2,13 +2,13 @@
|
|||
|
||||
__ #_ #b #_ #_ __ __ __ #_ #t #_ __ __ __ __ __ __ __ __ __ __ __ __ __
|
||||
__ R_ Rs R_ R_ #_ __ #_ Y_ Y_ Y_ #_ #b #t #_ __ __ __ #b #_ #_ __ __ __
|
||||
__ R_ R_ R_ R_ R_ __ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ #_ #_ #_ G_ G_ G_ __ __ __
|
||||
__ R_ R_ R_ R_ Y_ #_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ G_ G_ G_ __ __ __
|
||||
__ R_ Y_ Y_ Y_ Y_ Y_ Y_ __ __ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ __ __ __ __
|
||||
__ __ __ __ __ __ Y_ Y_ #t #_ Y_ Y_ Y_ Y_ Y_ Y_ __ Y_ Y_ Y_ #_ __ __ __
|
||||
__ R_ R_ R_ R_ R_ #_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ #_ #_ #_ G_ G_ G_ __ __ __
|
||||
__ R_ R_ R_ R_ Y_ Y_ Y_ Y_ R_ R_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ G_ G_ G_ __ __ __
|
||||
__ R_ Y_ Y_ Y_ Y_ Y_ Y_ R_ R_ R_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ __ __ __ __
|
||||
__ __ __ __ __ __ Y_ Y_ R_ R_ Y_ Y_ Y_ Y_ Y_ Y_ B_ Y_ Y_ Y_ #_ __ __ __
|
||||
__ __ #_ #t #_ #_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ Y_ __ Y_ Y_ Y_ Y_ #b #_ __
|
||||
__ __ G_ G_ G_ Y_ Y_ Y_ Y_ __ __ __ Y_ #_ #b #_ #_ Y_ B_ B_ B_ B_ Y_ __
|
||||
__ __ G_ G_ G_ __ Y_ Y_ Y_ __ __ __ Y_ Y_ Y_ B_ B_ B_ B_ Bs B_ B_ B_ __
|
||||
__ __ G_ G_ G_ G_ Y_ Y_ Y_ Y_ __ __ Y_ #_ #b #_ #_ Y_ B_ B_ B_ B_ Y_ __
|
||||
__ __ G_ G_ G_ G_ G_ Y_ Y_ __ __ __ Y_ Y_ Y_ B_ B_ B_ B_ Bs B_ B_ B_ __
|
||||
__ __ G_ G_ G_ __ __ Y_ Y_ __ __ __ __ __ __ __ __ B_ B_ B_ B_ B_ __ __
|
||||
__ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
player_*.aseprite:
|
||||
custom-type: aseprite-anim
|
||||
name_regex: (.*)\.aseprite anims_\1
|
||||
center: 12, 17
|
||||
name_regex: (.*)\.aseprite frames_\1
|
||||
center: 11, 19
|
||||
next: Idle=Idle, Walking=Walking
|
||||
profile: p8
|
||||
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 540 B After Width: | Height: | Size: 553 B |
|
@ -1,6 +1,6 @@
|
|||
*.aseprite:
|
||||
custom-type: aseprite-anim
|
||||
name_regex: (.*)\.aseprite anims_skill_\1
|
||||
name_regex: (.*)\.aseprite frames_skill_\1
|
||||
profile: p4
|
||||
|
||||
bullet_up.aseprite:
|
||||
|
|
98
src/anim.c
98
src/anim.c
|
@ -1,46 +1,72 @@
|
|||
#include "anim.h"
|
||||
#include <gint/display.h>
|
||||
|
||||
/* List of animations (and directional animations). */
|
||||
/* Definition of simple and direction animations. */
|
||||
|
||||
#define DIRECTIONAL_ANIM(name) \
|
||||
extern anim_frame_t name ## _up[]; \
|
||||
extern anim_frame_t name ## _right[]; \
|
||||
extern anim_frame_t name ## _down[]; \
|
||||
extern anim_frame_t name ## _left[]; \
|
||||
anim_frame_t *name[4] = { \
|
||||
name ## _up, name ## _right, name ## _down, name ## _left };
|
||||
#define ANIM1(name) \
|
||||
extern anim_frame_t frames_ ## name[]; \
|
||||
anim_t anims_ ## name = { 1, { frames_ ## name }};
|
||||
|
||||
#define ANIM_2DIRECTIONAL(prefix, suffix) \
|
||||
extern anim_frame_t frames_ ## prefix ## _left ## suffix[]; \
|
||||
extern anim_frame_t frames_ ## prefix ## _right ## suffix[]; \
|
||||
anim_t anims_ ## prefix ## suffix = { 2, { \
|
||||
frames_ ## prefix ## _left ## suffix, \
|
||||
frames_ ## prefix ## _right ## suffix, \
|
||||
}}; \
|
||||
|
||||
#define ANIM_4DIRECTIONAL(prefix, suffix) \
|
||||
extern anim_frame_t prefix ## _up_ ## suffix[]; \
|
||||
extern anim_frame_t prefix ## _right_ ## suffix[]; \
|
||||
extern anim_frame_t prefix ## _down_ ## suffix[]; \
|
||||
extern anim_frame_t prefix ## _left_ ## suffix[]; \
|
||||
anim_frame_t *prefix ## _ ## suffix[4] = { \
|
||||
prefix ## _up_ ## suffix, \
|
||||
prefix ## _right_ ## suffix, \
|
||||
prefix ## _down_ ## suffix, \
|
||||
prefix ## _left_ ## suffix, \
|
||||
};
|
||||
extern anim_frame_t frames_ ## prefix ## _up ## suffix[]; \
|
||||
extern anim_frame_t frames_ ## prefix ## _right ## suffix[]; \
|
||||
extern anim_frame_t frames_ ## prefix ## _down ## suffix[]; \
|
||||
extern anim_frame_t frames_ ## prefix ## _left ## suffix[]; \
|
||||
anim_t anims_ ## prefix ## suffix = { 4, { \
|
||||
frames_ ## prefix ## _up ## suffix, \
|
||||
frames_ ## prefix ## _right ## suffix, \
|
||||
frames_ ## prefix ## _down ## suffix, \
|
||||
frames_ ## prefix ## _left ## suffix, \
|
||||
}};
|
||||
|
||||
DIRECTIONAL_ANIM(anims_skill_swing);
|
||||
DIRECTIONAL_ANIM(anims_skill_impale);
|
||||
DIRECTIONAL_ANIM(anims_skill_bullet);
|
||||
/* Make suffix optional */
|
||||
#define ANIM2(prefix, ...) \
|
||||
ANIM_2DIRECTIONAL(prefix, __VA_OPT__(_ ## __VA_ARGS__))
|
||||
#define ANIM4(prefix, ...) \
|
||||
ANIM_4DIRECTIONAL(prefix, __VA_OPT__(_ ## __VA_ARGS__))
|
||||
|
||||
ANIM_4DIRECTIONAL(anims_player, Idle);
|
||||
ANIM_4DIRECTIONAL(anims_player, Walking);
|
||||
ANIM_4DIRECTIONAL(anims_player, Attack);
|
||||
ANIM_4DIRECTIONAL(anims_player, Hit);
|
||||
/* Basic skills */
|
||||
ANIM1(skill_hit);
|
||||
ANIM1(skill_teleport);
|
||||
ANIM1(skill_shock);
|
||||
ANIM1(skill_judgement);
|
||||
/* Directional skills */
|
||||
ANIM4(skill_swing);
|
||||
ANIM4(skill_impale);
|
||||
ANIM4(skill_bullet);
|
||||
|
||||
/* Enemies */
|
||||
ANIM2(slime, Idle);
|
||||
ANIM2(slime, Walking);
|
||||
ANIM2(slime, Hit);
|
||||
ANIM2(slime, Death);
|
||||
ANIM2(bat, Idle);
|
||||
ANIM2(bat, Hit);
|
||||
ANIM2(bat, Death);
|
||||
|
||||
/* Player */
|
||||
ANIM4(player, Idle);
|
||||
ANIM4(player, Walking);
|
||||
ANIM4(player, Attack);
|
||||
ANIM4(player, Hit);
|
||||
|
||||
/* Animation functions. */
|
||||
|
||||
fixed_t (anim_duration)(anim_frame_t const *first_frame)
|
||||
fixed_t (anim_duration)(anim_t const *anim)
|
||||
{
|
||||
anim_frame_t const *frame = first_frame;
|
||||
anim_frame_t const *frame = anim->start[0];
|
||||
int ms = 0;
|
||||
int i = 0;
|
||||
|
||||
while(frame == first_frame + i) {
|
||||
while(frame == anim->start[0] + i) {
|
||||
ms += frame->duration;
|
||||
frame = frame->next;
|
||||
i++;
|
||||
|
@ -49,6 +75,22 @@ fixed_t (anim_duration)(anim_frame_t const *first_frame)
|
|||
return fix(ms) / 1000;
|
||||
}
|
||||
|
||||
bool anim_in(anim_frame_t const *frame, anim_t const *anim, int index)
|
||||
{
|
||||
if(index >= anim->directions)
|
||||
return false;
|
||||
|
||||
anim_frame_t *f = anim->start[index];
|
||||
|
||||
for(int i = 0; f == anim->start[index] + i; i++) {
|
||||
if(f == frame)
|
||||
return true;
|
||||
f = f->next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void anim_frame_render(int x, int y, anim_frame_t const *frame)
|
||||
{
|
||||
if(!frame) return;
|
||||
|
|
81
src/anim.h
81
src/anim.h
|
@ -12,7 +12,8 @@
|
|||
|
||||
#include <gint/display.h>
|
||||
|
||||
typedef struct anim_frame {
|
||||
typedef struct anim_frame
|
||||
{
|
||||
/* Sheet */
|
||||
bopti_image_t const *sheet;
|
||||
/* Box for the frame within the sheet */
|
||||
|
@ -23,21 +24,35 @@ typedef struct anim_frame {
|
|||
uint16_t duration;
|
||||
/* Next frame */
|
||||
struct anim_frame *next;
|
||||
|
||||
} anim_frame_t;
|
||||
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
/* Number of directions
|
||||
1: Basic sprite
|
||||
2: LEFT and RIGHT (enemies)
|
||||
4: UP, RIGHT, DOWN and LEFT (player, some attacks) */
|
||||
int directions;
|
||||
/* First frame for each direction */
|
||||
anim_frame_t *start[];
|
||||
|
||||
} anim_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* Current frame */
|
||||
anim_frame_t const *frame;
|
||||
/* Time elapsed */
|
||||
uint16_t elapsed;
|
||||
|
||||
} anim_state_t;
|
||||
|
||||
/* Duration of an animation, in seconds (assuming linear order until end). */
|
||||
fixed_t anim_duration(anim_frame_t const *first_frame);
|
||||
/* Duration of an animation, in seconds; assumed unique for all directions */
|
||||
fixed_t anim_duration(anim_t const *anim);
|
||||
|
||||
#define anim_duration(ff) _Generic((ff), \
|
||||
anim_frame_t *: anim_duration((void *)ff), \
|
||||
anim_frame_t **: anim_duration(*(void **)ff))
|
||||
/* Check if a frame is in an animation. */
|
||||
bool anim_in(anim_frame_t const *frame, anim_t const *anim, int index);
|
||||
|
||||
/* Render a frame at the center position (x,y). */
|
||||
void anim_frame_render(int x, int y, anim_frame_t const *frame);
|
||||
|
@ -47,35 +62,27 @@ void anim_state_update(anim_state_t *state, fixed_t dt);
|
|||
|
||||
/* List of animations. */
|
||||
|
||||
/* Basic animations. */
|
||||
extern anim_frame_t anims_skill_hit[];
|
||||
extern anim_frame_t anims_skill_teleport[];
|
||||
extern anim_frame_t anims_skill_shock[];
|
||||
extern anim_frame_t anims_skill_judgement[];
|
||||
/* Basic skills */
|
||||
extern anim_t anims_skill_hit;
|
||||
extern anim_t anims_skill_teleport;
|
||||
extern anim_t anims_skill_shock;
|
||||
extern anim_t anims_skill_judgement;
|
||||
/* Directional skills */
|
||||
extern anim_t anims_skill_swing;
|
||||
extern anim_t anims_skill_impale;
|
||||
extern anim_t anims_skill_bullet;
|
||||
|
||||
/* Enemy animations (bidirectional). */
|
||||
/* Enemies */
|
||||
extern anim_t anims_slime_Idle;
|
||||
extern anim_t anims_slime_Walking;
|
||||
extern anim_t anims_slime_Hit;
|
||||
extern anim_t anims_slime_Death;
|
||||
extern anim_t anims_bat_Idle;
|
||||
extern anim_t anims_bat_Hit;
|
||||
extern anim_t anims_bat_Death;
|
||||
|
||||
extern anim_frame_t anims_slime_left_Idle[];
|
||||
extern anim_frame_t anims_slime_right_Idle[];
|
||||
extern anim_frame_t anims_slime_left_Walking[];
|
||||
extern anim_frame_t anims_slime_right_Walking[];
|
||||
extern anim_frame_t anims_slime_left_Hit[];
|
||||
extern anim_frame_t anims_slime_right_Hit[];
|
||||
extern anim_frame_t anims_slime_left_Death[];
|
||||
extern anim_frame_t anims_slime_right_Death[];
|
||||
|
||||
extern anim_frame_t anims_bat_left_Idle[];
|
||||
extern anim_frame_t anims_bat_right_Idle[];
|
||||
extern anim_frame_t anims_bat_left_Hit[];
|
||||
extern anim_frame_t anims_bat_right_Hit[];
|
||||
extern anim_frame_t anims_bat_left_Death[];
|
||||
extern anim_frame_t anims_bat_right_Death[];
|
||||
|
||||
/* Quadri-directional animations. */
|
||||
extern anim_frame_t *anims_player_Idle[4];
|
||||
extern anim_frame_t *anims_player_Walking[4];
|
||||
extern anim_frame_t *anims_player_Attack[4];
|
||||
extern anim_frame_t *anims_player_Hit[4];
|
||||
extern anim_frame_t *anims_skill_swing[4];
|
||||
extern anim_frame_t *anims_skill_impale[4];
|
||||
extern anim_frame_t *anims_skill_bullet[4];
|
||||
/* Player */
|
||||
extern anim_t anims_player_Idle;
|
||||
extern anim_t anims_player_Walking;
|
||||
extern anim_t anims_player_Attack;
|
||||
extern anim_t anims_player_Hit;
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/physical.h"
|
||||
#include "comp/visible.h"
|
||||
#include "comp/fighter.h"
|
||||
#include "aoe.h"
|
||||
#include "game.h"
|
||||
#include "enemies.h"
|
||||
|
||||
entity_t *aoe_make(uint16_t type, vec2 position, fixed_t lifetime)
|
||||
{
|
||||
entity_t *e = entity_make(physical, visible, aoe);
|
||||
|
||||
physical_t *p = getcomp(e, physical);
|
||||
p->x = position.x;
|
||||
p->y = position.y;
|
||||
|
||||
aoe_t *aoe = getcomp(e, aoe);
|
||||
aoe->type = type;
|
||||
aoe->lifetime = lifetime;
|
||||
aoe->hits = NULL;
|
||||
aoe->hit_count = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
void aoe_destroy(entity_t *e)
|
||||
{
|
||||
aoe_t *aoe = getcomp(e, aoe);
|
||||
free(aoe->hits);
|
||||
}
|
||||
|
||||
entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing)
|
||||
{
|
||||
entity_t *e = aoe_make(type, physical_pos(origin), fix(0));
|
||||
if(e == NULL)
|
||||
return NULL;
|
||||
|
||||
fighter_t *f = getcomp(origin, fighter);
|
||||
|
||||
physical_t *p = getcomp(e, physical);
|
||||
visible_t *v = getcomp(e, visible);
|
||||
aoe_t *aoe = getcomp(e, aoe);
|
||||
|
||||
rect hitbox = { 0 };
|
||||
fixed_t distance = fix(0.625);
|
||||
vec2 dir = fdir(facing);
|
||||
anim_t const *anim = NULL;
|
||||
bool rotate = true;
|
||||
fixed_t lifetime = fix(0);
|
||||
int plane = VERTICAL;
|
||||
|
||||
if(type == AOE_HIT) {
|
||||
anim = &anims_skill_hit;
|
||||
hitbox = (rect){ -fix(4)/16, fix(3)/16, -fix(4)/16, fix(3)/16 };
|
||||
}
|
||||
else if(type == AOE_SLASH) {
|
||||
anim = &anims_skill_swing;
|
||||
hitbox = (rect){ -fix(10)/16, fix(10)/16, -fix(8)/16, 0 };
|
||||
}
|
||||
else if(type == AOE_IMPALE) {
|
||||
anim = &anims_skill_impale;
|
||||
hitbox = (rect){ -fix(4)/16, fix(4)/16, -fix(10)/16, 0 };
|
||||
}
|
||||
else if(type == AOE_SHOCK) {
|
||||
anim = &anims_skill_shock;
|
||||
hitbox = (rect){ -fix(17)/16, fix(18)/16, -fix(17)/16, fix(18)/16 };
|
||||
distance = fix(0);
|
||||
rotate = false;
|
||||
plane = HORIZONTAL;
|
||||
}
|
||||
else if(type == AOE_JUDGEMENT) {
|
||||
anim = &anims_skill_judgement;
|
||||
hitbox = (rect){ -fix(10)/16, fix(11)/16, -fix(6)/16, fix(6)/16 };
|
||||
distance = fix(1.5);
|
||||
rotate = false;
|
||||
}
|
||||
else if(type == AOE_BULLET) {
|
||||
anim = &anims_skill_bullet;
|
||||
hitbox = (rect){ -fix(8)/16, fix(8)/16, -fix(7)/16, fix(7)/16 };
|
||||
distance = fix(0.375);
|
||||
lifetime = fix(999.0);
|
||||
}
|
||||
|
||||
p->x += fmul(distance, dir.x);
|
||||
p->y += fmul(distance, dir.y);
|
||||
p->facing = facing;
|
||||
p->hitbox = rotate ? rect_rotate(hitbox, UP, facing) : hitbox;
|
||||
|
||||
v->sprite_plane = plane;
|
||||
|
||||
aoe->lifetime = lifetime ? lifetime : anim_duration(anim);
|
||||
aoe->repeat_delay = 0;
|
||||
aoe->origin = origin;
|
||||
|
||||
if(type == AOE_HIT
|
||||
|| type == AOE_SLASH
|
||||
|| type == AOE_IMPALE
|
||||
|| type == AOE_JUDGEMENT) {
|
||||
aoe->data.generic.strength = f->ATK;
|
||||
aoe->data.generic.dir = facing;
|
||||
}
|
||||
else if(type == AOE_SHOCK) {
|
||||
aoe->data.shock.strength = f->ATK;
|
||||
aoe->data.shock.origin = physical_pos(origin);
|
||||
aoe->repeat_delay = fix(0.1);
|
||||
}
|
||||
else if(type == AOE_BULLET) {
|
||||
aoe->data.bullet.strength = f->ATK;
|
||||
aoe->data.bullet.dir = facing;
|
||||
aoe->data.bullet.v = fix(10.0);
|
||||
aoe->data.bullet.final_v = fix(4.5);
|
||||
aoe->repeat_delay = fix(0.05);
|
||||
}
|
||||
|
||||
visible_set_anim(e, anim, 1);
|
||||
return e;
|
||||
}
|
||||
|
||||
/* Existing record of the area hitting this entity */
|
||||
static aoe_record_t *aoe_record(aoe_t *aoe, entity_t *target)
|
||||
{
|
||||
for(int i = 0; i < aoe->hit_count; i++) {
|
||||
aoe_record_t *r = &aoe->hits[i];
|
||||
if(r->entity == target) return r;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool attack_apply(game_t *game, aoe_t *aoe, entity_t *target)
|
||||
{
|
||||
fighter_t *origin_f = getcomp(aoe->origin, fighter);
|
||||
fighter_t *target_f = getcomp(target, fighter);
|
||||
mechanical_t *target_m = getcomp(target, mechanical);
|
||||
physical_t *target_p = getcomp(target, physical);
|
||||
|
||||
if(!target_f)
|
||||
return false;
|
||||
|
||||
/* No friendly fire */
|
||||
bool origin_is_monster = (origin_f && origin_f->identity != 0);
|
||||
bool target_is_monster = (target_f->identity != 0);
|
||||
if(origin_is_monster == target_is_monster || target_f->HP == 0)
|
||||
return false;
|
||||
|
||||
/* Dash invincibility */
|
||||
if(target_m && mechanical_dashing(target))
|
||||
return false;
|
||||
|
||||
/* Knockback */
|
||||
fixed_t r = (rand() & (fix(0.125)-1)) + fix(0.375);
|
||||
/* Half knockback against players */
|
||||
if(target_f->identity == 0) r /= 2;
|
||||
|
||||
vec2 dir = { 0, 0 };
|
||||
int damage = 0;
|
||||
|
||||
if(aoe->type == AOE_HIT
|
||||
|| aoe->type == AOE_SLASH
|
||||
|| aoe->type == AOE_IMPALE) {
|
||||
dir = fdir(aoe->data.generic.dir);
|
||||
damage = aoe->data.generic.strength;
|
||||
}
|
||||
else if(aoe->type == AOE_SHOCK) {
|
||||
dir.x = target_p->x - aoe->data.shock.origin.x;
|
||||
dir.y = target_p->y - aoe->data.shock.origin.y;
|
||||
dir = fnormalize(dir);
|
||||
damage = aoe->data.shock.strength * 3 / 2;
|
||||
r /= 2;
|
||||
}
|
||||
else if(aoe->type == AOE_JUDGEMENT) {
|
||||
r = fix(0);
|
||||
damage = aoe->data.generic.strength * 5;
|
||||
}
|
||||
else if(aoe->type == AOE_BULLET) {
|
||||
/* TODO: Sideways knockback */
|
||||
damage = aoe->data.bullet.strength * 2;
|
||||
}
|
||||
|
||||
/* Inflict damage */
|
||||
damage = fighter_damage(target, damage);
|
||||
|
||||
/* Apply knockback */
|
||||
if(target_m) {
|
||||
target_m->vdx += fmul(dir.x, fmul(r, KNOCKBACK_SPEED));
|
||||
target_m->vdy += 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 = (vec2){ target_p->x, target_p->y - fix(0.5) };
|
||||
p->damage = damage;
|
||||
p->color = (target_f->identity == 0) ? C_RED : C_WHITE;
|
||||
game_add_particle(game, &p->particle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void aoe_apply(game_t *game, entity_t *entity, entity_t *e)
|
||||
{
|
||||
bool was_hit = false;
|
||||
aoe_t *aoe = getcomp(entity, aoe);
|
||||
aoe_record_t *rec = aoe_record(aoe, e);
|
||||
|
||||
/* Don't hit entities that have been recently hit */
|
||||
if(rec && aoe->repeat_delay == 0) return;
|
||||
if(rec && aoe->lifetime > rec->lifetime - aoe->repeat_delay) return;
|
||||
|
||||
if(aoe->type == AOE_HIT
|
||||
|| aoe->type == AOE_SLASH
|
||||
|| aoe->type == AOE_IMPALE
|
||||
|| aoe->type == AOE_SHOCK
|
||||
|| aoe->type == AOE_JUDGEMENT
|
||||
|| aoe->type == AOE_BULLET)
|
||||
was_hit = attack_apply(game, aoe, e);
|
||||
|
||||
if(!was_hit) return;
|
||||
|
||||
/* Update record */
|
||||
if(!rec) {
|
||||
size_t new_size = (aoe->hit_count + 1) * sizeof *aoe->hits;
|
||||
aoe_record_t *new_hits = realloc(aoe->hits, new_size);
|
||||
if(!new_hits) return;
|
||||
|
||||
aoe->hits = new_hits;
|
||||
rec = &new_hits[aoe->hit_count];
|
||||
rec->entity = e;
|
||||
aoe->hit_count++;
|
||||
}
|
||||
|
||||
rec->lifetime = aoe->lifetime;
|
||||
}
|
||||
|
||||
void aoe_update(game_t *game, entity_t *entity, fixed_t dt)
|
||||
{
|
||||
physical_t *p = getcomp(entity, physical);
|
||||
aoe_t *aoe = getcomp(entity, aoe);
|
||||
|
||||
if(aoe->type == AOE_BULLET) {
|
||||
vec2 dir = fdir(aoe->data.bullet.dir);
|
||||
p->x += fmul(fmul(aoe->data.bullet.v, dir.x), dt);
|
||||
p->y += fmul(fmul(aoe->data.bullet.v, dir.y), dt);
|
||||
|
||||
/* Speed update - exponential decline from initial value to final_v */
|
||||
aoe->data.bullet.v += fmul(
|
||||
aoe->data.bullet.final_v - aoe->data.bullet.v, fix(0.001));
|
||||
|
||||
/* Collision of anchor with map destroys the bullet */
|
||||
rect small_box = { 0, 0, 0, 0 };
|
||||
small_box = rect_translate(small_box, physical_pos(entity));
|
||||
|
||||
if(map_collides(&game->map, small_box))
|
||||
aoe->lifetime = 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
//---
|
||||
// aoe: Physical entities tracking areas of effect of skills
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "geometry.h"
|
||||
#include "anim.h"
|
||||
|
||||
|
||||
enum {
|
||||
/* Bare-hand fist/close-contact attack */
|
||||
AOE_HIT,
|
||||
/* Normal attack by a slashing weapon */
|
||||
AOE_SLASH,
|
||||
/* Impaling attack using a slashing weapon */
|
||||
AOE_IMPALE,
|
||||
/* Skills */
|
||||
AOE_SHOCK,
|
||||
AOE_JUDGEMENT,
|
||||
AOE_BULLET,
|
||||
/* Spawn effect */
|
||||
EFFECT_SPAWN,
|
||||
};
|
||||
|
||||
/* Record of when an area hit an entity */
|
||||
typedef struct
|
||||
{
|
||||
/* Entity hit */
|
||||
entity_t *entity;
|
||||
/* Lifetime of the area at the time of the hit */
|
||||
fixed_t lifetime;
|
||||
|
||||
} aoe_record_t;
|
||||
|
||||
/* Extra component for areas of effect */
|
||||
typedef struct
|
||||
{
|
||||
/* Lifetime (s) */
|
||||
fixed_t lifetime;
|
||||
/* Effect repeat delay (seconds; 0 for no repeat) */
|
||||
fixed_t repeat_delay;
|
||||
/* List of entities that were hit */
|
||||
aoe_record_t *hits;
|
||||
int hit_count;
|
||||
/* Entity that caused the area */
|
||||
entity_t *origin;
|
||||
/* Type of effect */
|
||||
uint16_t type;
|
||||
/* Effect data (type-dependent) */
|
||||
union {
|
||||
/* Generic attacks: source and ATK strength */
|
||||
struct { int strength; int dir; } generic;
|
||||
/* EFFECT_ATTACK_SHOCK: origin and ATK strength */
|
||||
struct { int strength; vec2 origin; } shock;
|
||||
/* EFFECT_ATTACK_BULLET: Magic strengh, speed parameters */
|
||||
struct { int strength; int dir; fixed_t v; fixed_t final_v; } bullet;
|
||||
} data;
|
||||
|
||||
} aoe_t;
|
||||
|
||||
/* Create a new area of the specified type. The animation, repeat delay, origin
|
||||
and type-specific data should be set manually after creation. */
|
||||
entity_t *aoe_make(uint16_t type, vec2 position, fixed_t lifetime);
|
||||
|
||||
/* Free resources for an area. */
|
||||
void aoe_destroy(entity_t *aoe);
|
||||
|
||||
/* Create an area for a particular attack; all attributes are initialized. */
|
||||
entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing);
|
||||
|
||||
/* Apply effect of area on entity */
|
||||
struct game;
|
||||
void aoe_apply(struct game *g, entity_t *aoe, entity_t *e);
|
||||
|
||||
/* Regular update, for moving areas (eg. bullet) */
|
||||
void aoe_update(struct game *g, entity_t *aoe, fixed_t dt);
|
|
@ -0,0 +1,69 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/physical.h"
|
||||
#include "comp/visible.h"
|
||||
#include "comp/mechanical.h"
|
||||
#include "comp/fighter.h"
|
||||
#include "aoe.h"
|
||||
|
||||
/* Align up to native pointer size */
|
||||
#define ALIGN(expr) ((((expr) - 1) | (sizeof(void *) - 1)) + 1)
|
||||
|
||||
entity_t *(entity_make)(uint32_t comps)
|
||||
{
|
||||
/* Determine full size of entity; also store offsets of components (we
|
||||
explicitly 4-align them so we don't want to compute it several times) */
|
||||
int offsets[ENTITY_COMP_CAPACITY] = { 0 };
|
||||
size_t size = 0;
|
||||
int n = 0;
|
||||
|
||||
if(comps & ENTITY_COMP_physical) {
|
||||
offsets[n++] = size;
|
||||
size = ALIGN(size + sizeof(physical_t));
|
||||
}
|
||||
if(comps & ENTITY_COMP_visible) {
|
||||
offsets[n++] = size;
|
||||
size = ALIGN(size + sizeof(visible_t));
|
||||
}
|
||||
if(comps & ENTITY_COMP_mechanical) {
|
||||
offsets[n++] = size;
|
||||
size = ALIGN(size + sizeof(mechanical_t));
|
||||
}
|
||||
if(comps & ENTITY_COMP_fighter) {
|
||||
offsets[n++] = size;
|
||||
size = ALIGN(size + sizeof(fighter_t));
|
||||
}
|
||||
if(comps & ENTITY_COMP_aoe) {
|
||||
offsets[n++] = size;
|
||||
size = ALIGN(size + sizeof(aoe_t));
|
||||
}
|
||||
|
||||
/* Allocate all components together */
|
||||
entity_t *e;
|
||||
size_t base_size = sizeof(*e) + n * sizeof(e->data[0]);
|
||||
|
||||
e = calloc(1, base_size + size);
|
||||
if(e == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Initialize component addresses */
|
||||
e->comps = comps;
|
||||
for(int i = 0; i < n; i++)
|
||||
e->data[i] = (void *)e + base_size + offsets[i];
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
void entity_mark_to_delete(entity_t *e)
|
||||
{
|
||||
e->deleted = 1;
|
||||
}
|
||||
|
||||
void entity_destroy(entity_t *e)
|
||||
{
|
||||
if(e->comps & ENTITY_COMP_aoe) {
|
||||
aoe_destroy(e);
|
||||
}
|
||||
|
||||
/* Since there's only one allocation for all components, this is easy */
|
||||
free(e);
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
//---
|
||||
// entity: Varied objects in the game, implemented with components
|
||||
//
|
||||
// This game runs on an Entity-Component system. Entities uniquely identify a
|
||||
// variety of game objects, ranging from players and enemies to skills' areas
|
||||
// of effect, item drops, spawn regions...
|
||||
//
|
||||
// Entities themselves are fairly empty, and most of their properties are
|
||||
// defined through components. For instance, the [physical] component assigns a
|
||||
// in space and a hitbox; the [visible] component gives sprites and rendering
|
||||
// properties; the [mechanical] component provides controlled motion.
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* Deletion flag - deletions are delayed until end of frame to ease
|
||||
management of dangling references */
|
||||
uint32_t deleted: 1;
|
||||
/* Bitmask of components in that entity (see ENTITY_COMP_*) */
|
||||
uint32_t comps: 31;
|
||||
/* Pointer to components; one pointer per non-zero entry in [comps],
|
||||
starting from LSB to MSB */
|
||||
void *data[];
|
||||
|
||||
} entity_t;
|
||||
|
||||
/* Bit field values for each type of component */
|
||||
#define ENTITY_COMP_physical 0x00000001
|
||||
#define ENTITY_COMP_visible 0x00000002
|
||||
#define ENTITY_COMP_mechanical 0x00000004
|
||||
#define ENTITY_COMP_fighter 0x00000008
|
||||
#define ENTITY_COMP_aoe 0x00000010
|
||||
/* Maximum number of components */
|
||||
#define ENTITY_COMP_CAPACITY 31
|
||||
|
||||
/* Get a component from an entity */
|
||||
static inline void *getcomp(entity_t const *e, uint32_t comp)
|
||||
{
|
||||
if(__builtin_expect(!(e->comps & comp), 0))
|
||||
return NULL;
|
||||
|
||||
int i = 0;
|
||||
while(comp) i += (e->comps & (comp >>= 1)) != 0;
|
||||
|
||||
return e->data[i];
|
||||
}
|
||||
/* Write "getcomp(e, physical)" */
|
||||
#define getcomp(e, comp) ((comp ## _t *)getcomp(e, ENTITY_COMP_ ## comp))
|
||||
|
||||
/* Create an entity with a fixed set of components */
|
||||
entity_t *entity_make(uint32_t comp);
|
||||
|
||||
/* Variation of entity_make() with up to 6 variable arguments */
|
||||
#define ENTITY_MAKE_ARGS1(type, ...) \
|
||||
ENTITY_COMP_##type __VA_OPT__(| ENTITY_MAKE_ARGS(__VA_ARGS__))
|
||||
#define ENTITY_MAKE_ARGS2(type, ...) \
|
||||
ENTITY_COMP_##type __VA_OPT__(| ENTITY_MAKE_ARGS1(__VA_ARGS__))
|
||||
#define ENTITY_MAKE_ARGS3(type, ...) \
|
||||
ENTITY_COMP_##type __VA_OPT__(| ENTITY_MAKE_ARGS2(__VA_ARGS__))
|
||||
#define ENTITY_MAKE_ARGS4(type, ...) \
|
||||
ENTITY_COMP_##type __VA_OPT__(| ENTITY_MAKE_ARGS3(__VA_ARGS__))
|
||||
#define ENTITY_MAKE_ARGS5(type, ...) \
|
||||
ENTITY_COMP_##type __VA_OPT__(| ENTITY_MAKE_ARGS4(__VA_ARGS__))
|
||||
#define ENTITY_MAKE_ARGS6(type, ...) \
|
||||
ENTITY_COMP_##type __VA_OPT__(| ENTITY_MAKE_ARGS5(__VA_ARGS__))
|
||||
#define entity_make(...) \
|
||||
entity_make(ENTITY_MAKE_ARGS6(__VA_ARGS__))
|
||||
|
||||
/* Mark an entity for deletion at the next collection step */
|
||||
void entity_mark_to_delete(entity_t *e);
|
||||
|
||||
/* Destroy an entity */
|
||||
void entity_destroy(entity_t *e);
|
||||
|
||||
// Useful types when finding/filtering/sorting entities
|
||||
|
||||
/* Propositional predicate on entities; used for counting and filtering */
|
||||
typedef bool entity_predicate_t(entity_t const *e);
|
||||
|
||||
/* Measure; used for filtering-sorting */
|
||||
typedef int entity_measure_t(entity_t const *e);
|
|
@ -0,0 +1,33 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/fighter.h"
|
||||
#include "comp/visible.h"
|
||||
#include "enemies.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
int fighter_damage(entity_t *e, int base_damage)
|
||||
{
|
||||
fighter_t *f = getcomp(e, fighter);
|
||||
|
||||
if(f->HP == 0) return 0;
|
||||
|
||||
base_damage -= f->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(f->HP < damage) f->HP = 0;
|
||||
else f->HP -= damage;
|
||||
|
||||
if(f->identity > 0) {
|
||||
if(f->HP == 0)
|
||||
visible_set_anim(e, enemies[f->identity]->anim_death, 4);
|
||||
else
|
||||
visible_set_anim(e, enemies[f->identity]->anim_hit, 3);
|
||||
}
|
||||
else {
|
||||
visible_set_anim(e, &anims_player_Hit, 3);
|
||||
}
|
||||
|
||||
return damage;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
//---
|
||||
// fighter: Component for entities that participate in fights
|
||||
//
|
||||
// This component gives combat statistics and leveling properties to entities.
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "comp/entity.h"
|
||||
#include "fixed.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* Current attack's effect area */
|
||||
entity_t *current_attack;
|
||||
/* Whether attack follows movement */
|
||||
uint8_t attack_follows_movement;
|
||||
/* Fighter ID (0 for player) */
|
||||
uint8_t identity;
|
||||
/* Combat statistics */
|
||||
uint16_t HP, ATK, DEF, HP_max;
|
||||
|
||||
/* Number of hits in main attack's combo (TODO: Delegate to weapon?) */
|
||||
uint8_t combo_length;
|
||||
/* Next hit to be dealt */
|
||||
uint8_t combo_next;
|
||||
/* Delay until ideal time to start next hit */
|
||||
fixed_t combo_delay;
|
||||
/* Skill and item cooldowns (F2..F5).The "total" field is updated when
|
||||
switching skills/items. The base field is the dynamic value. */
|
||||
fixed_t actions_cooldown_total[5];
|
||||
fixed_t actions_cooldown[5];
|
||||
|
||||
} fighter_t;
|
||||
|
||||
/* Damage entity for that amount of raw strength. Returns actual damage after
|
||||
DES is subtracted, and randomization. */
|
||||
int fighter_damage(entity_t *e, int base_damage);
|
|
@ -0,0 +1,132 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/mechanical.h"
|
||||
#include "comp/physical.h"
|
||||
#include "comp/fighter.h"
|
||||
|
||||
// Movement functions
|
||||
|
||||
void mechanical_move(entity_t *e, vec2 direction, fixed_t dt, map_t const *map)
|
||||
{
|
||||
physical_t *p = getcomp(e, physical);
|
||||
mechanical_t *m = getcomp(e, mechanical);
|
||||
mechanical_limits_t const *limits = m->limits;
|
||||
|
||||
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, with two components: dash motion and walk motion */
|
||||
vec2 target = { 0, 0 };
|
||||
|
||||
if(m->dash > 0) {
|
||||
/* The dash speed is set to one direction and cannot changed */
|
||||
vec2 dir = fdir(m->dash_facing);
|
||||
target.x += fmul(limits->dash_speed, dir.x);
|
||||
target.y += fmul(limits->dash_speed, dir.y);
|
||||
}
|
||||
if(facing >= 0) {
|
||||
/* Walking speed can be directed anywhere, anytime */
|
||||
target.x += fmul(limits->max_speed, direction.x);
|
||||
target.y += fmul(limits->max_speed, direction.y);
|
||||
}
|
||||
|
||||
/* Friction from environment */
|
||||
fixed_t friction_x = fmul(fmul(-target.x, limits->friction), dt);
|
||||
fixed_t friction_y = fmul(fmul(-target.y, limits->friction), dt);
|
||||
|
||||
/* Get there exponentially fast */
|
||||
m->vx = target.x + friction_x + m->vdx;
|
||||
m->vy = target.y + friction_y + m->vdy;
|
||||
|
||||
/* Round very small speeds (smaller than 1/256 tiles/s) to 0 */
|
||||
if(m->vx >= -256 && m->vx <= 255) m->vx = 0;
|
||||
if(m->vy >= -256 && m->vy <= 255) m->vy = 0;
|
||||
|
||||
/* Decrement dash duration or dash cooldown */
|
||||
if(m->dash > 0) {
|
||||
m->dash -= dt;
|
||||
if(m->dash <= 0) m->dash = -limits->dash_cooldown;
|
||||
}
|
||||
else if(m->dash < 0) {
|
||||
m->dash += dt;
|
||||
if(m->dash >= 0) m->dash = 0;
|
||||
}
|
||||
|
||||
if(facing >= 0)
|
||||
p->facing = facing;
|
||||
|
||||
fixed_t new_x = p->x + fmul(m->vx, dt);
|
||||
fixed_t new_y = p->y + fmul(m->vy, dt);
|
||||
rect new_hitbox = rect_translate(p->hitbox, (vec2){ new_x, new_y });
|
||||
|
||||
// TODO ECS: New collision/ejection system based on teleports
|
||||
if(!map_collides(map, new_hitbox)) {
|
||||
fighter_t *f = getcomp(e, fighter);
|
||||
if(f && f->current_attack && f->attack_follows_movement) {
|
||||
physical_t *attack = getcomp(f->current_attack, physical);
|
||||
attack->x += (new_x - p->x);
|
||||
attack->y += (new_y - p->y);
|
||||
}
|
||||
|
||||
p->x = new_x;
|
||||
p->y = new_y;
|
||||
}
|
||||
|
||||
/* TODO: Without acceleration, the movement model is broken */
|
||||
m->vdx = fmul(m->vdx, fix(0.8));
|
||||
m->vdy = fmul(m->vdy, fix(0.8));
|
||||
}
|
||||
|
||||
void mechanical_move4(entity_t *e, int direction, fixed_t dt, map_t const *map)
|
||||
{
|
||||
return mechanical_move(e, fdir(direction), dt, map);
|
||||
}
|
||||
|
||||
void mechanical_dash(entity_t *e, int direction)
|
||||
{
|
||||
mechanical_t *m = getcomp(e, mechanical);
|
||||
if(m->dash != 0)
|
||||
return;
|
||||
|
||||
m->dash = m->limits->dash_duration;
|
||||
m->dash_facing = direction;
|
||||
}
|
||||
|
||||
// Information functions
|
||||
|
||||
bool mechanical_moving(entity_t const *e)
|
||||
{
|
||||
mechanical_t *m = getcomp(e, mechanical);
|
||||
return (m->vx != 0) || (m->vy != 0);
|
||||
}
|
||||
|
||||
bool mechanical_dashing(entity_t const *e)
|
||||
{
|
||||
physical_t *p = getcomp(e, physical);
|
||||
mechanical_t *m = getcomp(e, mechanical);
|
||||
mechanical_limits_t const *limits = m->limits;
|
||||
|
||||
/* True only if the direction of the dash movement is preserved */
|
||||
if(p->facing != m->dash_facing)
|
||||
return false;
|
||||
|
||||
/* True during initial propulsion */
|
||||
if(m->dash > 0)
|
||||
return true;
|
||||
|
||||
/* Also true as long as 1.5x over-speed is maintained */
|
||||
fixed_t cur_v2 = fmul(m->vx, m->vx) + fmul(m->vy, m->vy);
|
||||
fixed_t max_v2 = fmul(limits->max_speed, limits->max_speed);
|
||||
|
||||
if(m->dash < 0 && 2 * cur_v2 > 3 * max_v2)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
//---
|
||||
// mechanical: Component for objects with continuous motion
|
||||
//
|
||||
// Mechanical entities (which must also be physical) have kinematic properties
|
||||
// that carry over from frame to frame, such as speed. The mechanical component
|
||||
// itself only holds simulation data, and is controlled by another component
|
||||
// (either player input or an AI).
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "comp/entity.h"
|
||||
#include "geometry.h"
|
||||
#include "map.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* Maximum walking speed (m/s) */
|
||||
fixed_t max_speed;
|
||||
/* Friction coefficient */
|
||||
fixed_t friction;
|
||||
|
||||
/* Peak dash speed (m/s) */
|
||||
fixed_t dash_speed;
|
||||
/* Dash duration (s) */
|
||||
fixed_t dash_duration;
|
||||
/* Dash cooldown (s) */
|
||||
fixed_t dash_cooldown;
|
||||
|
||||
} mechanical_limits_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
mechanical_limits_t const *limits;
|
||||
|
||||
/* Position of the mechanical entity is specified in physical.x/y! */
|
||||
|
||||
/* Current speed */
|
||||
fixed_t vx, vy;
|
||||
/* Disruption speed to be integrated next frame (eg. knockback) */
|
||||
fixed_t vdx, vdy;
|
||||
/* Dash time remaining (if positive) or cooldown remaining (if negative) */
|
||||
fixed_t dash;
|
||||
/* Dash direction */
|
||||
uint8_t dash_facing;
|
||||
|
||||
} mechanical_t;
|
||||
|
||||
// Movement functions
|
||||
|
||||
/* Update movement for a 4-directional control (stand still if direction = -1).
|
||||
Both the [mechanical] and [physical] components are affected. */
|
||||
void mechanical_move4(entity_t *e, int direction, fixed_t dt,map_t const *map);
|
||||
|
||||
/* Update movement for a full-directional control (for AIs). */
|
||||
void mechanical_move(entity_t *e, vec2 direction, fixed_t dt,map_t const *map);
|
||||
|
||||
/* Start dashing in the specified direction */
|
||||
void mechanical_dash(entity_t *e, int direction);
|
||||
|
||||
// Information functions
|
||||
|
||||
/* Check if the entity is currently moving */
|
||||
bool mechanical_moving(entity_t const *e);
|
||||
|
||||
/* Check if the entity is currently dashing */
|
||||
bool mechanical_dashing(entity_t const *e);
|
|
@ -0,0 +1,14 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/physical.h"
|
||||
|
||||
vec2 physical_pos(entity_t const *e)
|
||||
{
|
||||
physical_t *p = getcomp(e, physical);
|
||||
return (vec2){ p->x, p->y };
|
||||
}
|
||||
|
||||
rect physical_abs_hitbox(entity_t const *e)
|
||||
{
|
||||
physical_t *p = getcomp(e, physical);
|
||||
return rect_translate(p->hitbox, (vec2){ p->x, p->y });
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
//---
|
||||
// physical: Component for entities that exist on the map
|
||||
//
|
||||
// This basic component gives a position and size (hitbox) to every object on
|
||||
// the map. It's a requirement for several others, including [visible] and
|
||||
// [mechanical], since the position is very commonly used.
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "comp/entity.h"
|
||||
#include "geometry.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* Current position, in absolute map coordinates */
|
||||
fixed_t x, y;
|
||||
/* Hitbox on the floor, relative to (x,y) */
|
||||
rect hitbox;
|
||||
/* Direction facing */
|
||||
uint8_t facing;
|
||||
|
||||
} physical_t;
|
||||
|
||||
/* Entity position as a vector */
|
||||
vec2 physical_pos(entity_t const *e);
|
||||
|
||||
/* Hitbox offset by position */
|
||||
rect physical_abs_hitbox(entity_t const *e);
|
|
@ -0,0 +1,37 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/physical.h"
|
||||
#include "comp/visible.h"
|
||||
|
||||
void visible_set_anim(entity_t *e, anim_t const *anim, int priority)
|
||||
{
|
||||
visible_t *v = getcomp(e, visible);
|
||||
|
||||
if(priority < v->anim_priority)
|
||||
return;
|
||||
|
||||
int anim_index = 0;
|
||||
physical_t *p = getcomp(e, physical);
|
||||
|
||||
if(p && anim->directions == 2) {
|
||||
anim_index = (p->facing == RIGHT);
|
||||
}
|
||||
else if(p && anim->directions == 4) {
|
||||
anim_index = p->facing;
|
||||
}
|
||||
|
||||
if(anim_in(v->anim.frame, anim, anim_index))
|
||||
return;
|
||||
|
||||
v->anim.frame = anim->start[anim_index];
|
||||
v->anim.elapsed = 0;
|
||||
v->anim_priority = priority;
|
||||
}
|
||||
|
||||
void visible_update(entity_t *e, fixed_t dt)
|
||||
{
|
||||
visible_t *v = getcomp(e, visible);
|
||||
anim_state_update(&v->anim, dt);
|
||||
|
||||
if(v->anim.frame == NULL)
|
||||
v->anim_priority = 0;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
//---
|
||||
// visible: Component for visible entities with a sprite
|
||||
//
|
||||
// Visible entities (which must also be physical in order to have a position)
|
||||
// mostly consist of 2D sprites that either lie flat on the ground or stand
|
||||
// vertically. They also have a visual elevation (z) which only affects the
|
||||
// display, and can drop shadows.
|
||||
//
|
||||
// Below is a schematic diagram of visible objects' geometry.
|
||||
//
|
||||
// |-_
|
||||
// | `-_ z
|
||||
// | `. | y
|
||||
// ^--- :-_ | < Wall |_.-`
|
||||
// z | : `-_ | sprite `-_
|
||||
// | : `: x
|
||||
// v--- :_.-``-:
|
||||
// _.-``-_ :`-_
|
||||
// `-_ `x_ :_.-` < Floor sprite
|
||||
// `-_ _.-``
|
||||
// `
|
||||
//
|
||||
// The "x" marks the entity's anchor, which is the point of the object tied to
|
||||
// its position (which is used for depth ordering among other things). The
|
||||
// anchor is aligned with the sprite's anchor, before optionally adding a z
|
||||
// displacement for elevated visible entities.
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "comp/entity.h"
|
||||
#include "geometry.h"
|
||||
#include "anim.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* Display elevation; has no influence on mechanics */
|
||||
fixed_t z;
|
||||
/* Current sprite */
|
||||
anim_state_t anim;
|
||||
/* Priority of current sprite (used to avoid animation conflicts) */
|
||||
uint8_t anim_priority;
|
||||
/* Sprite plane (either VERTICAL or HORIZONTAL) */
|
||||
uint8_t sprite_plane;
|
||||
/* Size of shadow cast (0 for no shadow; not all values are valid) */
|
||||
uint8_t shadow_size;
|
||||
|
||||
} visible_t;
|
||||
|
||||
/* Set the entity's animation. If the priority is strictly lower than the
|
||||
current animation's priority, this operation is ignored. */
|
||||
void visible_set_anim(entity_t *e, anim_t const *anim, int priority);
|
||||
|
||||
/* Regular update function */
|
||||
void visible_update(entity_t *e, fixed_t dt);
|
|
@ -1,21 +1,26 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/physical.h"
|
||||
#include "comp/visible.h"
|
||||
#include "comp/mechanical.h"
|
||||
#include "comp/fighter.h"
|
||||
#include "enemies.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Declare animations for an enemy */
|
||||
#define ANIMS(name, I, W, H, D) \
|
||||
.anim_idle = { anims_##name##_left_##I, anims_##name##_right_##I }, \
|
||||
.anim_walking = { anims_##name##_left_##W, anims_##name##_right_##W }, \
|
||||
.anim_hit = { anims_##name##_left_##H, anims_##name##_right_##H }, \
|
||||
.anim_death = { anims_##name##_left_##D, anims_##name##_right_##D }
|
||||
.anim_idle = &anims_ ## name ## _ ## I, \
|
||||
.anim_walking = &anims_ ## name ## _ ## W, \
|
||||
.anim_hit = &anims_ ## name ## _ ## H, \
|
||||
.anim_death = &anims_ ## name ## _ ## D
|
||||
|
||||
static enemy_t const slime = {
|
||||
.name = "Slime",
|
||||
ANIMS(slime, Idle, Walking, Hit, Death),
|
||||
.hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 },
|
||||
.sprite = (rect){ -fix(5)/16, fix(5)/16, -fix(4)/16, fix(3)/16 },
|
||||
.movement_params = {
|
||||
.limits = {
|
||||
.max_speed = fix(1),
|
||||
.propulsion = fix(12),
|
||||
.friction = fix(0.6),
|
||||
},
|
||||
.HP = {
|
||||
.base = fix(10),
|
||||
|
@ -32,6 +37,8 @@ static enemy_t const slime = {
|
|||
.growth = fix(1),
|
||||
.affinity = fix(0.5),
|
||||
},
|
||||
.shadow_size = 4,
|
||||
.z = 0,
|
||||
};
|
||||
|
||||
static enemy_t const bat = {
|
||||
|
@ -39,9 +46,9 @@ static enemy_t const bat = {
|
|||
ANIMS(bat, Idle, Idle, Hit, Death),
|
||||
.hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 },
|
||||
.sprite = (rect){ -fix(5)/16, fix(5)/16, -fix(4)/16, fix(3)/16 },
|
||||
.movement_params = {
|
||||
.limits = {
|
||||
.max_speed = fix(1.8),
|
||||
.propulsion = fix(8),
|
||||
.friction = fix(0.8),
|
||||
},
|
||||
.HP = {
|
||||
.base = fix(8),
|
||||
|
@ -58,6 +65,8 @@ static enemy_t const bat = {
|
|||
.growth = fix(2),
|
||||
.affinity = fix(1),
|
||||
},
|
||||
.shadow_size = 4,
|
||||
.z = fix(0.75),
|
||||
};
|
||||
|
||||
enemy_t const * const enemies[] = {
|
||||
|
@ -76,42 +85,49 @@ static int instantiate_stat(enemy_stat_t const *stat, int level)
|
|||
return ffloor(value);
|
||||
}
|
||||
|
||||
entity_t *enemy_spawn(int enemy_id, int level)
|
||||
entity_t *enemy_make(int enemy_id, int level)
|
||||
{
|
||||
if(enemy_id < 0 || (size_t)enemy_id >= sizeof enemies / sizeof *enemies)
|
||||
return NULL;
|
||||
|
||||
entity_t *e = malloc(sizeof *e);
|
||||
if(!e) return NULL;
|
||||
entity_t *e = entity_make(physical, visible, mechanical, fighter);
|
||||
if(e == NULL)
|
||||
return NULL;
|
||||
|
||||
enemy_t const *data = enemies[enemy_id];
|
||||
|
||||
/* These will probably be overridden by the caller */
|
||||
e->movement.x = 0;
|
||||
e->movement.y = 0;
|
||||
e->movement.vx = 0;
|
||||
e->movement.vy = 0;
|
||||
e->movement.facing = LEFT;
|
||||
e->movement.dash = 0;
|
||||
e->movement.dash_facing = LEFT;
|
||||
|
||||
e->movement_params = &data->movement_params;
|
||||
e->anim_priority = 0;
|
||||
entity_set_anim(e, data->anim_idle[0], 1);
|
||||
physical_t *p = getcomp(e, physical);
|
||||
p->x = fix(0);
|
||||
p->y = fix(0);
|
||||
p->hitbox = data->hitbox;
|
||||
p->facing = LEFT;
|
||||
|
||||
e->hitbox = data->hitbox;
|
||||
e->sprite = data->sprite;
|
||||
visible_t *v = getcomp(e, visible);
|
||||
v->z = data->z;
|
||||
v->sprite_plane = VERTICAL;
|
||||
v->shadow_size = data->shadow_size;
|
||||
v->anim_priority = 0;
|
||||
visible_set_anim(e, data->anim_idle, 1);
|
||||
|
||||
e->current_attack = NULL;
|
||||
e->attack_follows_movement = false;
|
||||
mechanical_t *m = getcomp(e, mechanical);
|
||||
m->limits = &data->limits;
|
||||
m->vx = fix(0);
|
||||
m->vy = fix(0);
|
||||
m->dash = fix(0);
|
||||
m->dash_facing = LEFT;
|
||||
|
||||
e->identity = enemy_id;
|
||||
/* Instantiate fighter statistics */
|
||||
fighter_t *f = getcomp(e, fighter);
|
||||
memset(f, 0, sizeof *f);
|
||||
|
||||
e->HP_max = instantiate_stat(&data->HP, level);
|
||||
e->ATK = instantiate_stat(&data->ATK, level);
|
||||
e->DEF = instantiate_stat(&data->DEF, level);
|
||||
|
||||
e->HP = e->HP_max;
|
||||
f->identity = enemy_id;
|
||||
f->HP_max = instantiate_stat(&data->HP, level);
|
||||
f->ATK = instantiate_stat(&data->ATK, level);
|
||||
f->DEF = instantiate_stat(&data->DEF, level);
|
||||
f->HP = f->HP_max;
|
||||
f->combo_length = 1;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "comp/entity.h"
|
||||
#include "comp/mechanical.h"
|
||||
#include "geometry.h"
|
||||
#include "anim.h"
|
||||
#include "entities.h"
|
||||
|
||||
/* enemy_stat_t: Statistic base and growth */
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
/* Base value at level 1 */
|
||||
fixed_t base;
|
||||
/* Initial per-level growth */
|
||||
|
@ -20,19 +22,24 @@ typedef struct {
|
|||
} enemy_stat_t;
|
||||
|
||||
/* enemy_t: Static enemy information */
|
||||
typedef struct {
|
||||
/* Enemy name (showed in wave information) */
|
||||
typedef struct
|
||||
{
|
||||
/* Enemy name (shown in wave information) */
|
||||
char const *name;
|
||||
/* Map hitbox (represents ground size, collides with walls) */
|
||||
rect hitbox;
|
||||
/* Sprite hitbox (interacts with attacks and effect areas) */
|
||||
rect sprite;
|
||||
/* Idle animation, death animation, damage animation */
|
||||
anim_frame_t *anim_idle[2], *anim_walking[2], *anim_hit[2], *anim_death[2];
|
||||
anim_t *anim_idle, *anim_walking, *anim_hit, *anim_death;
|
||||
/* Movement parameters */
|
||||
entity_movement_params_t movement_params;
|
||||
mechanical_limits_t limits;
|
||||
/* Statistics model */
|
||||
enemy_stat_t HP, ATK, DEF;
|
||||
/* Shadow size */
|
||||
uint8_t shadow_size;
|
||||
/* Rendering elevation */
|
||||
fixed_t z;
|
||||
|
||||
} enemy_t;
|
||||
|
||||
|
@ -47,4 +54,4 @@ enum {
|
|||
extern enemy_t const * const enemies[];
|
||||
|
||||
/* Create a new enemy of the given type. */
|
||||
entity_t *enemy_spawn(int enemy_id, int level);
|
||||
entity_t *enemy_make(int enemy_id, int level);
|
||||
|
|
404
src/entities.c
404
src/entities.c
|
@ -1,404 +0,0 @@
|
|||
#include "entities.h"
|
||||
#include "game.h"
|
||||
#include "enemies.h"
|
||||
#include <stdlib.h>
|
||||
#include <gint/defs/util.h>
|
||||
|
||||
//---
|
||||
// Entities
|
||||
//---
|
||||
|
||||
vec2 entity_pos(entity_t const *e)
|
||||
{
|
||||
return (vec2){ e->movement.x, e->movement.y };
|
||||
}
|
||||
|
||||
rect entity_hitbox(entity_t const *e)
|
||||
{
|
||||
return rect_translate(e->hitbox, entity_pos(e));
|
||||
}
|
||||
|
||||
rect 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, vec2 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 */
|
||||
vec2 target = { 0, 0 };
|
||||
|
||||
/* The dash speed is set to one direction and cannot changed */
|
||||
if(next.dash > 0) {
|
||||
vec2 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);
|
||||
}
|
||||
|
||||
bool entity_moving(entity_t const *e)
|
||||
{
|
||||
return (e->movement.vx != 0) || (e->movement.vy != 0);
|
||||
}
|
||||
|
||||
void entity_set_normal_anim(entity_t *e, anim_frame_t *frame, int priority)
|
||||
{
|
||||
if(priority < e->anim_priority) return;
|
||||
|
||||
e->anim.frame = frame;
|
||||
e->anim.elapsed = 0;
|
||||
e->anim_priority = priority;
|
||||
}
|
||||
|
||||
void entity_set_directional_anim(entity_t *e, anim_frame_t *frame_dirs[],
|
||||
int priority)
|
||||
{
|
||||
if(e->movement.facing >= 4) return;
|
||||
entity_set_normal_anim(e, frame_dirs[e->movement.facing], priority);
|
||||
}
|
||||
|
||||
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], 4);
|
||||
else
|
||||
entity_set_anim(e, enemies[e->identity]->anim_hit[index], 3);
|
||||
}
|
||||
else {
|
||||
entity_set_anim(e, anims_player_Hit[e->movement.facing], 3);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
rect hitbox = { 0 };
|
||||
fixed_t distance = fix(0.625);
|
||||
vec2 dir = fdir(facing);
|
||||
vec2 anchor = rect_center(entity_sprite(e));
|
||||
anim_frame_t *anim = NULL;
|
||||
bool rotate = true;
|
||||
fixed_t lifetime = fix(0);
|
||||
|
||||
if(type == EFFECT_ATTACK_HIT) {
|
||||
anim = anims_skill_hit;
|
||||
hitbox = (rect){ -fix(4)/16, fix(3)/16, -fix(4)/16, fix(3)/16 };
|
||||
}
|
||||
else if(type == EFFECT_ATTACK_SLASH) {
|
||||
anim = anims_skill_swing[facing];
|
||||
hitbox = (rect){ -fix(10)/16, fix(10)/16, -fix(8)/16, 0 };
|
||||
if(facing == UP || facing == DOWN)
|
||||
anchor = entity_pos(e);
|
||||
}
|
||||
else if(type == EFFECT_ATTACK_IMPALE) {
|
||||
anim = anims_skill_impale[facing];
|
||||
hitbox = (rect){ -fix(4)/16, fix(4)/16, -fix(10)/16, 0 };
|
||||
if(facing == UP || facing == DOWN)
|
||||
anchor = entity_pos(e);
|
||||
}
|
||||
else if(type == EFFECT_ATTACK_SHOCK) {
|
||||
anim = anims_skill_shock;
|
||||
hitbox = (rect){ -fix(17)/16, fix(18)/16, -fix(17)/16, fix(18)/16 };
|
||||
anchor = entity_pos(e);
|
||||
distance = fix(0);
|
||||
rotate = false;
|
||||
}
|
||||
else if(type == EFFECT_ATTACK_JUDGEMENT) {
|
||||
anim = anims_skill_judgement;
|
||||
hitbox = (rect){ -fix(10)/16, fix(11)/16, -fix(6)/16, fix(6)/16 };
|
||||
anchor = entity_pos(e);
|
||||
distance = fix(1.5);
|
||||
rotate = false;
|
||||
}
|
||||
else if(type == EFFECT_ATTACK_BULLET) {
|
||||
anim = anims_skill_bullet[facing];
|
||||
hitbox = (rect){ -fix(8)/16, fix(8)/16, -fix(7)/16, fix(7)/16 };
|
||||
distance = fix(0.375);
|
||||
anchor = entity_pos(e);
|
||||
lifetime = fix(999.0);
|
||||
}
|
||||
|
||||
area->sprite = rotate ? rect_rotate(hitbox, UP, facing) : hitbox;
|
||||
area->anchor = anchor;
|
||||
area->anchor.x += fmul(distance, dir.x);
|
||||
area->anchor.y += fmul(distance, dir.y);
|
||||
area->lifetime = lifetime ? lifetime : anim_duration(anim);
|
||||
area->repeat_delay = 0;
|
||||
area->origin = e;
|
||||
|
||||
if(type == EFFECT_ATTACK_HIT
|
||||
|| type == EFFECT_ATTACK_SLASH
|
||||
|| type == EFFECT_ATTACK_IMPALE
|
||||
|| type == EFFECT_ATTACK_JUDGEMENT) {
|
||||
area->data.generic.strength = e->ATK;
|
||||
area->data.generic.dir = facing;
|
||||
}
|
||||
else if(type == EFFECT_ATTACK_SHOCK) {
|
||||
area->data.shock.strength = e->ATK;
|
||||
area->data.shock.origin = entity_pos(e);
|
||||
area->repeat_delay = fix(0.1);
|
||||
}
|
||||
else if(type == EFFECT_ATTACK_BULLET) {
|
||||
area->data.bullet.strength = e->ATK;
|
||||
area->data.bullet.dir = facing;
|
||||
area->data.bullet.v = fix(10.0);
|
||||
area->data.bullet.final_v = fix(4.5);
|
||||
area->repeat_delay = fix(0.05);
|
||||
}
|
||||
|
||||
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 attack_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;
|
||||
|
||||
/* Knockback */
|
||||
fixed_t r = (rand() & (fix(0.25)-1)) + fix(0.875);
|
||||
/* Half knockback against players */
|
||||
if(e->identity == 0) r /= 2;
|
||||
|
||||
vec2 dir = { 0, 0 };
|
||||
int damage = 0;
|
||||
|
||||
if(ea->type == EFFECT_ATTACK_HIT
|
||||
|| ea->type == EFFECT_ATTACK_SLASH
|
||||
|| ea->type == EFFECT_ATTACK_IMPALE) {
|
||||
dir = fdir(ea->data.generic.dir);
|
||||
damage = ea->data.generic.strength;
|
||||
}
|
||||
else if(ea->type == EFFECT_ATTACK_SHOCK) {
|
||||
dir.x = e->movement.x - ea->data.shock.origin.x;
|
||||
dir.y = e->movement.y - ea->data.shock.origin.y;
|
||||
dir = fnormalize(dir);
|
||||
damage = ea->data.shock.strength * 3 / 2;
|
||||
r /= 2;
|
||||
}
|
||||
else if(ea->type == EFFECT_ATTACK_JUDGEMENT) {
|
||||
r = fix(0);
|
||||
damage = ea->data.generic.strength * 5;
|
||||
}
|
||||
else if(ea->type == EFFECT_ATTACK_BULLET) {
|
||||
/* TODO: Sideways knockback */
|
||||
damage = ea->data.bullet.strength * 2;
|
||||
}
|
||||
|
||||
/* Inflict damage */
|
||||
damage = entity_damage(e, damage);
|
||||
|
||||
/* Apply knockback */
|
||||
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 = (vec2){ 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
|
||||
|| ea->type == EFFECT_ATTACK_SHOCK
|
||||
|| ea->type == EFFECT_ATTACK_JUDGEMENT
|
||||
|| ea->type == EFFECT_ATTACK_BULLET)
|
||||
was_hit = attack_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;
|
||||
}
|
||||
|
||||
bool effect_area_is_background(effect_area_t const *ea)
|
||||
{
|
||||
return ea->type == EFFECT_ATTACK_SHOCK;
|
||||
}
|
||||
|
||||
void effect_area_update(game_t *game, effect_area_t *ea, fixed_t dt)
|
||||
{
|
||||
if(ea->type == EFFECT_ATTACK_BULLET) {
|
||||
vec2 dir = fdir(ea->data.bullet.dir);
|
||||
ea->anchor.x += fmul(fmul(ea->data.bullet.v, dir.x), dt);
|
||||
ea->anchor.y += fmul(fmul(ea->data.bullet.v, dir.y), dt);
|
||||
|
||||
/* Speed update - exponential decline from initial value to final_v */
|
||||
ea->data.bullet.v += fmul(ea->data.bullet.final_v - ea->data.bullet.v,
|
||||
fix(0.001));
|
||||
|
||||
/* Collision of anchor with map destroys the bullet */
|
||||
rect small_box = { 0, 0, 0, 0 };
|
||||
small_box = rect_translate(small_box, ea->anchor);
|
||||
|
||||
if(map_collides(&game->map, small_box))
|
||||
ea->lifetime = 0;
|
||||
}
|
||||
}
|
204
src/entities.h
204
src/entities.h
|
@ -1,204 +0,0 @@
|
|||
//---
|
||||
// entities: Objects that move around the map
|
||||
//---
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "geometry.h"
|
||||
#include "anim.h"
|
||||
|
||||
struct effect_area;
|
||||
|
||||
//---
|
||||
// Agents
|
||||
//---
|
||||
|
||||
typedef struct {
|
||||
/* Maximum walking speed (m/s) */
|
||||
fixed_t max_speed;
|
||||
/* Rate a which target speed is approached (1/s) */
|
||||
fixed_t propulsion;
|
||||
|
||||
/* Peak dash speed (m/s) */
|
||||
fixed_t dash_speed;
|
||||
/* Dash duration (s) */
|
||||
fixed_t dash_duration;
|
||||
/* Dash cooldown (s) */
|
||||
fixed_t dash_cooldown;
|
||||
|
||||
} entity_movement_params_t;
|
||||
|
||||
typedef struct {
|
||||
/* Current position */
|
||||
fixed_t x, y;
|
||||
/* Current speed */
|
||||
fixed_t vx, vy;
|
||||
/* Dash time remaining (if positive) or cooldown remaining (if negative) */
|
||||
fixed_t dash;
|
||||
/* Direction currently facing */
|
||||
uint8_t facing;
|
||||
/* Dash direction */
|
||||
uint8_t dash_facing;
|
||||
|
||||
} entity_movement_t;
|
||||
|
||||
typedef struct {
|
||||
/* Number of hits in the main combo (TODO: delegate to weapon?) */
|
||||
uint8_t combo_length;
|
||||
/* Next hit to be dealt */
|
||||
uint8_t combo_next;
|
||||
/* Delay until ideal time to start next hit */
|
||||
fixed_t combo_delay;
|
||||
/* Skill and item cooldowns (F2..F5).The "total" field is updated when
|
||||
switching skills/items. The base field is the dynamic value. */
|
||||
fixed_t actions_cooldown_total[5];
|
||||
fixed_t actions_cooldown[5];
|
||||
|
||||
} entity_player_t;
|
||||
|
||||
/* Alive agents with hitboxes and movement control */
|
||||
typedef struct {
|
||||
/* Map hitbox, centered around (movement.x, movement.y) */
|
||||
rect hitbox;
|
||||
/* Sprite hitbox, centered similarly */
|
||||
rect sprite;
|
||||
/* Current cinematic situation */
|
||||
entity_movement_t movement;
|
||||
/* Cinematic parameters */
|
||||
entity_movement_params_t const *movement_params;
|
||||
/* Player-only parameters */
|
||||
entity_player_t const *player;
|
||||
/* Animated image and state */
|
||||
anim_state_t anim;
|
||||
/* Current attack's effect area */
|
||||
struct effect_area *current_attack;
|
||||
/* Animation priority (to improve legibility) */
|
||||
uint8_t anim_priority;
|
||||
/* Whether attack follows movement */
|
||||
uint8_t attack_follows_movement;
|
||||
/* Enemy ID (0 for player) */
|
||||
uint8_t identity;
|
||||
/* Combat statistics */
|
||||
uint16_t HP, ATK, DEF, HP_max;
|
||||
/* Time left until nexth pathfinding run (ms) */
|
||||
// uint8_t pathfind_delay;
|
||||
|
||||
} entity_t;
|
||||
|
||||
/* Entity position */
|
||||
vec2 entity_pos(entity_t const *e);
|
||||
|
||||
/* Entity hitbox, accounting for position */
|
||||
rect entity_hitbox(entity_t const *e);
|
||||
|
||||
/* Entity sprite, accounting for position */
|
||||
rect entity_sprite(entity_t const *e);
|
||||
|
||||
/* Update walk/sprint movement (stand still if direction == -1); this cinematic
|
||||
state can be submitted for collisions and may or may not be accpeted */
|
||||
entity_movement_t entity_move4(entity_t *e, int direction, fixed_t dt);
|
||||
|
||||
/* Update walk/sprint movement in any direction (for IAs) */
|
||||
entity_movement_t entity_move(entity_t *e, vec2 direction, fixed_t dt);
|
||||
|
||||
/* Check if the entity is currently moving */
|
||||
bool entity_moving(entity_t const *e);
|
||||
|
||||
/* Start dashing in the set direction */
|
||||
void entity_dash(entity_t *e, int direction);
|
||||
|
||||
/* Check if the entity is currently dashing */
|
||||
bool entity_dashing(entity_t const *e);
|
||||
|
||||
/* Set entity animation */
|
||||
void entity_set_normal_anim(entity_t *e, anim_frame_t *frame, int priority);
|
||||
void entity_set_directional_anim(entity_t *e, anim_frame_t *frame_dirs[],
|
||||
int priority);
|
||||
|
||||
#define entity_set_anim(e, frame, priority) _Generic((frame), \
|
||||
anim_frame_t *: entity_set_normal_anim(e, (void *)frame, priority), \
|
||||
anim_frame_t **: entity_set_directional_anim(e, (void *)frame, priority))
|
||||
|
||||
/* Damage entity for that amount of raw strength. Returns actual damage after
|
||||
DES is subtracted, and randomization. */
|
||||
int entity_damage(entity_t *e, int base_damage);
|
||||
|
||||
//---
|
||||
// Effect areas
|
||||
//---
|
||||
|
||||
enum {
|
||||
/* Bare-hand fist/close-contact attack */
|
||||
EFFECT_ATTACK_HIT,
|
||||
/* Normal attack by a slashing weapon */
|
||||
EFFECT_ATTACK_SLASH,
|
||||
/* Impaling attack using a slashing weapon */
|
||||
EFFECT_ATTACK_IMPALE,
|
||||
/* Skills */
|
||||
EFFECT_ATTACK_SHOCK,
|
||||
EFFECT_ATTACK_JUDGEMENT,
|
||||
EFFECT_ATTACK_BULLET,
|
||||
/* Monster spawning */
|
||||
EFFECT_SPAWN,
|
||||
};
|
||||
|
||||
/* Record of when an effect area hit an entity */
|
||||
typedef struct {
|
||||
/* Entity hit */
|
||||
entity_t *entity;
|
||||
/* Lifetime of the area at that time */
|
||||
fixed_t lifetime;
|
||||
|
||||
} effect_area_record_t;
|
||||
|
||||
/* Effect area */
|
||||
typedef struct effect_area {
|
||||
/* Area sprite (used as a hitbox) */
|
||||
rect sprite;
|
||||
/* Position of center */
|
||||
vec2 anchor;
|
||||
/* Lifetime (s) */
|
||||
fixed_t lifetime;
|
||||
/* Animated image and state */
|
||||
anim_state_t anim;
|
||||
/* Effect repeat delay (s; 0 for no repeat) */
|
||||
fixed_t repeat_delay;
|
||||
/* List of entities that were hit */
|
||||
effect_area_record_t *hits;
|
||||
int hit_count;
|
||||
/* Entity that caused the area */
|
||||
entity_t *origin;
|
||||
/* Type of effect */
|
||||
uint16_t type;
|
||||
/* Effect data (type-dependent) */
|
||||
union {
|
||||
/* Generic attacks: source and ATK strength */
|
||||
struct { int strength; int dir; } generic;
|
||||
/* EFFECT_ATTACK_SHOCK: origin and ATK strength */
|
||||
struct { int strength; vec2 origin; } shock;
|
||||
/* EFFECT_ATTACK_BULLET: Magic strengh, speed parameters */
|
||||
struct { int strength; int dir; fixed_t v; fixed_t final_v; } bullet;
|
||||
} data;
|
||||
|
||||
} effect_area_t;
|
||||
|
||||
/* Create a new effect area of the specified type.
|
||||
* sprite, anchor, lifetime, anim, repeat_delay, and data should be set. */
|
||||
effect_area_t *effect_area_new(uint16_t type);
|
||||
|
||||
/* Create an effect area for a particular attack
|
||||
All additional parameters are set by this function. */
|
||||
effect_area_t *effect_area_new_attack(uint16_t type, entity_t *e, int facing);
|
||||
|
||||
/* Set area animation */
|
||||
void effect_area_set_anim(effect_area_t *ea, anim_frame_t *frame);
|
||||
|
||||
/* Apply effect of area on entity */
|
||||
struct game;
|
||||
void effect_area_apply(struct game *g, effect_area_t *ea, entity_t *e);
|
||||
|
||||
/* Whether the area should be rendered in background (floor level) */
|
||||
bool effect_area_is_background(effect_area_t const *ea);
|
||||
|
||||
/* Regular update, for moving areas (eg. bullet) */
|
||||
void effect_area_update(struct game *g, effect_area_t *ea, fixed_t dt);
|
250
src/game.c
250
src/game.c
|
@ -2,6 +2,11 @@
|
|||
#include "util.h"
|
||||
#include "enemies.h"
|
||||
|
||||
#include "comp/fighter.h"
|
||||
#include "comp/physical.h"
|
||||
#include "comp/visible.h"
|
||||
#include "aoe.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
bool game_load(game_t *g, level_t *level)
|
||||
|
@ -19,7 +24,7 @@ bool game_load(game_t *g, level_t *level)
|
|||
|
||||
for(int y = 0; y < m->height; y++)
|
||||
for(int x = 0; x < m->width; x++) {
|
||||
struct tile *t = map_tile(m, x, y);
|
||||
tile_t *t = map_tile(m, x, y);
|
||||
t->base = level->map->tiles[level->map->width * y + x].base;
|
||||
t->decor = level->map->tiles[level->map->width * y + x].decor;
|
||||
t->solid = (t->base == 0) || (t->base >= 16);
|
||||
|
@ -33,8 +38,6 @@ bool game_load(game_t *g, level_t *level)
|
|||
|
||||
g->entities = NULL;
|
||||
g->entity_count = 0;
|
||||
g->effect_areas = NULL;
|
||||
g->effect_area_count = 0;
|
||||
g->particles = NULL;
|
||||
g->particle_count = 0;
|
||||
|
||||
|
@ -53,16 +56,19 @@ void game_unload(game_t *g)
|
|||
free(g->map.tiles);
|
||||
|
||||
for(int i = 0; i < g->entity_count; i++)
|
||||
free(g->entities[i]);
|
||||
entity_destroy(g->entities[i]);
|
||||
free(g->entities);
|
||||
|
||||
for(int i = 0; i < g->effect_area_count; i++)
|
||||
free(g->effect_areas[i]);
|
||||
free(g->effect_areas);
|
||||
g->entities = NULL;
|
||||
g->entity_count = 0;
|
||||
|
||||
// TODo ECS: Remove particles
|
||||
for(int i = 0; i < g->particle_count; i++)
|
||||
free(g->particles[i]);
|
||||
free(g->particles);
|
||||
|
||||
g->particles = NULL;
|
||||
g->particle_count = 0;
|
||||
}
|
||||
|
||||
level_wave_t const *game_current_wave(game_t const *g)
|
||||
|
@ -97,97 +103,79 @@ void game_add_entity(game_t *g, entity_t *entity)
|
|||
g->entity_count++;
|
||||
}
|
||||
|
||||
void game_spawn_entity(game_t *g, entity_t *e, vec2 pos)
|
||||
void game_spawn_entity(game_t *g, entity_t *e)
|
||||
{
|
||||
game_add_entity(g, e);
|
||||
e->movement.x = pos.x;
|
||||
e->movement.y = pos.y;
|
||||
|
||||
if(e->current_attack) return;
|
||||
fighter_t *f = getcomp(e, fighter);
|
||||
if(!f || f->current_attack) return;
|
||||
|
||||
/* Teleport hitbox */
|
||||
rect hitbox = {
|
||||
-fix(8)/16, fix(7)/16, -fix(20)/16, fix(4)/16,
|
||||
};
|
||||
|
||||
effect_area_t *area = effect_area_new(EFFECT_SPAWN);
|
||||
area->sprite = hitbox;
|
||||
area->anchor = entity_pos(e);
|
||||
area->lifetime = anim_duration(anims_skill_teleport);
|
||||
area->repeat_delay = 0;
|
||||
area->origin = e;
|
||||
effect_area_set_anim(area, anims_skill_teleport);
|
||||
game_add_effect_area(g, area);
|
||||
entity_t *spawn = aoe_make(EFFECT_SPAWN, physical_pos(e), fix(0));
|
||||
|
||||
e->current_attack = area;
|
||||
e->attack_follows_movement = true;
|
||||
getcomp(spawn, physical)->hitbox = hitbox;
|
||||
|
||||
aoe_t *aoe = getcomp(spawn, aoe);
|
||||
aoe->lifetime = anim_duration(&anims_skill_teleport);
|
||||
aoe->repeat_delay = 0;
|
||||
aoe->origin = e;
|
||||
|
||||
visible_set_anim(spawn, &anims_skill_teleport, 2);
|
||||
game_add_entity(g, spawn);
|
||||
|
||||
f->current_attack = spawn;
|
||||
f->attack_follows_movement = true;
|
||||
}
|
||||
|
||||
/* Remove an entity and rearrange the array. */
|
||||
static void game_remove_entity(game_t *g, int i)
|
||||
{
|
||||
if(i < 0 || i >= g->entity_count) return;
|
||||
|
||||
entity_t *e = g->entities[i];
|
||||
if(e->current_attack) {
|
||||
/* Kill the effect area */
|
||||
e->current_attack->lifetime = fix(0);
|
||||
}
|
||||
|
||||
free(g->entities[i]);
|
||||
entity_destroy(g->entities[i]);
|
||||
g->entities[i] = g->entities[--g->entity_count];
|
||||
}
|
||||
|
||||
void game_remove_dead_entities(game_t *g)
|
||||
{
|
||||
int i = 0;
|
||||
while(i < g->entity_count) {
|
||||
for(int i = 0; i < g->entity_count; i++) {
|
||||
entity_t *e = g->entities[i];
|
||||
|
||||
if(e->HP == 0 && !e->anim.frame && e->identity != 0)
|
||||
game_remove_entity(g, i);
|
||||
else i++;
|
||||
}
|
||||
}
|
||||
/* First remove dead fighters */
|
||||
fighter_t *f = getcomp(e, fighter);
|
||||
visible_t *v = getcomp(e, visible);
|
||||
bool anim_finished = !v || (v->anim.frame == NULL);
|
||||
|
||||
void game_add_effect_area(game_t *g, effect_area_t *ea)
|
||||
{
|
||||
size_t new_size = (g->effect_area_count + 1) * sizeof *g->effect_areas;
|
||||
effect_area_t **new_effect_areas = realloc(g->effect_areas, new_size);
|
||||
if(!new_effect_areas) return;
|
||||
if(f && f->HP == 0 && f->identity != 0 && anim_finished) {
|
||||
/* Disown remaining areas of effect */
|
||||
fighter_t *f = getcomp(e, fighter);
|
||||
if(f->current_attack) {
|
||||
aoe_t *aoe = getcomp(f->current_attack, aoe);
|
||||
aoe->origin = NULL;
|
||||
}
|
||||
entity_mark_to_delete(e);
|
||||
}
|
||||
|
||||
g->effect_areas = new_effect_areas;
|
||||
g->effect_areas[g->effect_area_count] = ea;
|
||||
g->effect_area_count++;
|
||||
}
|
||||
|
||||
/* Remove an effect area and rearrange the array. */
|
||||
static void game_remove_effect_area(game_t *g, int i)
|
||||
{
|
||||
if(i < 0 || i >= g->effect_area_count) return;
|
||||
|
||||
effect_area_t *ea = g->effect_areas[i];
|
||||
if(ea->origin && ea->origin->current_attack == ea) {
|
||||
ea->origin->current_attack = NULL;
|
||||
ea->origin->attack_follows_movement = false;
|
||||
/* Then remove areas of effect with expired lifetime */
|
||||
aoe_t *aoe = getcomp(e, aoe);
|
||||
if(aoe && aoe->lifetime <= 0) {
|
||||
/* Notify origin of area removal */
|
||||
if(aoe->origin) {
|
||||
fighter_t *f = getcomp(aoe->origin, fighter);
|
||||
f->current_attack = NULL;
|
||||
f->attack_follows_movement = false;
|
||||
}
|
||||
entity_mark_to_delete(e);
|
||||
}
|
||||
}
|
||||
|
||||
free(g->effect_areas[i]->hits);
|
||||
free(g->effect_areas[i]);
|
||||
g->effect_areas[i] = g->effect_areas[--g->effect_area_count];
|
||||
|
||||
/* Don't realloc, we'll likely add new areas soon enough and space will be
|
||||
reclaimed as needed at that time. Don't need to add heap work now. */
|
||||
}
|
||||
|
||||
/* Remove all dead effect areas */
|
||||
static void game_remove_dead_effect_areas(game_t *g)
|
||||
{
|
||||
int i = 0;
|
||||
while(i < g->effect_area_count) {
|
||||
effect_area_t *ea = g->effect_areas[i];
|
||||
|
||||
if(ea->lifetime <= 0) game_remove_effect_area(g, i);
|
||||
while(i < g->entity_count) {
|
||||
if(g->entities[i]->deleted)
|
||||
game_remove_entity(g, i);
|
||||
else i++;
|
||||
}
|
||||
}
|
||||
|
@ -215,9 +203,61 @@ static void game_remove_particle(game_t *g, int i)
|
|||
}
|
||||
|
||||
//---
|
||||
// Interacting with game elements
|
||||
// Generic entity functions
|
||||
//---
|
||||
|
||||
int game_count_entities(game_t const *g, entity_predicate_t *predicate)
|
||||
{
|
||||
int total = 0;
|
||||
for(int i = 0; i < g->entity_count; i++)
|
||||
total += (predicate(g->entities[i]) != 0);
|
||||
return total;
|
||||
}
|
||||
|
||||
/* Not re-entrant, but we can deal with that */
|
||||
static entity_measure_t *gse_measure = NULL;
|
||||
static entity_t **gse_entities = NULL;
|
||||
|
||||
static int gse_compare(const void *ptr1, const void *ptr2)
|
||||
{
|
||||
int i1 = *(uint16_t *)ptr1;
|
||||
int i2 = *(uint16_t *)ptr2;
|
||||
return gse_measure(gse_entities[i1]) - gse_measure(gse_entities[i2]);
|
||||
}
|
||||
|
||||
int game_sort_entities(game_t const *g, entity_measure_t *measure,
|
||||
uint16_t **result)
|
||||
{
|
||||
int count = 0;
|
||||
*result = NULL;
|
||||
|
||||
for(int i = 0; i < g->entity_count; i++)
|
||||
count += (measure(g->entities[i]) >= 0);
|
||||
if(count == 0)
|
||||
return 0;
|
||||
|
||||
/* Initialize array with matching entities in storage order */
|
||||
uint16_t *array = malloc(count * sizeof *array);
|
||||
*result = array;
|
||||
if(array == NULL)
|
||||
return -1;
|
||||
|
||||
for(int i=0, j=0; i < g->entity_count; i++) {
|
||||
if(measure(g->entities[i]) >= 0)
|
||||
array[j++] = i;
|
||||
}
|
||||
|
||||
/* Sort and return */
|
||||
gse_measure = measure;
|
||||
gse_entities = g->entities;
|
||||
qsort(array, count, sizeof *array, gse_compare);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// TODO ECS: New collision/ejection system based on teleports
|
||||
|
||||
void game_try_move_entity(game_t *g, entity_t *e,
|
||||
entity_movement_t const * next)
|
||||
{
|
||||
|
@ -249,6 +289,7 @@ void game_try_move_entity(game_t *g, entity_t *e,
|
|||
m->dash = next->dash;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//---
|
||||
// Per-frame update functions
|
||||
|
@ -263,9 +304,12 @@ void game_spawn_enemies(game_t *g)
|
|||
level_wave_spawn_t *s = &wave->enemies[i];
|
||||
if(s->time > g->time_wave) break;
|
||||
|
||||
entity_t *e = enemy_spawn(s->identity, s->level);
|
||||
vec2 pos = { fix(s->x) + fix(0.5), fix(s->y) + fix(0.5) };
|
||||
game_spawn_entity(g, e, pos);
|
||||
entity_t *e = enemy_make(s->identity, s->level);
|
||||
physical_t *p = getcomp(e, physical);
|
||||
p->x = fix(s->x) + fix(0.5);
|
||||
p->y = fix(s->y) + fix(0.5);
|
||||
|
||||
game_spawn_entity(g, e);
|
||||
g->wave_spawned++;
|
||||
}
|
||||
}
|
||||
|
@ -274,37 +318,37 @@ void game_update_animations(game_t *g, fixed_t dt)
|
|||
{
|
||||
for(int i = 0; i < g->entity_count; i++) {
|
||||
entity_t *e = g->entities[i];
|
||||
anim_state_update(&e->anim, dt);
|
||||
|
||||
if(e->anim.frame == NULL)
|
||||
e->anim_priority = 0;
|
||||
}
|
||||
for(int i = 0; i < g->effect_area_count; i++) {
|
||||
effect_area_t *ea = g->effect_areas[i];
|
||||
anim_state_update(&ea->anim, dt);
|
||||
if(getcomp(e, visible))
|
||||
visible_update(e, dt);
|
||||
}
|
||||
}
|
||||
|
||||
void game_update_effect_areas(game_t *g, fixed_t dt)
|
||||
void game_update_aoes(game_t *g, fixed_t dt)
|
||||
{
|
||||
for(int i = 0; i < g->effect_area_count; i++) {
|
||||
effect_area_t *ea = g->effect_areas[i];
|
||||
for(int i = 0; i < g->entity_count; i++) {
|
||||
entity_t *e = g->entities[i];
|
||||
aoe_t *aoe = getcomp(e, aoe);
|
||||
if(aoe == NULL)
|
||||
continue;
|
||||
|
||||
/* Movement and collisions, when relevant */
|
||||
effect_area_update(g, ea, dt);
|
||||
aoe_update(g, e, dt);
|
||||
|
||||
rect hitbox = rect_translate(ea->sprite, ea->anchor);
|
||||
rect hitbox = physical_abs_hitbox(e);
|
||||
|
||||
// TODO ECS: Move collisions in a proper system
|
||||
// TODO ECS: Quadratic collision check is a no-no for high performance
|
||||
for(int i = 0; i < g->entity_count; i++) {
|
||||
entity_t *e = g->entities[i];
|
||||
if(rect_collide(hitbox, entity_sprite(e)))
|
||||
effect_area_apply(g, ea, e);
|
||||
entity_t *target = g->entities[i];
|
||||
physical_t *p = getcomp(target, physical);
|
||||
|
||||
if(p && rect_collide(hitbox, rect_translate(p->hitbox,
|
||||
(vec2){ p->x, p->y })))
|
||||
aoe_apply(g, e, target);
|
||||
}
|
||||
|
||||
ea->lifetime -= dt;
|
||||
aoe->lifetime -= dt;
|
||||
}
|
||||
|
||||
game_remove_dead_effect_areas(g);
|
||||
}
|
||||
|
||||
void game_update_particles(game_t *g, fixed_t dt)
|
||||
|
@ -320,32 +364,18 @@ void game_update_particles(game_t *g, fixed_t dt)
|
|||
/* Spawn dash particles */
|
||||
for(int i = 0; i < g->entity_count; i++) {
|
||||
entity_t *e = g->entities[i];
|
||||
mechanical_t *m = getcomp(e, mechanical);
|
||||
|
||||
if(entity_dashing(e)) {
|
||||
if(m && mechanical_dashing(e)) {
|
||||
particle_dash_t *p = malloc(sizeof *p);
|
||||
p->particle.type = PARTICLE_DASH;
|
||||
p->particle.age = 0;
|
||||
p->particle.pos = entity_pos(e);
|
||||
p->particle.pos = physical_pos(e);
|
||||
game_add_particle(g, &p->particle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Compare the y values of two entities. */
|
||||
static int game_sort_entities_compare(void const *p1, void const *p2)
|
||||
{
|
||||
entity_t const *e1 = *(entity_t const **)p1;
|
||||
entity_t const *e2 = *(entity_t const **)p2;
|
||||
|
||||
return e1->movement.y - e2->movement.y;
|
||||
}
|
||||
|
||||
void game_sort_entities(game_t *g)
|
||||
{
|
||||
heap_sort(g->entities, g->entity_count, sizeof *g->entities,
|
||||
game_sort_entities_compare);
|
||||
}
|
||||
|
||||
/* Compare the y values of two particles. */
|
||||
static int game_sort_particles_compare(void const *v1, void const *v2)
|
||||
{
|
||||
|
|
45
src/game.h
45
src/game.h
|
@ -5,12 +5,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "map.h"
|
||||
#include "entities.h"
|
||||
#include "render.h"
|
||||
#include "level.h"
|
||||
#include "pathfinding.h"
|
||||
#include "particles.h"
|
||||
|
||||
#include "comp/entity.h"
|
||||
|
||||
typedef struct game {
|
||||
/* The map's coordinate system is the primary coordinate system in all of
|
||||
this game's code */
|
||||
|
@ -27,16 +28,13 @@ typedef struct game {
|
|||
int entity_count;
|
||||
/* Player; this must be one of the entities loaded in the game */
|
||||
entity_t *player;
|
||||
/* List of effect areas */
|
||||
effect_area_t **effect_areas;
|
||||
int effect_area_count;
|
||||
/* List of particles */
|
||||
particle_t **particles;
|
||||
int particle_count;
|
||||
/* Field of movement to reach the player (used by most enemy AIs) */
|
||||
pfg_all2one_t paths_to_player;
|
||||
|
||||
/* Level begin played */
|
||||
/* Level being played */
|
||||
level_t *level;
|
||||
/* Current wave, number of enemies spawned in wave, time spent in wave */
|
||||
int wave;
|
||||
|
@ -58,31 +56,34 @@ level_wave_t const *game_current_wave(game_t const *g);
|
|||
void game_next_wave(game_t *g);
|
||||
|
||||
//---
|
||||
// Adding dynamic game elements
|
||||
// Managing dynamic game elements
|
||||
//---
|
||||
|
||||
/* Add an entity to the game (takes ownership; e will be freed). */
|
||||
void game_add_entity(game_t *g, entity_t *e);
|
||||
|
||||
/* Add an effect area to the game (takes ownership; ea will be freed). */
|
||||
void game_add_effect_area(game_t *g, effect_area_t *ea);
|
||||
|
||||
/* Add a particle to the game (takes ownership; p will be freed) */
|
||||
void game_add_particle(game_t *g, particle_t *p);
|
||||
|
||||
/* Like game_add_entity() at a specific position, but with a visual effect */
|
||||
void game_spawn_entity(game_t *g, entity_t *e, vec2 pos);
|
||||
/* Like game_add_entity(), but with a visual effect */
|
||||
void game_spawn_entity(game_t *g, entity_t *e);
|
||||
|
||||
/* Remove dead entities. */
|
||||
void game_remove_dead_entities(game_t *g);
|
||||
|
||||
//---
|
||||
// Interacting with game elements
|
||||
// Generic entity functions
|
||||
//---
|
||||
|
||||
/* Try to move an entity at the specified next-frame movement data. The data is
|
||||
applied if valid (no collisions). Otherwise the entity does not move and
|
||||
only some data is updated. The data is obtained by entity_move() or related
|
||||
functions. */
|
||||
void game_try_move_entity(game_t *g, entity_t *e,
|
||||
entity_movement_t const *next_movement);
|
||||
/* Count entities satisfying the provided predicate. */
|
||||
int game_count_entities(game_t const *g, entity_predicate_t *predicate);
|
||||
|
||||
/* Filter and sort entities. The entities are sorted by increasing measure,
|
||||
with negative measures filtering entities out. Returns an array of entity
|
||||
indices (within [g->entities]) to be freed with free() in [*result], while
|
||||
returning the number of matching entities. If 0, *result is NULL. */
|
||||
int game_sort_entities(game_t const *g, entity_measure_t *measure,
|
||||
uint16_t **result);
|
||||
|
||||
//---
|
||||
// Per-frame update functions
|
||||
|
@ -95,16 +96,10 @@ void game_spawn_enemies(game_t *g);
|
|||
void game_update_animations(game_t *g, fixed_t dt);
|
||||
|
||||
/* Update all effect areas and apply their effects. */
|
||||
void game_update_effect_areas(game_t *g, fixed_t dt);
|
||||
void game_update_aoes(game_t *g, fixed_t dt);
|
||||
|
||||
/* Update all particles and remove the oldest ones. */
|
||||
void game_update_particles(game_t *g, fixed_t dt);
|
||||
|
||||
/* Sort entities by increasing y position (which is rendering order). */
|
||||
void game_sort_entities(game_t *g);
|
||||
|
||||
/* Sort particles by increasing y position */
|
||||
void game_sort_particles(game_t *g);
|
||||
|
||||
/* Remove dead entities. */
|
||||
void game_remove_dead_entities(game_t *g);
|
||||
|
|
311
src/main.c
311
src/main.c
|
@ -1,6 +1,12 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/physical.h"
|
||||
#include "comp/visible.h"
|
||||
#include "comp/mechanical.h"
|
||||
#include "comp/fighter.h"
|
||||
|
||||
#include "anim.h"
|
||||
#include "aoe.h"
|
||||
#include "enemies.h"
|
||||
#include "entities.h"
|
||||
#include "game.h"
|
||||
#include "geometry.h"
|
||||
#include "level.h"
|
||||
|
@ -15,6 +21,7 @@
|
|||
#include <gint/usb-ff-bulk.h>
|
||||
#include <gint/cpu.h>
|
||||
#include <gint/timer.h>
|
||||
#include <gint/kmalloc.h>
|
||||
#include <gint/drivers/r61524.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
@ -38,7 +45,7 @@ int main(void)
|
|||
usb_open(interfaces, GINT_CALL_NULL);
|
||||
|
||||
game_t game = { 0 };
|
||||
map_t *m = &game.map;
|
||||
map_t *map = &game.map;
|
||||
camera_t *c = &game.camera;
|
||||
|
||||
game_load(&game, &lv_1);
|
||||
|
@ -65,48 +72,41 @@ int main(void)
|
|||
// Spawn player
|
||||
//---
|
||||
|
||||
entity_movement_params_t emp_player = {
|
||||
mechanical_limits_t ml_player = {
|
||||
.max_speed = fix(4.5),
|
||||
.propulsion = fix(12),
|
||||
.dash_speed = fix(55),
|
||||
.dash_duration = fix(1) / 32,
|
||||
.friction = fix(0.7),
|
||||
.dash_speed = fix(20),
|
||||
.dash_duration = fix(1) / 8,
|
||||
.dash_cooldown = fix(1),
|
||||
};
|
||||
entity_player_t player_data = {
|
||||
.combo_length = 2,
|
||||
.combo_next = 0,
|
||||
.combo_delay = fix(0),
|
||||
};
|
||||
|
||||
entity_t *player = enemy_spawn(ENEMY_BAT, 1);
|
||||
entity_player_t *playerd = &player_data;
|
||||
player->player = playerd;
|
||||
entity_t *player = enemy_make(ENEMY_BAT, 2);
|
||||
physical_t *player_p = getcomp(player, physical);
|
||||
visible_t *player_v = getcomp(player, visible);
|
||||
mechanical_t *player_m = getcomp(player, mechanical);
|
||||
fighter_t *player_f = getcomp(player, fighter);
|
||||
|
||||
int x=1, y=1;
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
x = rand() % m->width;
|
||||
y = rand() % m->height;
|
||||
player_f->combo_length = 2;
|
||||
player_f->combo_next = 0;
|
||||
player_f->combo_delay = fix(0);
|
||||
player_f->identity = 0;
|
||||
player_f->HP_max += 100;
|
||||
player_f->HP += 100;
|
||||
|
||||
struct tile *t = map_tile(m, x, y);
|
||||
if(t && !t->solid) break;
|
||||
}
|
||||
player->movement.x = fix(x) + fix(0.5);
|
||||
player->movement.y = fix(y) + fix(0.5);
|
||||
player_p->x = fix(9.5);
|
||||
player_p->y = fix(4.5);
|
||||
player_p->hitbox = (rect){ -fix(5)/16, fix(5)/16, -fix(2)/16, fix(4)/16 };
|
||||
|
||||
player_m->limits = &ml_player;
|
||||
|
||||
player_v->z = 0;
|
||||
player_v->shadow_size = 4;
|
||||
|
||||
visible_set_anim(player, &anims_player_Idle, 1);
|
||||
|
||||
game_add_entity(&game, player);
|
||||
game.player = player;
|
||||
|
||||
player->HP_max += 100;
|
||||
player->HP += 100;
|
||||
player->movement_params = &emp_player;
|
||||
player->identity = 0;
|
||||
player->hitbox = (rect){
|
||||
-fix(5)/16, fix(5)/16, -fix(2)/16, fix(4)/16 };
|
||||
player->sprite = (rect){
|
||||
-fix(6)/16, fix(5)/16, -fix(12)/16, fix(4)/16 };
|
||||
|
||||
entity_set_anim(player, anims_player_Idle, 1);
|
||||
|
||||
//---
|
||||
// Main loop
|
||||
//---
|
||||
|
@ -137,6 +137,10 @@ int main(void)
|
|||
|
||||
render_game(&game, debug.show_hitboxes);
|
||||
|
||||
/* kmalloc_arena_t *_uram = kmalloc_get_arena("_uram");
|
||||
kmalloc_gint_stats_t *_uram_stats = kmalloc_get_gint_stats(_uram);
|
||||
dprint(1, 1, C_WHITE, "Memory: %d", _uram_stats->used_memory); */
|
||||
|
||||
/* Developer/tweaking menu */
|
||||
if(debug.show_vars) {
|
||||
uint32_t *vram = (void *)gint_vram;
|
||||
|
@ -149,25 +153,23 @@ int main(void)
|
|||
uint16_t gray = C_RGB(16, 16, 16);
|
||||
|
||||
dprint(3, 40, C_WHITE, "Player speed: %g tiles/s",
|
||||
f2double(emp_player.max_speed));
|
||||
f2double(ml_player.max_speed));
|
||||
dprint(15, 55, gray, "[frac] -/+ [X,0,T]");
|
||||
|
||||
dprint(3, 70, C_WHITE, "Propulsion: %g s^-1",
|
||||
f2double(emp_player.propulsion));
|
||||
dprint(3, 70, C_WHITE, "Friction: %g",
|
||||
f2double(ml_player.friction));
|
||||
dprint(15, 85, gray, "[F<>D] -/+ [log]");
|
||||
dprint(15, 100, C_WHITE, "(Friction: %g)",
|
||||
f2double(fix(1) - emp_player.propulsion / FRAME_RATE));
|
||||
|
||||
dprint(3, 115, C_WHITE, "Dash speed: %g tiles/s",
|
||||
f2double(emp_player.dash_speed));
|
||||
f2double(ml_player.dash_speed));
|
||||
dprint(15, 130, gray, "[(] -/+ [ln]");
|
||||
|
||||
dprint(3, 145, C_WHITE, "Dash duration: %g s",
|
||||
f2double(emp_player.dash_duration));
|
||||
f2double(ml_player.dash_duration));
|
||||
dprint(15, 160, gray, "[)] -/+ [sin]");
|
||||
|
||||
dprint(3, 175, C_WHITE, "Dash cooldown: %g s",
|
||||
f2double(emp_player.dash_cooldown));
|
||||
f2double(ml_player.dash_cooldown));
|
||||
dprint(15, 190, gray, "[,] -/+ [cos]");
|
||||
}
|
||||
|
||||
|
@ -189,9 +191,9 @@ int main(void)
|
|||
dline(j.x, j.y, k.x, k.y, C_RGB(0, 31, 31));
|
||||
}
|
||||
|
||||
vec2 p = entity_pos(player);
|
||||
vec2 p = physical_pos(player);
|
||||
vec2 q = vec_i2f_center((ivec2){ 6, 9 });
|
||||
bool clear = raycast_clear_hitbox(m, p, q, player->hitbox);
|
||||
bool clear = raycast_clear_hitbox(map, p, q, player_p->hitbox);
|
||||
|
||||
ivec2 j = camera_map2screen(c, p);
|
||||
ivec2 k = camera_map2screen(c, q);
|
||||
|
@ -254,10 +256,6 @@ int main(void)
|
|||
prof_enter(perf_simul);
|
||||
|
||||
game_spawn_enemies(&game);
|
||||
|
||||
game_update_animations(&game, dt);
|
||||
|
||||
game_sort_entities(&game);
|
||||
game_sort_particles(&game);
|
||||
|
||||
key_event_t ev;
|
||||
|
@ -282,46 +280,46 @@ int main(void)
|
|||
debug.show_perf ^= 1;
|
||||
|
||||
if(ev.key == KEY_XOT)
|
||||
emp_player.max_speed += fix(1)/8;
|
||||
ml_player.max_speed += fix(1)/8;
|
||||
if(ev.key == KEY_FRAC) {
|
||||
emp_player.max_speed -= fix(1)/8;
|
||||
if(emp_player.max_speed < 0)
|
||||
emp_player.max_speed = 0;
|
||||
ml_player.max_speed -= fix(1)/8;
|
||||
if(ml_player.max_speed < 0)
|
||||
ml_player.max_speed = 0;
|
||||
}
|
||||
|
||||
if(ev.key == KEY_LOG) {
|
||||
emp_player.propulsion += fix(1) / 8;
|
||||
if(emp_player.propulsion > fix(FRAME_RATE))
|
||||
emp_player.propulsion = fix(FRAME_RATE);
|
||||
ml_player.friction += fix(1) / 32;
|
||||
if(ml_player.friction > 1)
|
||||
ml_player.friction = 1;
|
||||
}
|
||||
if(ev.key == KEY_FD) {
|
||||
emp_player.propulsion -= fix(1) / 8;
|
||||
if(emp_player.propulsion <= 0)
|
||||
emp_player.propulsion = 0;
|
||||
ml_player.friction -= fix(1) / 32;
|
||||
if(ml_player.friction <= 0)
|
||||
ml_player.friction = 0;
|
||||
}
|
||||
|
||||
if(ev.key == KEY_LN)
|
||||
emp_player.dash_speed += fix(0.5);
|
||||
ml_player.dash_speed += fix(0.5);
|
||||
if(ev.key == KEY_LEFTP) {
|
||||
emp_player.dash_speed -= fix(0.5);
|
||||
if(emp_player.dash_speed <= 0)
|
||||
emp_player.dash_speed = 0;
|
||||
ml_player.dash_speed -= fix(0.5);
|
||||
if(ml_player.dash_speed <= 0)
|
||||
ml_player.dash_speed = 0;
|
||||
}
|
||||
|
||||
if(ev.key == KEY_SIN)
|
||||
emp_player.dash_duration += fix(1) / 64;
|
||||
ml_player.dash_duration += fix(1) / 64;
|
||||
if(ev.key == KEY_RIGHTP) {
|
||||
emp_player.dash_duration -= fix(1) / 64;
|
||||
if(emp_player.dash_duration <= 0)
|
||||
emp_player.dash_duration = 0;
|
||||
ml_player.dash_duration -= fix(1) / 64;
|
||||
if(ml_player.dash_duration <= 0)
|
||||
ml_player.dash_duration = 0;
|
||||
}
|
||||
|
||||
if(ev.key == KEY_COS)
|
||||
emp_player.dash_cooldown += fix(1) / 8;
|
||||
ml_player.dash_cooldown += fix(1) / 8;
|
||||
if(ev.key == KEY_COMMA) {
|
||||
emp_player.dash_cooldown -= fix(1) / 8;
|
||||
if(emp_player.dash_cooldown <= 0)
|
||||
emp_player.dash_cooldown = 0;
|
||||
ml_player.dash_cooldown -= fix(1) / 8;
|
||||
if(ml_player.dash_cooldown <= 0)
|
||||
ml_player.dash_cooldown = 0;
|
||||
}
|
||||
|
||||
if(ev.key == KEY_PLUS)
|
||||
|
@ -348,7 +346,7 @@ int main(void)
|
|||
#endif
|
||||
|
||||
/* Player movement */
|
||||
if(player->HP > 0) {
|
||||
if(player_f->HP > 0) {
|
||||
int dir = -1;
|
||||
if(keydown(KEY_UP)) dir = UP;
|
||||
if(keydown(KEY_DOWN)) dir = DOWN;
|
||||
|
@ -356,43 +354,43 @@ int main(void)
|
|||
if(keydown(KEY_RIGHT)) dir = RIGHT;
|
||||
|
||||
if(keydown(KEY_F1) && !keydown(KEY_VARS)) {
|
||||
int dash_dir = (dir >= 0) ? dir : player->movement.facing;
|
||||
entity_dash(player, dash_dir);
|
||||
int dash_dir = (dir >= 0) ? dir : player_p->facing;
|
||||
mechanical_dash(player, dash_dir);
|
||||
}
|
||||
entity_movement_t next = entity_move4(player, dir, dt);
|
||||
mechanical_move4(player, dir, dt, map);
|
||||
|
||||
bool set_anim = (player->movement.facing != next.facing) ||
|
||||
((entity_moving(player) == 0) != (dir == -1)) ||
|
||||
(player->anim.frame == NULL);
|
||||
|
||||
game_try_move_entity(&game, player, &next);
|
||||
if(set_anim && dir >= 0)
|
||||
entity_set_anim(player, anims_player_Walking, 1);
|
||||
else if(set_anim && dir < 0)
|
||||
entity_set_anim(player, anims_player_Idle, 1);
|
||||
if(dir >= 0)
|
||||
visible_set_anim(player, &anims_player_Walking, 1);
|
||||
else
|
||||
visible_set_anim(player, &anims_player_Idle, 1);
|
||||
}
|
||||
|
||||
/* Directions to reach the player from anywhere on the grid */
|
||||
pfg_all2one_free(&game.paths_to_player);
|
||||
game.paths_to_player = pfg_bfs(m, vec_f2i(entity_pos(player)));
|
||||
game.paths_to_player = pfg_bfs(map, vec_f2i(physical_pos(player)));
|
||||
|
||||
/* Enemy AI */
|
||||
if(player->HP > 0) for(int i = 0; i < game.entity_count; i++) {
|
||||
if(player_f->HP > 0)
|
||||
for(int i = 0; i < game.entity_count; i++) {
|
||||
entity_t *e = game.entities[i];
|
||||
if(e == player || e->HP == 0) continue;
|
||||
fighter_t *f = getcomp(e, fighter);
|
||||
mechanical_t *m = getcomp(e, mechanical);
|
||||
physical_t *p = getcomp(e, physical);
|
||||
if(!f || !m || f->identity == 0 || f->HP == 0)
|
||||
continue;
|
||||
|
||||
/* Go within 1 block of the player */
|
||||
vec2 direction = { 0, 0 };
|
||||
vec2 pos = entity_pos(e);
|
||||
vec2 pos = physical_pos(e);
|
||||
|
||||
bool in_range = dist2(pos, entity_pos(player)) <= fix(1);
|
||||
bool in_range = dist2(pos, physical_pos(player)) <= fix(1);
|
||||
|
||||
if(!in_range) {
|
||||
pfg_path_t path = pfg_bfs_inwards(&game.paths_to_player,
|
||||
vec_f2i(pos));
|
||||
if(path.points) {
|
||||
direction = pfc_shortcut_one(&path, pos,
|
||||
entity_pos(player), e->hitbox);
|
||||
physical_pos(player), getcomp(e, physical)->hitbox);
|
||||
pfg_path_free(&path);
|
||||
}
|
||||
|
||||
|
@ -402,88 +400,83 @@ int main(void)
|
|||
}
|
||||
}
|
||||
|
||||
entity_movement_t next = entity_move(e, direction, dt);
|
||||
if(direction.x > 0) next.facing = RIGHT;
|
||||
else if(direction.x < 0) next.facing = LEFT;
|
||||
else if(e->movement.x < player->movement.x) next.facing = RIGHT;
|
||||
else next.facing = LEFT;
|
||||
mechanical_move(e, direction, dt, map);
|
||||
if(direction.x > 0) p->facing = RIGHT;
|
||||
else if(direction.x < 0) p->facing = LEFT;
|
||||
else if(p->x < player_p->x) p->facing = RIGHT;
|
||||
else p->facing = LEFT;
|
||||
|
||||
bool will_move = !in_range;
|
||||
bool set_anim = (e->movement.facing != next.facing) ||
|
||||
((entity_moving(e) == 0) != (will_move == false)) ||
|
||||
(e->anim.frame == NULL);
|
||||
if(mechanical_moving(e))
|
||||
visible_set_anim(e, enemies[f->identity]->anim_walking, 1);
|
||||
else
|
||||
visible_set_anim(e, enemies[f->identity]->anim_idle, 1);
|
||||
|
||||
game_try_move_entity(&game, e, &next);
|
||||
int id = e->movement.facing == RIGHT;
|
||||
if(set_anim && will_move)
|
||||
entity_set_anim(e, enemies[e->identity]->anim_walking[id], 1);
|
||||
else if(set_anim && !will_move)
|
||||
entity_set_anim(e, enemies[e->identity]->anim_idle[id], 1);
|
||||
|
||||
if(in_range && !e->current_attack) {
|
||||
if(in_range && !f->current_attack) {
|
||||
/* Enemy attack */
|
||||
int facing = frdir((vec2){
|
||||
player->movement.x - e->movement.x,
|
||||
player->movement.y - e->movement.y });
|
||||
player_p->x - p->x,
|
||||
player_p->y - p->y });
|
||||
|
||||
effect_area_t *area = effect_area_new_attack(EFFECT_ATTACK_HIT,
|
||||
e, facing);
|
||||
game_add_effect_area(&game, area);
|
||||
entity_t *aoe = aoe_make_attack(AOE_HIT, e, facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
e->current_attack = area;
|
||||
e->attack_follows_movement = true;
|
||||
f->current_attack = aoe;
|
||||
f->attack_follows_movement = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Player attack */
|
||||
if(player->HP > 0 && attack && !player->current_attack) {
|
||||
int hit_number=0, effect=EFFECT_ATTACK_SLASH;
|
||||
if(player_f->HP > 0 && attack && !player_f->current_attack) {
|
||||
int hit_number=0, effect=AOE_SLASH;
|
||||
|
||||
/* If hitting within .25s of the previous hit ending, combo! */
|
||||
if(abs(playerd->combo_delay) < fix(0.25))
|
||||
hit_number = playerd->combo_next;
|
||||
playerd->combo_next = (hit_number + 1) % playerd->combo_length;
|
||||
if(abs(player_f->combo_delay) < fix(0.25))
|
||||
hit_number = player_f->combo_next;
|
||||
player_f->combo_next = (hit_number + 1) % player_f->combo_length;
|
||||
|
||||
if(hit_number == 0) effect = EFFECT_ATTACK_SLASH;
|
||||
if(hit_number == 1) effect = EFFECT_ATTACK_IMPALE;
|
||||
if(hit_number == 0) effect = AOE_SLASH;
|
||||
if(hit_number == 1) effect = AOE_IMPALE;
|
||||
entity_t *aoe = aoe_make_attack(effect, player, player_p->facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
effect_area_t *area = effect_area_new_attack(effect, player,
|
||||
player->movement.facing);
|
||||
game_add_effect_area(&game, area);
|
||||
|
||||
entity_set_anim(player, anims_player_Attack, 2);
|
||||
player->current_attack = area;
|
||||
player->attack_follows_movement = true;
|
||||
playerd->combo_delay = area->lifetime;
|
||||
visible_set_anim(player, &anims_player_Attack, 2);
|
||||
player_f->current_attack = aoe;
|
||||
player_f->attack_follows_movement = true;
|
||||
player_f->combo_delay = getcomp(aoe, aoe)->lifetime;
|
||||
}
|
||||
|
||||
/* Player skills */
|
||||
if(player->HP > 0 && keydown(KEY_F2) && !player->current_attack) {
|
||||
effect_area_t *area = effect_area_new_attack(EFFECT_ATTACK_SHOCK,
|
||||
player, player->movement.facing);
|
||||
game_add_effect_area(&game, area);
|
||||
bool can_attack = player_f->HP > 0 && !player_f->current_attack &&
|
||||
!keydown(KEY_VARS);
|
||||
|
||||
entity_set_anim(player, anims_player_Attack, 2);
|
||||
player->current_attack = area;
|
||||
player->attack_follows_movement = true;
|
||||
}
|
||||
if(player->HP > 0 && keydown(KEY_F3) && !player->current_attack) {
|
||||
effect_area_t *area = effect_area_new_attack(
|
||||
EFFECT_ATTACK_JUDGEMENT, player, player->movement.facing);
|
||||
game_add_effect_area(&game, area);
|
||||
if(can_attack && keydown(KEY_F2)) {
|
||||
entity_t *aoe = aoe_make_attack(AOE_SHOCK, player,
|
||||
player_p->facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
entity_set_anim(player, anims_player_Attack, 2);
|
||||
player->current_attack = area;
|
||||
player->attack_follows_movement = false;
|
||||
visible_set_anim(player, &anims_player_Attack, 2);
|
||||
player_f->current_attack = aoe;
|
||||
player_f->attack_follows_movement = true;
|
||||
}
|
||||
if(player->HP > 0 && keydown(KEY_F4) && !player->current_attack) {
|
||||
effect_area_t *area = effect_area_new_attack(
|
||||
EFFECT_ATTACK_BULLET, player, player->movement.facing);
|
||||
game_add_effect_area(&game, area);
|
||||
if(can_attack && keydown(KEY_F3)) {
|
||||
entity_t *aoe = aoe_make_attack( AOE_JUDGEMENT, player,
|
||||
player_p->facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
entity_set_anim(player, anims_player_Attack, 2);
|
||||
player->current_attack = area;
|
||||
player->attack_follows_movement = false;
|
||||
visible_set_anim(player, &anims_player_Attack, 2);
|
||||
player_f->current_attack = aoe;
|
||||
player_f->attack_follows_movement = false;
|
||||
}
|
||||
if(can_attack && keydown(KEY_F4)) {
|
||||
entity_t *aoe = aoe_make_attack(AOE_BULLET, player,
|
||||
player_p->facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
visible_set_anim(player, &anims_player_Attack, 2);
|
||||
player_f->current_attack = aoe;
|
||||
player_f->attack_follows_movement = false;
|
||||
}
|
||||
|
||||
/* Ideas for additional skills:
|
||||
- Freeze enemies
|
||||
- Barrier around player
|
||||
|
@ -496,18 +489,22 @@ int main(void)
|
|||
- XP boosts
|
||||
- Weaker but longer-lasting buffs */
|
||||
|
||||
/* Remove dead entities first as it will kill their attack areas */
|
||||
game_remove_dead_entities(&game);
|
||||
game_update_effect_areas(&game, dt);
|
||||
game_update_animations(&game, dt);
|
||||
game_update_aoes(&game, dt);
|
||||
game_update_particles(&game, dt);
|
||||
playerd->combo_delay -= dt;
|
||||
game_remove_dead_entities(&game);
|
||||
player_f->combo_delay -= dt;
|
||||
|
||||
/* Reset default anims */
|
||||
if(!player_v->anim.frame)
|
||||
visible_set_anim(player, &anims_player_Idle, 1);
|
||||
|
||||
game.time_total += dt;
|
||||
game.time_wave += dt;
|
||||
|
||||
if(game.time_defeat == 0 && player->HP == 0)
|
||||
if(game.time_defeat == 0 && player_f->HP == 0)
|
||||
game.time_defeat = game.time_total;
|
||||
if(game.time_victory == 0 && player->HP > 0 && game.entity_count == 1)
|
||||
if(game.time_victory == 0 && player_f->HP > 0 && game.entity_count==1)
|
||||
game.time_victory = game.time_total;
|
||||
|
||||
/* Next wave */
|
||||
|
@ -524,7 +521,7 @@ int main(void)
|
|||
debug.grid_path = pfg_bfs_outwards(&game.paths_to_player,
|
||||
vec_f2i(target));
|
||||
debug.continuous_path = pfc_shortcut_full(&debug.grid_path,
|
||||
entity_pos(player), target, player->hitbox);
|
||||
physical_pos(player), target, player_p->hitbox);
|
||||
}
|
||||
|
||||
prof_leave(perf_simul);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "map.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
rect tile_shape(struct tile const *tile)
|
||||
rect tile_shape(tile_t const *tile)
|
||||
{
|
||||
if(!tile->solid) return (rect){ 0 };
|
||||
|
||||
|
@ -13,7 +13,7 @@ rect tile_shape(struct tile const *tile)
|
|||
};
|
||||
}
|
||||
|
||||
struct tile *map_tile(map_t const *m, int x, int y)
|
||||
tile_t *map_tile(map_t const *m, int x, int y)
|
||||
{
|
||||
if((unsigned)x >= (unsigned)m->width || (unsigned)y >= (unsigned)m->height)
|
||||
return NULL;
|
||||
|
@ -31,7 +31,7 @@ bool map_collides(map_t const *m, rect hitbox)
|
|||
/* Collisions against walls and static objects */
|
||||
for(int y = y_min; y < y_max; y++)
|
||||
for(int x = x_min; x < x_max; x++) {
|
||||
struct tile *t = map_tile(m, x, y);
|
||||
tile_t *t = map_tile(m, x, y);
|
||||
if(!t || !t->solid) continue;
|
||||
|
||||
vec2 center = { fix(x) + fix(0.5), fix(y) + fix(0.5) };
|
||||
|
|
16
src/map.h
16
src/map.h
|
@ -11,7 +11,6 @@
|
|||
#include "fixed.h"
|
||||
#include "map.h"
|
||||
#include "geometry.h"
|
||||
#include "entities.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
@ -21,7 +20,8 @@
|
|||
// Tiles
|
||||
//---
|
||||
|
||||
struct tile {
|
||||
typedef struct
|
||||
{
|
||||
/* TODO: Layers of objects, stuff, dynamic elements, etc? */
|
||||
/* TODO: Allow any collision shape for the tile! */
|
||||
bool solid;
|
||||
|
@ -29,27 +29,29 @@ struct tile {
|
|||
uint8_t base;
|
||||
/* Decoration layer */
|
||||
uint8_t decor;
|
||||
};
|
||||
|
||||
} tile_t;
|
||||
|
||||
/* Shape for a tile; this lives in a coordinate system whose (0,0) ends up at
|
||||
the middle of the tile in the map space (which is a point with half-integer
|
||||
coordinates) */
|
||||
rect tile_shape(struct tile const *tile);
|
||||
rect tile_shape(tile_t const *tile);
|
||||
|
||||
//---
|
||||
// Map grid, tiles location in space, and entities
|
||||
//---
|
||||
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
/* Dimensions, columns are 0 to width-1, rows are 0 to height-1 */
|
||||
int width, height;
|
||||
/* All tiles */
|
||||
struct tile *tiles;
|
||||
tile_t *tiles;
|
||||
|
||||
} map_t;
|
||||
|
||||
/* Get a pointer to the tile at (x,y) in map; NULL if out of bounds. */
|
||||
struct tile *map_tile(map_t const *m, int x, int y);
|
||||
tile_t *map_tile(map_t const *m, int x, int y);
|
||||
|
||||
/* Check whether a hitbox collides with the map. */
|
||||
bool map_collides(map_t const *m, rect hitbox);
|
||||
|
|
|
@ -66,7 +66,7 @@ pfg_all2one_t pfg_bfs(map_t const *map, ivec2 center)
|
|||
int dx = dx_array[dir];
|
||||
int dy = dy_array[dir];
|
||||
|
||||
struct tile *tile = map_tile(map, point.x+dx, point.y+dy);
|
||||
tile_t *tile = map_tile(map, point.x+dx, point.y+dy);
|
||||
if(!tile || tile->solid) continue;
|
||||
|
||||
int next_i = idx(point.x+dx, point.y+dy);
|
||||
|
@ -159,7 +159,7 @@ bool raycast_clear(map_t const *map, vec2 start, vec2 end)
|
|||
perfectly */
|
||||
int current_x = ffloor(x-(u.x < 0));
|
||||
int current_y = ffloor(y-(u.y < 0));
|
||||
struct tile *tile = map_tile(map, current_x, current_y);
|
||||
tile_t *tile = map_tile(map, current_x, current_y);
|
||||
if(tile && tile->solid) return false;
|
||||
|
||||
if(raycast_clear_points < 64) {
|
||||
|
@ -185,7 +185,7 @@ bool raycast_clear(map_t const *map, vec2 start, vec2 end)
|
|||
int next_x = ffloor(x-(u.x < 0));
|
||||
int next_y = ffloor(y-(u.y < 0)) + (u.y >= 0 ? 1 : -1);
|
||||
|
||||
struct tile *tile = map_tile(map, next_x, next_y);
|
||||
tile_t *tile = map_tile(map, next_x, next_y);
|
||||
if(tile && tile->solid) return false;
|
||||
}
|
||||
else {
|
||||
|
@ -195,7 +195,7 @@ bool raycast_clear(map_t const *map, vec2 start, vec2 end)
|
|||
int next_x = ffloor(x-(u.x < 0)) + (u.x >= 0 ? 1 : -1);
|
||||
int next_y = ffloor(y-(u.y < 0));
|
||||
|
||||
struct tile *tile = map_tile(map, next_x, next_y);
|
||||
tile_t *tile = map_tile(map, next_x, next_y);
|
||||
if(tile && tile->solid) return false;
|
||||
}
|
||||
}
|
||||
|
|
195
src/render.c
195
src/render.c
|
@ -1,8 +1,14 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/physical.h"
|
||||
#include "comp/visible.h"
|
||||
#include "comp/mechanical.h"
|
||||
#include "comp/fighter.h"
|
||||
#include "render.h"
|
||||
#include "game.h"
|
||||
#include "anim.h"
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <gint/defs/util.h>
|
||||
|
||||
//---
|
||||
// Camera management
|
||||
|
@ -111,7 +117,7 @@ fixed_t camera_ppu(camera_t const *c)
|
|||
// Rendering
|
||||
//---
|
||||
|
||||
void render_map(map_t const *m, camera_t const *c)
|
||||
static void render_map_layer(map_t const *m, camera_t const *c, int layer)
|
||||
{
|
||||
extern bopti_image_t img_tileset_base;
|
||||
extern bopti_image_t img_tileset_decor;
|
||||
|
@ -119,7 +125,7 @@ void render_map(map_t const *m, camera_t const *c)
|
|||
/* Render floor and walls */
|
||||
for(int row = -2; row < m->height + 2; row++)
|
||||
for(int col = -1; col < m->width + 1; col++) {
|
||||
struct tile *t = map_tile(m, col, row);
|
||||
tile_t *t = map_tile(m, col, row);
|
||||
vec2 tile_pos = { fix(col), fix(row) };
|
||||
ivec2 p = camera_map2screen(c, tile_pos);
|
||||
|
||||
|
@ -128,7 +134,12 @@ void render_map(map_t const *m, camera_t const *c)
|
|||
continue;
|
||||
}
|
||||
|
||||
/* Floor/wall layer */
|
||||
bool will_draw = false;
|
||||
if(layer == 0) will_draw = (t->base < 16); // Floor
|
||||
if(layer == 1) will_draw = (t->base >= 16); // Walls
|
||||
if(!will_draw)
|
||||
continue;
|
||||
|
||||
dsubimage(p.x, p.y, &img_tileset_base,
|
||||
TILE_WIDTH * (t->base % 16), TILE_HEIGHT * (t->base / 16),
|
||||
TILE_WIDTH, TILE_HEIGHT, DIMAGE_NOCLIP);
|
||||
|
@ -139,82 +150,128 @@ void render_map(map_t const *m, camera_t const *c)
|
|||
}
|
||||
}
|
||||
|
||||
static void render_effect_area(effect_area_t const *ea, camera_t const *c,
|
||||
bool show_hitboxes)
|
||||
static int depth_measure(entity_t const *e, int direction)
|
||||
{
|
||||
ivec2 anchor = camera_map2screen(c, ea->anchor);
|
||||
visible_t *v = getcomp(e, visible);
|
||||
if(v == NULL || v->sprite_plane != direction)
|
||||
return -1;
|
||||
return getcomp(e, physical)->y;
|
||||
}
|
||||
static int floor_depth_measure(entity_t const *e)
|
||||
{
|
||||
return depth_measure(e, HORIZONTAL);
|
||||
}
|
||||
static int wall_depth_measure(entity_t const *e)
|
||||
{
|
||||
return depth_measure(e, VERTICAL);
|
||||
}
|
||||
|
||||
if(ea->anim.frame) {
|
||||
ivec2 p = camera_map2screen(c, ea->anchor);
|
||||
anim_frame_render(p.x, p.y, ea->anim.frame);
|
||||
static void render_shadow(int cx, int cy, int shadow_size)
|
||||
{
|
||||
if(shadow_size < 1 || shadow_size > 4)
|
||||
return;
|
||||
|
||||
static const int hw_array[] = { 2, 3, 5, 7 };
|
||||
static const int hh_array[] = { 1, 1, 2, 3 };
|
||||
|
||||
/* TODO: render_shadow(): Encode shadow shapes properly x) */
|
||||
static const uint8_t profile_1[] = { 1, 0, 1 };
|
||||
static const uint8_t profile_2[] = { 1, 0, 1 };
|
||||
static const uint8_t profile_3[] = { 3, 1, 0, 1, 3 };
|
||||
static const uint8_t profile_4[] = { 4, 2, 1, 0, 1, 2, 4 };
|
||||
static const uint8_t *profile_array[] = {
|
||||
profile_1, profile_2, profile_3, profile_4,
|
||||
};
|
||||
|
||||
int hw = hw_array[shadow_size - 1];
|
||||
int hh = hh_array[shadow_size - 1];
|
||||
uint8_t const *profile = profile_array[shadow_size - 1] + hh;
|
||||
|
||||
int xmin = max(-hw, -cx);
|
||||
int xmax = min(hw, DWIDTH-1 - cx);
|
||||
int ymin = max(-hh, -cy);
|
||||
int ymax = min(hh, DHEIGHT-1 - cy);
|
||||
|
||||
for(int y = ymin; y <= ymax; y++)
|
||||
for(int x = xmin; x <= xmax; x++) {
|
||||
if(hw - abs(x) < profile[y])
|
||||
continue;
|
||||
int i = DWIDTH * (cy+y) + (cx+x);
|
||||
gint_vram[i] = (gint_vram[i] & 0xf7de) >> 1;
|
||||
}
|
||||
if(!ea->anim.frame || show_hitboxes) {
|
||||
rect r = ea->sprite;
|
||||
r = rect_scale(r, camera_ppu(c));
|
||||
r = rect_translate(r, vec_i2f(anchor));
|
||||
rect_draw(r, C_RGB(31, 31, 0));
|
||||
}
|
||||
|
||||
static void render_entities(game_t const *g, camera_t const *camera,
|
||||
entity_measure_t *measure, bool show_hitboxes)
|
||||
{
|
||||
uint16_t *rendering_order;
|
||||
int count = game_sort_entities(g, measure, &rendering_order);
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
entity_t *e = g->entities[rendering_order[i]];
|
||||
physical_t *p = getcomp(e, physical);
|
||||
visible_t *v = getcomp(e, visible);
|
||||
|
||||
ivec2 scr = camera_map2screen(camera, physical_pos(e));
|
||||
int elevated_y = scr.y - fround(16 * v->z);
|
||||
|
||||
/* Show shadow */
|
||||
if(v->shadow_size) {
|
||||
render_shadow(scr.x, scr.y, v->shadow_size);
|
||||
}
|
||||
|
||||
/* Show entity sprite */
|
||||
if(v->anim.frame) {
|
||||
anim_frame_render(scr.x, elevated_y, v->anim.frame);
|
||||
}
|
||||
/* Show entity hitbox in the map coordinate system */
|
||||
if(!v->anim.frame || show_hitboxes) {
|
||||
rect r = p->hitbox;
|
||||
r = rect_scale(r, camera_ppu(camera));
|
||||
r = rect_translate(r, vec_i2f(scr));
|
||||
rect_draw(r, C_BLUE);
|
||||
|
||||
/* Show entity center */
|
||||
dline(scr.x-1, scr.y, scr.x+1, scr.y, C_BLUE);
|
||||
dline(scr.x, scr.y-1, scr.x, scr.y+1, C_BLUE);
|
||||
}
|
||||
}
|
||||
|
||||
free(rendering_order);
|
||||
}
|
||||
|
||||
void render_game(game_t const *g, bool show_hitboxes)
|
||||
{
|
||||
camera_t const *c = &g->camera;
|
||||
camera_t const *camera = &g->camera;
|
||||
|
||||
render_map(&g->map, &g->camera);
|
||||
/* Render map floor */
|
||||
render_map_layer(&g->map, camera, 0);
|
||||
|
||||
/* Render background particles */
|
||||
/* Render background particles
|
||||
TODO ECS: Use ECS entities for particles */
|
||||
for(int i = 0; i < g->particle_count; i++) {
|
||||
particle_t *p = g->particles[i];
|
||||
if(!particle_is_background(p)) continue;
|
||||
ivec2 center = camera_map2screen(c, p->pos);
|
||||
ivec2 center = camera_map2screen(camera, p->pos);
|
||||
particle_render(center.x, center.y, p);
|
||||
}
|
||||
|
||||
/* Render background effect areas */
|
||||
for(int i = 0; i < g->effect_area_count; i++) {
|
||||
if(!effect_area_is_background(g->effect_areas[i])) continue;
|
||||
render_effect_area(g->effect_areas[i], c, show_hitboxes);
|
||||
}
|
||||
/* Render floor entities */
|
||||
render_entities(g, camera, floor_depth_measure, show_hitboxes);
|
||||
|
||||
/* Render entities */
|
||||
for(int i = 0; i < g->entity_count; i++) {
|
||||
entity_t *e = g->entities[i];
|
||||
/* Render map walls */
|
||||
render_map_layer(&g->map, camera, 1);
|
||||
|
||||
ivec2 p = camera_map2screen(c, entity_pos(e));
|
||||
/* Render wall entities
|
||||
TODO ECS: Sort walls along wall entities! */
|
||||
render_entities(g, camera, wall_depth_measure, show_hitboxes);
|
||||
|
||||
/* Show entity sprite */
|
||||
if(e->anim.frame) {
|
||||
anim_frame_render(p.x, p.y, e->anim.frame);
|
||||
}
|
||||
/* Show entity hitbox in the map coordinate system */
|
||||
if(!e->anim.frame || show_hitboxes) {
|
||||
rect r = e->hitbox;
|
||||
r = rect_scale(r, camera_ppu(c));
|
||||
r = rect_translate(r, vec_i2f(p));
|
||||
rect_draw(r, C_BLUE);
|
||||
|
||||
r = e->sprite;
|
||||
r = rect_scale(r, camera_ppu(c));
|
||||
r = rect_translate(r, vec_i2f(p));
|
||||
rect_draw(r, C_RGB(12, 12, 12));
|
||||
|
||||
/* Show entity center */
|
||||
dline(p.x-1, p.y, p.x+1, p.y, C_BLUE);
|
||||
dline(p.x, p.y-1, p.x, p.y+1, C_BLUE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Render background effect areas */
|
||||
for(int i = 0; i < g->effect_area_count; i++) {
|
||||
if(effect_area_is_background(g->effect_areas[i])) continue;
|
||||
render_effect_area(g->effect_areas[i], c, show_hitboxes);
|
||||
}
|
||||
|
||||
/* Render foreground particles */
|
||||
/* Render foreground particles
|
||||
TODO ECS: Use ECS entities for particles */
|
||||
for(int i = 0; i < g->particle_count; i++) {
|
||||
particle_t *p = g->particles[i];
|
||||
if(particle_is_background(p)) continue;
|
||||
ivec2 center = camera_map2screen(c, p->pos);
|
||||
ivec2 center = camera_map2screen(camera, p->pos);
|
||||
particle_render(center.x, center.y, p);
|
||||
}
|
||||
|
||||
|
@ -223,8 +280,10 @@ void render_game(game_t const *g, bool show_hitboxes)
|
|||
|
||||
/* Render wave progress bar */
|
||||
int enemies_left = 0;
|
||||
for(int i = 0; i < g->entity_count; i++)
|
||||
enemies_left += (g->entities[i]->identity != 0);
|
||||
for(int i = 0; i < g->entity_count; i++) {
|
||||
fighter_t *f = getcomp(g->entities[i], fighter);
|
||||
enemies_left += (f && f->identity != 0);
|
||||
}
|
||||
|
||||
if(g->wave_spawned < game_current_wave(g)->enemy_count)
|
||||
dprint_opt(DWIDTH/2, 2, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP,
|
||||
|
@ -241,10 +300,12 @@ void render_game(game_t const *g, bool show_hitboxes)
|
|||
extern bopti_image_t img_hud;
|
||||
dimage(0, DHEIGHT - img_hud.height, &img_hud);
|
||||
|
||||
fighter_t *player_f = getcomp(g->player, fighter);
|
||||
mechanical_t *player_m = getcomp(g->player, mechanical);
|
||||
|
||||
/* Render life bar */
|
||||
extern bopti_image_t img_hud_life;
|
||||
entity_t const *player = g->player;
|
||||
int fill_height = (img_hud_life.height * player->HP) / player->HP_max;
|
||||
int fill_height = (img_hud_life.height * player_f->HP) / player_f->HP_max;
|
||||
dsubimage(184, DHEIGHT - 5 - fill_height, &img_hud_life,
|
||||
0, img_hud_life.height - fill_height, img_hud_life.width, fill_height,
|
||||
DIMAGE_NONE);
|
||||
|
@ -254,13 +315,13 @@ void render_game(game_t const *g, bool show_hitboxes)
|
|||
for(int i = 0; i < 6; i++) {
|
||||
/* Activity and cooldown */
|
||||
fixed_t cooldown_total=0, cooldown_remaining=0;
|
||||
if(i == 0 && player->movement.dash < 0) {
|
||||
cooldown_total = player->movement_params->dash_cooldown;
|
||||
cooldown_remaining = -player->movement.dash;
|
||||
if(i == 0 && player_m->dash < 0) {
|
||||
cooldown_total = player_m->limits->dash_cooldown;
|
||||
cooldown_remaining = -player_m->dash;
|
||||
}
|
||||
else if(i > 0) {
|
||||
cooldown_total = player->player->actions_cooldown_total[i-1];
|
||||
cooldown_remaining = player->player->actions_cooldown[i-1];
|
||||
cooldown_total = player_f->actions_cooldown_total[i-1];
|
||||
cooldown_remaining = player_f->actions_cooldown[i-1];
|
||||
}
|
||||
|
||||
int x = 33 + 44*i + 84*(i>=3);
|
||||
|
@ -288,7 +349,7 @@ void render_pfg_all2one(pfg_all2one_t const *paths, camera_t const *c)
|
|||
{
|
||||
for(int row = 0; row < paths->map->height; row++)
|
||||
for(int col = 0; col < paths->map->width; col++) {
|
||||
struct tile *tile = map_tile(paths->map, col, row);
|
||||
tile_t *tile = map_tile(paths->map, col, row);
|
||||
if(!tile || tile->solid) continue;
|
||||
|
||||
vec2 fp = vec_i2f_center((ivec2){ col, row });
|
||||
|
|
|
@ -56,11 +56,6 @@ fixed_t camera_ppu(camera_t const *c);
|
|||
// Rendering
|
||||
//---
|
||||
|
||||
/* Render map for the specified camera.
|
||||
TODO: render_map: Honor zoom
|
||||
TODO: render_map: Clip around viewport */
|
||||
void render_map(map_t const *m, camera_t const *c);
|
||||
|
||||
/* Render game full-screen. */
|
||||
struct game;
|
||||
void render_game(struct game const *g, bool show_hitboxes);
|
||||
|
|
Loading…
Reference in New Issue