enemy attacks, defeat, factored movement, unused dash particles
This commit is contained in:
parent
70ecd17834
commit
0838277274
|
@ -49,6 +49,7 @@ set(ASSETS
|
|||
assets-cg/skills/swing_right.png
|
||||
assets-cg/skills/swing_down.png
|
||||
assets-cg/skills/swing_left.png
|
||||
assets-cg/skills/hit.png
|
||||
# Enemies: Slime
|
||||
assets-cg/enemies/slime_idle_down.png
|
||||
assets-cg/enemies/slime_death.png
|
||||
|
@ -56,7 +57,8 @@ set(ASSETS
|
|||
assets-cg/enemies/bat_idle_down.png
|
||||
assets-cg/enemies/bat_death.png
|
||||
# Misc
|
||||
assets-cg/font_damage.png
|
||||
assets-cg/font_damage_red.png
|
||||
assets-cg/font_damage_white.png
|
||||
)
|
||||
|
||||
fxconv_declare_assets(${ASSETS} ${ASSETS} WITH_METADATA)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 278 B |
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
|
@ -6,3 +6,6 @@
|
|||
|
||||
swing_*.png:
|
||||
frame_duration: 30, 60, 30, 30, 30, 60, 30
|
||||
|
||||
hit.png:
|
||||
frame_duration: 60, 90, 90, 60, 90
|
||||
|
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 181 B |
|
@ -52,6 +52,7 @@ extern anim_frame_t anim_slime_idle_down[];
|
|||
extern anim_frame_t anim_slime_death[];
|
||||
extern anim_frame_t anim_bat_idle_down[];
|
||||
extern anim_frame_t anim_bat_death[];
|
||||
extern anim_frame_t anim_hit[];
|
||||
|
||||
/* Directional animations. */
|
||||
extern anim_frame_t *anim_player_idle[4];
|
||||
|
|
|
@ -90,6 +90,7 @@ entity_t *enemy_spawn(int enemy_id, int level)
|
|||
e->movement.facing = DOWN;
|
||||
e->movement.dash = 0;
|
||||
e->movement.dash_facing = DOWN;
|
||||
e->movement.dash_particle_cooldown = fix(0);
|
||||
|
||||
e->movement_params = &data->movement_params;
|
||||
entity_set_anim(e, data->anim_idle);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "game.h"
|
||||
#include "enemies.h"
|
||||
#include <stdlib.h>
|
||||
#include <gint/defs/util.h>
|
||||
|
||||
//---
|
||||
// Entities
|
||||
|
@ -36,6 +37,30 @@ void entity_dash(entity_t *e, int direction)
|
|||
|
||||
move->dash = params->dash_duration;
|
||||
move->dash_facing = direction;
|
||||
move->dash_particle_cooldown = fix(0);
|
||||
}
|
||||
|
||||
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 over-speed is maintained */
|
||||
fixed_t cur_v2 = fmul(move->x, move->x) + fmul(move->y, move->y);
|
||||
fixed_t max_v2 = fmul(params->max_speed, params->max_speed);
|
||||
|
||||
if(move->dash < 0 && cur_v2 > max_v2)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
entity_movement_t entity_move(entity_t *e, fpoint_t direction, fixed_t dt)
|
||||
|
@ -162,15 +187,24 @@ static effect_area_record_t *effect_record(effect_area_t *ea, entity_t *e)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static bool slash_apply(game_t *game, effect_area_t *ea, entity_t *e)
|
||||
static bool slash_hit_apply(game_t *game, effect_area_t *ea, entity_t *e)
|
||||
{
|
||||
if(e == ea->origin || e->HP == 0) return false;
|
||||
/* No friendly fire */
|
||||
bool origin_is_monster = (ea->origin->identity != 0);
|
||||
bool target_is_monster = (e->identity != 0);
|
||||
if(origin_is_monster == target_is_monster || e->HP == 0) return false;
|
||||
|
||||
/* Dash invincibility */
|
||||
if(entity_dashing(e)) return false;
|
||||
|
||||
/* Inflict damage */
|
||||
int damage = entity_damage(e, ea->data.slash.strength);
|
||||
|
||||
/* Knockback */
|
||||
fixed_t r = (rand() & (fix(1)/4-1)) + fix(7)/8;
|
||||
/* Half knockback against players */
|
||||
if(e->identity == 0) r /= 2;
|
||||
|
||||
fpoint_t dir = fdir(ea->data.slash.dir);
|
||||
e->movement.vx += fmul(dir.x, fmul(r, KNOCKBACK_SPEED));
|
||||
e->movement.vy += fmul(dir.y, fmul(r, KNOCKBACK_SPEED));
|
||||
|
@ -181,6 +215,7 @@ static bool slash_apply(game_t *game, effect_area_t *ea, entity_t *e)
|
|||
p->particle.age = 0;
|
||||
p->particle.pos = (fpoint_t){ e->movement.x, e->movement.y - fix(1)/2 };
|
||||
p->damage = damage;
|
||||
p->color = (e->identity == 0) ? C_RED : C_WHITE;
|
||||
game_add_particle(game, &p->particle);
|
||||
|
||||
return true;
|
||||
|
@ -196,9 +231,9 @@ void effect_area_apply(game_t *game, effect_area_t *ea, entity_t *e)
|
|||
if(rec && ea->lifetime > rec->lifetime - ea->repeat_delay) return;
|
||||
|
||||
if(ea->type == EFFECT_HIT)
|
||||
was_hit = false;
|
||||
was_hit = slash_hit_apply(game, ea, e);
|
||||
else if(ea->type == EFFECT_SLASH)
|
||||
was_hit = slash_apply(game, ea, e);
|
||||
was_hit = slash_hit_apply(game, ea, e);
|
||||
|
||||
if(!was_hit) return;
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ typedef struct {
|
|||
uint8_t facing;
|
||||
/* Dash direction */
|
||||
uint8_t dash_facing;
|
||||
/* Cooldown until next dash particle */
|
||||
fixed_t dash_particle_cooldown;
|
||||
|
||||
} entity_movement_t;
|
||||
|
||||
|
@ -86,6 +88,9 @@ entity_movement_t entity_move(entity_t *e, fpoint_t direction, fixed_t dt);
|
|||
/* 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);
|
||||
void entity_set_directional_anim(entity_t *e, anim_frame_t *frame_dirs[]);
|
||||
|
|
69
src/game.c
69
src/game.c
|
@ -28,6 +28,7 @@ bool game_load(game_t *g, level_t *level)
|
|||
|
||||
g->time_total = fix(0);
|
||||
g->time_victory = fix(0);
|
||||
g->time_defeat = fix(0);
|
||||
|
||||
g->entities = NULL;
|
||||
g->entity_count = 0;
|
||||
|
@ -77,6 +78,12 @@ 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]);
|
||||
g->entities[i] = g->entities[--g->entity_count];
|
||||
}
|
||||
|
@ -158,7 +165,44 @@ static void game_remove_particle(game_t *g, int i)
|
|||
}
|
||||
|
||||
//---
|
||||
// Update functions
|
||||
// Interacting with game elements
|
||||
//---
|
||||
|
||||
void game_try_move_entity(game_t *g, entity_t *e,
|
||||
entity_movement_t const * next)
|
||||
{
|
||||
entity_movement_t *m = &e->movement;
|
||||
frect_t hitbox = rect_translate(e->hitbox, (fpoint_t){ next->x, next->y });
|
||||
|
||||
if(!map_collides(&g->map, hitbox)) {
|
||||
/* Movement is allowed */
|
||||
fixed_t dx = next->x - m->x;
|
||||
fixed_t dy = next->y - m->y;
|
||||
|
||||
/* Update attached attack animation */
|
||||
if(next->facing != m->facing) {
|
||||
e->attack_follows_movement = false;
|
||||
}
|
||||
if(e->current_attack && e->attack_follows_movement) {
|
||||
e->current_attack->anchor.x += dx;
|
||||
e->current_attack->anchor.y += dy;
|
||||
}
|
||||
|
||||
*m = *next;
|
||||
}
|
||||
else {
|
||||
/* Movement is denied. Halve speed so that high-speed movement doesn't
|
||||
halt at a large distance from a wall. */
|
||||
m->facing = next->facing;
|
||||
m->vx = next->vx / 2;
|
||||
m->vy = next->vy / 2;
|
||||
m->dash = next->dash;
|
||||
m->dash_particle_cooldown = next->dash_particle_cooldown;
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// Per-frame update functions
|
||||
//---
|
||||
|
||||
void game_update_animations(game_t *g, fixed_t dt)
|
||||
|
@ -201,6 +245,29 @@ void game_update_particles(game_t *g, fixed_t dt)
|
|||
if(remove) game_remove_particle(g, i);
|
||||
else i++;
|
||||
}
|
||||
|
||||
/* Spawn dash particles */
|
||||
#if 0
|
||||
for(int i = 0; i < g->entity_count; i++) {
|
||||
entity_t *e = g->entities[i];
|
||||
|
||||
if(entity_dashing(e)) {
|
||||
e->movement.dash_particle_cooldown -= dt;
|
||||
|
||||
if(e->movement.dash_particle_cooldown < 0) {
|
||||
particle_dash_t *p = malloc(sizeof *p);
|
||||
p->particle.type = PARTICLE_DASH;
|
||||
p->particle.age = 0;
|
||||
p->particle.pos = entity_pos(e);
|
||||
extern anim_frame_t anim_player_dash_trail_right[];
|
||||
p->frame = e->anim.frame;
|
||||
game_add_particle(g, &p->particle);
|
||||
|
||||
e->movement.dash_particle_cooldown = fix(25)/100;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Compare the y values of two entities. */
|
||||
|
|
24
src/game.h
24
src/game.h
|
@ -19,8 +19,9 @@ typedef struct game {
|
|||
camera_t camera;
|
||||
/* Time played */
|
||||
fixed_t time_total;
|
||||
/* Time when victory was reached */
|
||||
/* Time when victory was reached or defeat was dealt */
|
||||
fixed_t time_victory;
|
||||
fixed_t time_defeat;
|
||||
/* List of entities */
|
||||
entity_t **entities;
|
||||
int entity_count;
|
||||
|
@ -43,6 +44,10 @@ bool game_load(game_t *g, level_t *level);
|
|||
/* Free resources allocated for the level. */
|
||||
void game_unload(game_t *g);
|
||||
|
||||
//---
|
||||
// Adding 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);
|
||||
|
||||
|
@ -52,6 +57,23 @@ 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);
|
||||
|
||||
//---
|
||||
// Interacting with game elements
|
||||
//---
|
||||
|
||||
/* 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);
|
||||
|
||||
|
||||
|
||||
//---
|
||||
// Per-frame update functions
|
||||
//---
|
||||
|
||||
/* Update all entities' and effect areas' animations. */
|
||||
void game_update_animations(game_t *g, fixed_t dt);
|
||||
|
||||
|
|
111
src/main.c
111
src/main.c
|
@ -93,6 +93,7 @@ int main(void)
|
|||
|
||||
entity_t *player = game.entities[5];
|
||||
game.player = player;
|
||||
player->HP += 100;
|
||||
player->movement_params = &emp_player;
|
||||
player->identity = 0;
|
||||
player->hitbox = (frect_t){
|
||||
|
@ -133,8 +134,10 @@ int main(void)
|
|||
|
||||
if(game.time_victory != 0) dprint(1, 1, C_WHITE, "Victory in %.1f s!",
|
||||
f2double(game.time_victory));
|
||||
else dprint(1, 1, C_WHITE, "HP:%d ATK:%d DEF:%d",
|
||||
player->HP, player->ATK, player->DEF);
|
||||
else if(game.time_defeat != 0) dprint(1, 1, C_WHITE, "Defeat! :(");
|
||||
else dprint(1, 1, C_WHITE, "HP:%d ATK:%d DEF:%d dpc:%.1f",
|
||||
player->HP, player->ATK, player->DEF,
|
||||
f2double(player->movement.dash_particle_cooldown));
|
||||
|
||||
/* Developer/tweaking menu */
|
||||
if(debug.show_vars) {
|
||||
|
@ -335,53 +338,30 @@ int main(void)
|
|||
camera_move(c, 0, fmul(dt, vy));
|
||||
|
||||
/* Player movement */
|
||||
int dir = -1;
|
||||
if(keydown(KEY_UP)) dir = UP;
|
||||
if(keydown(KEY_DOWN)) dir = DOWN;
|
||||
if(keydown(KEY_LEFT)) dir = LEFT;
|
||||
if(keydown(KEY_RIGHT)) dir = RIGHT;
|
||||
if(player->HP > 0) {
|
||||
int dir = -1;
|
||||
if(keydown(KEY_UP)) dir = UP;
|
||||
if(keydown(KEY_DOWN)) dir = DOWN;
|
||||
if(keydown(KEY_LEFT)) dir = LEFT;
|
||||
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);
|
||||
}
|
||||
entity_movement_t next = entity_move4(player, dir, dt);
|
||||
|
||||
frect_t player_hitbox = rect_translate(player->hitbox,
|
||||
(fpoint_t){ next.x, next.y });
|
||||
|
||||
bool set_anim = (player->movement.facing != next.facing);
|
||||
|
||||
if(!map_collides(m, player_hitbox)) {
|
||||
fixed_t dx = next.x - player->movement.x;
|
||||
fixed_t dy = next.y - player->movement.y;
|
||||
|
||||
if(next.facing != player->movement.facing) {
|
||||
player->attack_follows_movement = false;
|
||||
if(keydown(KEY_F1) && !keydown(KEY_VARS)) {
|
||||
int dash_dir = (dir >= 0) ? dir : player->movement.facing;
|
||||
entity_dash(player, dash_dir);
|
||||
}
|
||||
entity_movement_t next = entity_move4(player, dir, dt);
|
||||
|
||||
if(player->current_attack && player->attack_follows_movement) {
|
||||
player->current_attack->anchor.x += dx;
|
||||
player->current_attack->anchor.y += dy;
|
||||
}
|
||||
|
||||
player->movement = next;
|
||||
bool set_anim = (player->movement.facing != next.facing);
|
||||
game_try_move_entity(&game, player, &next);
|
||||
if(set_anim) entity_set_anim(player, anim_player_idle);
|
||||
}
|
||||
else {
|
||||
player->movement.facing = next.facing;
|
||||
player->movement.vx = next.vx / 2;
|
||||
player->movement.vy = next.vy / 2;
|
||||
player->movement.dash = next.dash;
|
||||
}
|
||||
|
||||
if(set_anim) entity_set_anim(player, anim_player_idle);
|
||||
|
||||
/* Directions to reach the player from anywhere on the grid */
|
||||
pfg_all2one_free(&game.paths_to_player);
|
||||
game.paths_to_player = pfg_bfs(m, point_f2i(entity_pos(player)));
|
||||
|
||||
/* Enemy movement */
|
||||
for(int i = 0; i < game.entity_count; i++) {
|
||||
/* Enemy AI */
|
||||
if(player->HP > 0) for(int i = 0; i < game.entity_count; i++) {
|
||||
entity_t *e = game.entities[i];
|
||||
if(e == player || e->HP == 0) continue;
|
||||
|
||||
|
@ -389,7 +369,9 @@ int main(void)
|
|||
fpoint_t direction = { 0, 0 };
|
||||
fpoint_t pos = entity_pos(e);
|
||||
|
||||
if(dist2(pos, entity_pos(player)) > fix(1)) {
|
||||
bool in_range = dist2(pos, entity_pos(player)) <= fix(1);
|
||||
|
||||
if(!in_range) {
|
||||
pfg_path_t path = pfg_bfs_inwards(&game.paths_to_player,
|
||||
point_f2i(pos));
|
||||
if(path.points) {
|
||||
|
@ -405,21 +387,39 @@ int main(void)
|
|||
}
|
||||
|
||||
entity_movement_t next = entity_move(e, direction, dt);
|
||||
frect_t e_hitbox = rect_translate(e->hitbox,
|
||||
(fpoint_t){ next.x, next.y });
|
||||
game_try_move_entity(&game, e, &next);
|
||||
|
||||
if(!map_collides(m, e_hitbox)) {
|
||||
e->movement = next;
|
||||
}
|
||||
else {
|
||||
e->movement.facing = next.facing;
|
||||
e->movement.vx = fix(0);
|
||||
e->movement.vy = fix(0);
|
||||
if(in_range && !e->current_attack) {
|
||||
/* Attack */
|
||||
frect_t hitbox = {
|
||||
-fix(4)/16, fix(3)/16, -fix(4)/16, fix(3)/16,
|
||||
};
|
||||
hitbox = rect_rotate(hitbox, UP, e->movement.facing);
|
||||
fpoint_t dir = fdir(e->movement.facing);
|
||||
|
||||
fpoint_t anchor = rect_center(entity_sprite(e));
|
||||
anchor.x += fmul(fix(3)/4, dir.x);
|
||||
anchor.y += fmul(fix(3)/4, dir.y);
|
||||
|
||||
effect_area_t *area = effect_area_new(EFFECT_HIT);
|
||||
area->sprite = hitbox;
|
||||
area->anchor = anchor;
|
||||
area->lifetime = anim_duration(anim_hit);
|
||||
area->repeat_delay = 0;
|
||||
area->origin = e;
|
||||
area->data.slash.strength = e->ATK;
|
||||
area->data.slash.dir = e->movement.facing;
|
||||
effect_area_set_anim(area, anim_hit);
|
||||
game_add_effect_area(&game, area);
|
||||
|
||||
// entity_set_anim(player, anim_player_attack);
|
||||
e->current_attack = area;
|
||||
e->attack_follows_movement = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Attack */
|
||||
if(attack && !player->current_attack) {
|
||||
/* Player attack */
|
||||
if(player->HP > 0 && attack && !player->current_attack) {
|
||||
frect_t hitbox = {
|
||||
-fix(5)/8, fix(5)/8, -fix(1)/4, fix(1)/4,
|
||||
};
|
||||
|
@ -446,14 +446,17 @@ int main(void)
|
|||
player->attack_follows_movement = true;
|
||||
}
|
||||
|
||||
game_update_effect_areas(&game, dt);
|
||||
/* Remove dead entities first as it will kill their attack areas */
|
||||
game_remove_dead_entities(&game);
|
||||
game_update_effect_areas(&game, dt);
|
||||
game_sort_entities(&game);
|
||||
game_update_particles(&game, dt);
|
||||
|
||||
game.time_total += dt;
|
||||
|
||||
if(game.time_victory == 0 && game.entity_count == 1)
|
||||
if(game.time_defeat == 0 && player->HP == 0)
|
||||
game.time_defeat = game.time_total;
|
||||
if(game.time_victory == 0 && player->HP > 0 && game.entity_count == 1)
|
||||
game.time_victory = game.time_total;
|
||||
|
||||
/* Visual pathfinding debug */
|
||||
|
|
|
@ -10,9 +10,14 @@ static bool damage_update(particle_damage_t *p, GUNUSED fixed_t dt)
|
|||
|
||||
static void damage_render(int x, int y, particle_damage_t *p)
|
||||
{
|
||||
extern bopti_image_t img_font_damage;
|
||||
int char_w = img_font_damage.width / 10;
|
||||
int char_h = img_font_damage.height;
|
||||
extern bopti_image_t img_font_damage_white;
|
||||
extern bopti_image_t img_font_damage_red;
|
||||
|
||||
bopti_image_t *img = (p->color == C_RED) ? &img_font_damage_red :
|
||||
&img_font_damage_white;
|
||||
|
||||
int char_w = img->width / 10;
|
||||
int char_h = img->height;
|
||||
|
||||
/* Determine number of characters */
|
||||
char str[16];
|
||||
|
@ -23,12 +28,22 @@ static void damage_render(int x, int y, particle_damage_t *p)
|
|||
|
||||
for(int i = 0; i < n; i++) {
|
||||
int offset = (char_w + 1) * (str[i] - '0');
|
||||
dsubimage(x, y, &img_font_damage, offset, 0, char_w, char_h,
|
||||
DIMAGE_NONE);
|
||||
dsubimage(x, y, img, offset, 0, char_w, char_h, DIMAGE_NONE);
|
||||
x += char_w;
|
||||
}
|
||||
}
|
||||
|
||||
static bool dash_update(particle_dash_t *p, GUNUSED fixed_t dt)
|
||||
{
|
||||
return p->particle.age >= 500;
|
||||
}
|
||||
|
||||
static void dash_render(int x, int y, particle_dash_t *p)
|
||||
{
|
||||
if(!p->frame) return;
|
||||
anim_frame_render(x, y, p->frame);
|
||||
}
|
||||
|
||||
//---
|
||||
// Generic functions
|
||||
//---
|
||||
|
@ -39,6 +54,8 @@ bool particle_update(particle_t *p, fixed_t dt)
|
|||
|
||||
if(p->type == PARTICLE_DAMAGE)
|
||||
return damage_update((void *)p, dt);
|
||||
if(p->type == PARTICLE_DASH)
|
||||
return dash_update((void *)p, dt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -47,4 +64,6 @@ void particle_render(int x, int y, particle_t const *p)
|
|||
{
|
||||
if(p->type == PARTICLE_DAMAGE)
|
||||
return damage_render(x, y, (void *)p);
|
||||
if(p->type == PARTICLE_DASH)
|
||||
return dash_render(x, y, (void *)p);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "fixed.h"
|
||||
#include "geometry.h"
|
||||
#include "anim.h"
|
||||
|
||||
#include <gint/defs/types.h>
|
||||
|
||||
|
@ -23,6 +24,7 @@ typedef struct {
|
|||
/* Particle types */
|
||||
enum {
|
||||
PARTICLE_DAMAGE = 0,
|
||||
PARTICLE_DASH = 1,
|
||||
};
|
||||
|
||||
/* Update time and layout for a particle (type-generic). */
|
||||
|
@ -40,5 +42,15 @@ typedef struct {
|
|||
particle_t particle;
|
||||
/* Damage value */
|
||||
int damage;
|
||||
/* Color (accepts C_RED; everything else is white) */
|
||||
uint16_t color;
|
||||
|
||||
} particle_damage_t;
|
||||
|
||||
/* Dash trail particles */
|
||||
typedef struct {
|
||||
particle_t particle;
|
||||
/* Source sprite */
|
||||
anim_frame_t const *frame;
|
||||
|
||||
} particle_dash_t;
|
||||
|
|
Loading…
Reference in New Issue