Flexible draw order; entity "base class" for all game objects

This commit is contained in:
Andrei Alexeyev 2018-04-13 22:13:48 +03:00
parent d3ffc7ff55
commit 8ef5ffd32c
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
39 changed files with 908 additions and 262 deletions

View file

@ -12,13 +12,14 @@
#include "global.h"
#include "stage.h"
#include "stagetext.h"
#include "entity.h"
static void ent_draw_boss(EntityInterface *ent);
Boss* create_boss(char *name, char *ani, char *dialog, complex pos) {
Boss *buf = malloc(sizeof(Boss));
memset(buf, 0, sizeof(Boss));
Boss *buf = calloc(1, sizeof(Boss));
buf->name = malloc(strlen(name) + 1);
strcpy(buf->name, name);
buf->name = strdup(name);
buf->pos = pos;
char strbuf[strlen(ani) + sizeof("boss/")];
@ -30,6 +31,11 @@ Boss* create_boss(char *name, char *ani, char *dialog, complex pos) {
}
buf->birthtime = global.frames;
buf->ent.draw_layer = LAYER_BOSS;
buf->ent.draw_func = ent_draw_boss;
ent_register(&buf->ent, ENT_BOSS);
return buf;
}
@ -184,8 +190,6 @@ static int boss_glow(Projectile *p, int t) {
static Projectile* spawn_boss_glow(Boss *boss, Color clr, int timeout) {
// XXX: memdup is required because the Sprite returned by animation_get_frame is only temporarily valid
// XXX: this used to set size to 9000 to ensure its below everything but now that does not work anymore
// maybe we can use a custom insertion rule?
return PARTICLE(
.sprite_ptr = memdup(aniplayer_get_frame(&boss->ani), sizeof(Sprite)),
// this is in sync with the boss position oscillation
@ -195,6 +199,7 @@ static Projectile* spawn_boss_glow(Boss *boss, Color clr, int timeout) {
.draw_rule = BossGlow,
.args = { timeout },
.angle = M_PI * 2 * frand(),
.layer = LAYER_PARTICLE_LOW,
);
}
@ -244,7 +249,11 @@ void draw_boss_background(Boss *boss) {
r_mat_pop();
}
void draw_boss(Boss *boss) {
static void ent_draw_boss(EntityInterface *ent) {
// TODO: separate overlay from this
Boss *boss = ENT_CAST(ent, Boss);
float red = 0.5*exp(-0.5*(global.frames-boss->lastdamageframe));
if(red > 1)
red = 0;
@ -370,7 +379,7 @@ void draw_boss(Boss *boss) {
}
}
r_color3(1,1,1);
// r_color3(1,1,1);
}
}
@ -771,6 +780,7 @@ static void free_attack(Attack *a) {
void free_boss(Boss *boss) {
del_ref(boss);
ent_unregister(&boss->ent);
for(int i = 0; i < boss->acount; i++)
free_attack(&boss->attacks[i]);

View file

@ -14,6 +14,7 @@
#include "aniplayer.h"
#include "color.h"
#include "projectile.h"
#include "entity.h"
enum {
ATTACK_START_DELAY = 60,
@ -99,8 +100,7 @@ typedef struct Attack {
} Attack;
typedef struct Boss {
// XXX: temporary hack for binary compatibility of pos with Enemy and Projectile structs
OBJECT_INTERFACE(struct Boss);
ENTITY_INTERFACE_NAMED(struct Boss, ent);
complex pos;
Attack *attacks;
@ -129,7 +129,6 @@ void free_boss(Boss *boss) attr_nonnull(1);
void process_boss(Boss **boss) attr_nonnull(1);
void draw_extraspell_bg(Boss *boss, int time) attr_nonnull(1);
void draw_boss(Boss *boss) attr_nonnull(1);
void draw_boss_background(Boss *boss) attr_nonnull(1);
Attack* boss_add_attack(Boss *boss, AttackType type, char *name, float timeout, int hp, BossRule rule, BossRule draw_rule)

14
src/drawlayers.inc.h Normal file
View file

@ -0,0 +1,14 @@
LAYER(PLAYER_SHOT)
LAYER(PARTICLE_LOW)
LAYER(ITEM)
LAYER(PLAYER_SLAVE)
LAYER(PLAYER)
LAYER(LASER_LOW)
LAYER(BULLET)
LAYER(PARTICLE_HIGH)
LAYER(ENEMY)
LAYER(LASER_HIGH)
LAYER(BOSS)
LAYER(PLAYER_FOCUS)
#undef LAYER

View file

@ -17,6 +17,7 @@
#include "aniplayer.h"
#include "stageobjects.h"
#include "glm.h"
#include "entity.h"
#ifdef create_enemy_p
#undef create_enemy_p
@ -30,6 +31,8 @@ Enemy* _enemy_attach_dbginfo(Enemy *e, DebugInfo *dbg) {
}
#endif
static void ent_draw_enemy(EntityInterface *ent);
Enemy *create_enemy_p(Enemy **enemies, complex pos, int hp, EnemyVisualRule visual_rule, EnemyLogicRule logic_rule,
complex a1, complex a2, complex a3, complex a4) {
if(IN_DRAW_CODE) {
@ -56,6 +59,10 @@ Enemy *create_enemy_p(Enemy **enemies, complex pos, int hp, EnemyVisualRule visu
e->args[2] = a3;
e->args[3] = a4;
e->ent.draw_layer = LAYER_ENEMY;
e->ent.draw_func = ent_draw_enemy;
ent_register(&e->ent, ENT_ENEMY);
e->logic_rule(e, EVENT_BIRTH);
return e;
}
@ -81,6 +88,7 @@ void* _delete_enemy(List **enemies, List* enemy, void *arg) {
e->logic_rule(e, EVENT_DEATH);
del_ref(enemy);
ent_unregister(&e->ent);
objpool_release(stage_object_pools.enemies, (ObjectInterface*)list_unlink(enemies, enemy));
return NULL;
@ -94,41 +102,32 @@ void delete_enemies(Enemy **enemies) {
list_foreach(enemies, _delete_enemy, NULL);
}
static void draw_enemy(Enemy *e) {
static void ent_draw_enemy(EntityInterface *ent) {
Enemy *e = ENT_CAST(ent, Enemy);
if(!e->visual_rule) {
return;
}
#ifdef ENEMY_DEBUG
static Enemy prev_state;
memcpy(&prev_state, e, sizeof(Enemy));
#endif
if(e->alpha < 1) {
r_color4(1, 1, 1, e->alpha);
}
e->visual_rule(e, global.frames - e->birthtime, true);
#ifdef ENEMY_DEBUG
if(memcmp(&prev_state, e, sizeof(Enemy))) {
set_debug_info(&e->debug);
log_fatal("Enemy modified its own state in draw rule");
}
#else
e->visual_rule(e, global.frames - e->birthtime, true);
#endif
}
void draw_enemies(Enemy *enemies) {
Enemy *e;
bool reset = false;
for(e = enemies; e; e = e->next) {
if(e->visual_rule) {
if(e->alpha < 1) {
r_color4(1,1,1,e->alpha);
reset = true;
}
draw_enemy(e);
if(reset) {
r_color4(1,1,1,1);
}
}
}
}
void killall(Enemy *enemies) {
Enemy *e;

View file

@ -12,6 +12,7 @@
#include "util.h"
#include "projectile.h"
#include "objectpool.h"
#include "entity.h"
#ifdef DEBUG
#define ENEMY_DEBUG
@ -27,7 +28,7 @@ enum {
};
struct Enemy {
OBJECT_INTERFACE(Enemy);
ENTITY_INTERFACE_NAMED(Enemy, ent);
complex pos;
complex pos0;
@ -66,7 +67,6 @@ Enemy *create_enemy_p(
#endif
void delete_enemy(Enemy **enemies, Enemy* enemy);
void draw_enemies(Enemy *enemies);
void delete_enemies(Enemy **enemies);
void process_enemies(Enemy **enemies);

113
src/entity.c Normal file
View file

@ -0,0 +1,113 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2018, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
*/
#include "taisei.h"
#include "entity.h"
#include "util.h"
#include "renderer/api.h"
static struct {
EntityInterface **array;
uint num;
uint capacity;
uint32_t total_spawns;
} entities;
#define FOR_EACH_ENT(ent) for(EntityInterface **_ent = entities.array, *ent = *entities.array; _ent < entities.array + entities.num; ent = *(++_ent))
void ent_init(void) {
memset(&entities, 0, sizeof(entities));
entities.capacity = 4096;
entities.array = calloc(entities.capacity, sizeof(EntityInterface*));
}
void ent_shutdown(void) {
if(entities.num) {
log_warn("%u entities were not properly unregistered", entities.num);
}
FOR_EACH_ENT(ent) {
ent_unregister(ent);
}
free(entities.array);
}
void ent_register(EntityInterface *ent, EntityType type) {
assert(type > _ENT_TYPE_ENUM_BEGIN && type < _ENT_TYPE_ENUM_END);
ent->type = type;
ent->index = entities.num++;
ent->spawn_id = ++entities.total_spawns;
if(ent->spawn_id == 0) {
// This is not really an error, but it may result in weird draw order
// in some corner cases. If this overflow ever actually occurs, though,
// then you've probably got much bigger problems.
log_debug("spawn_id just overflowed. You might be spawning stuff waaaay too often");
}
if(entities.capacity < entities.num) {
entities.capacity *= 2;
entities.array = realloc(entities.array, entities.capacity * sizeof(EntityInterface*));
}
entities.array[ent->index] = ent;
assert(ent->index < entities.num);
assert(entities.array[ent->index] == ent);
}
void ent_unregister(EntityInterface *ent) {
EntityInterface *sub = entities.array[--entities.num];
assert(ent->index <= entities.num);
assert(entities.array[ent->index] == ent);
entities.array[sub->index = ent->index] = sub;
}
static int ent_cmp(const void *ptr1, const void *ptr2) {
const EntityInterface *ent1 = *(const EntityInterface**)ptr1;
const EntityInterface *ent2 = *(const EntityInterface**)ptr2;
int r = (ent1->draw_layer > ent2->draw_layer) - (ent1->draw_layer < ent2->draw_layer);
if(r == 0) {
// Same layer? Put whatever spawned later on top, then.
r = (ent1->spawn_id > ent2->spawn_id) - (ent1->spawn_id < ent2->spawn_id);
}
return r;
}
void ent_draw(EntityPredicate predicate) {
qsort(entities.array, entities.num, sizeof(EntityInterface*), ent_cmp);
if(predicate) {
FOR_EACH_ENT(ent) {
ent->index = _ent - entities.array;
assert(entities.array[ent->index] == ent);
if(ent->draw_func && predicate(ent)) {
r_state_push();
ent->draw_func(ent);
r_state_pop();
}
}
} else {
FOR_EACH_ENT(ent) {
ent->index = _ent - entities.array;
assert(entities.array[ent->index] == ent);
if(ent->draw_func) {
r_state_push();
ent->draw_func(ent);
r_state_pop();
}
}
}
}

112
src/entity.h Normal file
View file

@ -0,0 +1,112 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2018, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
*/
#pragma once
#include "taisei.h"
#include "objectpool.h"
#define LAYER_LOW_BITS 16
#define LAYER_LOW_MASK ((1 << LAYER_LOW_BITS) - 1)
typedef uint32_t drawlayer_t;
typedef uint16_t drawlayer_low_t;
typedef enum DrawLayerID {
LAYER_ID_NONE,
#define LAYER(x) LAYER_ID_##x,
#include "drawlayers.inc.h"
} DrawLayerID;
typedef enum DrawLayer {
#define LAYER(x) LAYER_##x = (LAYER_ID_##x << LAYER_LOW_BITS),
#include "drawlayers.inc.h"
} DrawLayer;
// NOTE: you can bit-or a drawlayer_low_t value with one of the LAYER_x constants
// for sub-layer ordering.
#define ENT_TYPES \
ENT_TYPE(Projectile, ENT_PROJECTILE) \
ENT_TYPE(Laser, ENT_LASER) \
ENT_TYPE(Enemy, ENT_ENEMY) \
ENT_TYPE(Boss, ENT_BOSS) \
ENT_TYPE(Player, ENT_PLAYER) \
ENT_TYPE(Item, ENT_ITEM) \
typedef enum EntityType {
_ENT_TYPE_ENUM_BEGIN,
#define ENT_TYPE(typename, id) id, _ENT_TYPEID_##typename = id,
ENT_TYPES
#undef ENT_TYPE
_ENT_TYPE_ENUM_END,
} EntityType;
typedef struct EntityInterface EntityInterface;
typedef struct EntityListNode EntityListNode;
typedef void (*EntityDrawFunc)(EntityInterface *ent);
typedef bool (*EntityPredicate)(EntityInterface *ent);
#define ENTITY_INTERFACE_BASE(typename) struct { \
OBJECT_INTERFACE(typename); \
EntityType type; \
EntityDrawFunc draw_func; \
drawlayer_t draw_layer; \
uint32_t spawn_id; \
uint index; \
}
#define ENTITY_INTERFACE(typename) union { \
EntityInterface entity_interface; \
ENTITY_INTERFACE_BASE(typename); \
}
#define ENTITY_INTERFACE_NAMED(typename, name) union { \
OBJECT_INTERFACE(typename); \
EntityInterface entity_interface; \
EntityInterface name; \
}
struct EntityInterface {
ENTITY_INTERFACE_BASE(EntityInterface);
};
static inline attr_must_inline const char* ent_type_name(EntityType type) {
switch(type) {
#define ENT_TYPE(typename, id) case id: return #id;
ENT_TYPES
#undef ENT_TYPE
default: return "ENT_INVALID";
}
UNREACHABLE;
}
#define ENT_TYPE_ID(typename) (_ENT_TYPEID_##typename)
#ifdef USE_GNU_EXTENSIONS
#define ENT_CAST(ent, typename) (__extension__({ \
static_assert(__builtin_types_compatible_p(EntityInterface, __typeof__(*(ent))), \
"Expression is not an EntityInterface pointer"); \
static_assert(__builtin_types_compatible_p(EntityInterface, __typeof__(((typename){}).entity_interface)), \
#typename " doesn't implement EntityInterface"); \
static_assert(__builtin_offsetof(typename, entity_interface) == 0, \
"entity_interface has non-zero offset in " #typename); \
if(ent->type != _ENT_TYPEID_##typename) { \
log_fatal("Invalid entity cast from %s to " #typename, ent_type_name(ent->type)); \
} \
(typename *)(ent); \
}))
#else
#define ENT_CAST(ent, typename) ((typename *)(ent))
#endif
void ent_init(void);
void ent_shutdown(void);
void ent_register(EntityInterface *ent, EntityType type);
void ent_unregister(EntityInterface *ent);
void ent_draw(EntityPredicate predicate);

View file

@ -424,6 +424,14 @@ void hashtable_copyfunc_ptr(void **dst, void *src) {
*dst = src;
}
hash_t hashtable_hashfunc_ptr(void *val) {
hash_t x = (uintptr_t)val & ((1u << 31u) - 1u);
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
/*
* Diagnostic functions
*/

View file

@ -64,6 +64,7 @@ void hashtable_unset_deferred_string(Hashtable *ht, const char *key);
void* hashtable_iter_free_data(void *key, void *data, void *arg);
bool hashtable_cmpfunc_ptr(void *p1, void *p2);
void hashtable_copyfunc_ptr(void **dst, void *src);
hash_t hashtable_hashfunc_ptr(void *val);
int hashtable_test(void);

View file

@ -30,6 +30,22 @@ static Sprite* item_sprite(ItemType type) {
return get_sprite(map[type]);
}
static void ent_draw_item(EntityInterface *ent) {
Item *i = ENT_CAST(ent, Item);
Color c = rgba(1, 1, 1,
i->type == BPoint && !i->auto_collect
? clamp(2.0 - (global.frames - i->birthtime) / 60.0, 0.1, 1.0)
: 1.0
);
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = item_sprite(i->type),
.pos = { creal(i->pos), cimag(i->pos) },
.color = c,
});
}
static int item_prio(List *litem) {
Item *item = (Item*)litem;
return item->type;
@ -45,6 +61,9 @@ Item* create_item(complex pos, complex v, ItemType type) {
// type = 1 + floor(Life * frand());
Item *i = (Item*)objpool_acquire(stage_object_pools.items);
// FIXME: use simpler/faster insertions when v1.2 compat is not needed
// list_push(&global.items, i);
list_insert_at_priority_tail(&global.items, i, type, item_prio);
i->pos = pos;
@ -54,10 +73,15 @@ Item* create_item(complex pos, complex v, ItemType type) {
i->auto_collect = 0;
i->type = type;
i->ent.draw_layer = LAYER_ITEM | i->type;
i->ent.draw_func = ent_draw_item;
ent_register(&i->ent, ENT_ITEM);
return i;
}
void delete_item(Item *item) {
ent_unregister(&item->ent);
objpool_release(stage_object_pools.items, (ObjectInterface*)list_unlink(&global.items, item));
}
@ -72,22 +96,6 @@ Item* create_bpoint(complex pos) {
return i;
}
void draw_items(void) {
for(Item *i = global.items; i; i = i->next) {
Color c = rgba(1, 1, 1,
i->type == BPoint && !i->auto_collect
? clamp(2.0 - (global.frames - i->birthtime) / 60.0, 0.1, 1.0)
: 1.0
);
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = item_sprite(i->type),
.pos = { creal(i->pos), cimag(i->pos) },
.color = c,
});
}
}
void delete_items(void) {
objpool_release_list(stage_object_pools.items, (List**)&global.items);
}

View file

@ -12,6 +12,7 @@
#include "util.h"
#include "resource/texture.h"
#include "objectpool.h"
#include "entity.h"
typedef struct Item Item;
@ -28,7 +29,7 @@ typedef enum {
} ItemType;
struct Item {
OBJECT_INTERFACE(Item);
ENTITY_INTERFACE_NAMED(Item, ent);
int birthtime;
complex pos;
@ -42,7 +43,6 @@ struct Item {
Item *create_item(complex pos, complex v, ItemType type);
void delete_item(Item *item);
void draw_items(void);
void delete_items(void);
Item* create_bpoint(complex pos);

View file

@ -64,6 +64,8 @@ void lasers_free(void) {
r_vertex_buffer_destroy(&lasers.vbuf);
}
static void ent_draw_laser(EntityInterface *ent);
Laser *create_laser(complex pos, float time, float deathtime, Color color, LaserPosRule prule, LaserLogicRule lrule, complex a0, complex a1, complex a2, complex a3) {
Laser *l = (Laser*)list_push(&global.lasers, objpool_acquire(stage_object_pools.lasers));
@ -87,10 +89,13 @@ Laser *create_laser(complex pos, float time, float deathtime, Color color, Laser
l->width_exponent = 1.0;
l->speed = 1;
l->timeshift = 0;
l->in_background = false;
l->dead = false;
l->unclearable = false;
l->ent.draw_layer = LAYER_LASER_HIGH;
l->ent.draw_func = ent_draw_laser;
ent_register(&l->ent, ENT_LASER);
if(l->lrule)
l->lrule(l, EVENT_BIRTH);
@ -184,37 +189,31 @@ static void draw_laser_curve_generic(Laser *l) {
r_draw(PRIM_TRIANGLE_FAN, 0, 4, NULL, instances, 0);
}
void draw_lasers(int bgpass) {
Laser *laser;
VertexArray *varr_saved = r_vertex_array_current();
ShaderProgram *prog_saved = r_shader_current();
static void ent_draw_laser(EntityInterface *ent) {
Laser *laser = ENT_CAST(ent, Laser);
r_texture(0, "part/lasercurve");
r_blend(BLEND_ADD);
for(laser = global.lasers; laser; laser = laser->next) {
if(bgpass != laser->in_background) {
continue;
}
if(laser->shader) {
// Specialized lasers work with either vertex array,
// provided that the static models buffer is attached to it.
// We'll only ever draw the first quad, and only care about
// attributes 0 and 2 (vec3 position, vec2 uv)
if(laser->shader) {
// Specialized lasers work with either vertex array,
// provided that the static models buffer is attached to it.
// We'll only ever draw the first quad, and only care about
// attributes 0 and 2 (vec3 position, vec2 uv)
r_shader_ptr(laser->shader);
draw_laser_curve_specialized(laser);
} else {
VertexArray *va = r_vertex_array_current();
if(va != &lasers.varr && va != r_vertex_array_static_models()) {
r_vertex_array(&lasers.varr);
r_shader_ptr(lasers.shader_generic);
draw_laser_curve_generic(laser);
}
}
r_blend(BLEND_ALPHA);
r_shader_ptr(prog_saved);
r_vertex_array(varr_saved);
r_color4(1, 1, 1, 1);
r_shader_ptr(laser->shader);
draw_laser_curve_specialized(laser);
} else {
r_vertex_array(&lasers.varr);
r_shader_ptr(lasers.shader_generic);
draw_laser_curve_generic(laser);
}
}
void* _delete_laser(List **lasers, List *laser, void *arg) {
@ -224,6 +223,7 @@ void* _delete_laser(List **lasers, List *laser, void *arg) {
l->lrule(l, EVENT_DEATH);
del_ref(laser);
ent_unregister(&l->ent);
objpool_release(stage_object_pools.lasers, (ObjectInterface*)list_unlink(lasers, laser));
return NULL;
}

View file

@ -12,6 +12,7 @@
#include "util.h"
#include "projectile.h"
#include "resource/shader_program.h"
#include "entity.h"
typedef struct Laser Laser;
@ -19,7 +20,7 @@ typedef complex (*LaserPosRule)(Laser* l, float time);
typedef void (*LaserLogicRule)(Laser* l, int time);
struct Laser {
OBJECT_INTERFACE(Laser);
ENTITY_INTERFACE_NAMED(Laser, ent);
complex pos;
@ -42,8 +43,6 @@ struct Laser {
LaserPosRule prule;
LaserLogicRule lrule;
int in_background;
complex args[4];
bool unclearable;
@ -62,7 +61,6 @@ Laser *create_laserline(complex pos, complex dir, float charge, float dur, Color
Laser *create_laserline_ab(complex a, complex b, float width, float charge, float dur, Color clr);
Laser *create_laser(complex pos, float time, float deathtime, Color color, LaserPosRule prule, LaserLogicRule lrule, complex a0, complex a1, complex a2, complex a3);
void draw_lasers(int);
void delete_lasers(void);
void process_lasers(void);

View file

@ -108,11 +108,11 @@ static List* list_insert_at_priority(List **list_head, List *elem, int prio, Lis
return elem;
}
List* list_insert_at_priority_head(List **dest, List *elem, int prio, ListPriorityFunc prio_func){
List* list_insert_at_priority_head(List **dest, List *elem, int prio, ListPriorityFunc prio_func) {
return list_insert_at_priority(dest, elem, prio, prio_func, true);
}
List* list_insert_at_priority_tail(List **dest, List *elem, int prio, ListPriorityFunc prio_func){
List* list_insert_at_priority_tail(List **dest, List *elem, int prio, ListPriorityFunc prio_func) {
return list_insert_at_priority(dest, elem, prio, prio_func, false);
}

View file

@ -71,6 +71,7 @@ taisei_src = files(
'difficulty.c',
'ending.c',
'enemy.c',
'entity.c',
'events.c',
'fbo.c',
'framerate.c',

View file

@ -15,6 +15,7 @@
#include "plrmodes.h"
#include "stage.h"
#include "stagetext.h"
#include "entity.h"
void player_init(Player *plr) {
memset(plr, 0, sizeof(Player));
@ -35,6 +36,9 @@ void player_stage_pre_init(Player *plr) {
plrmode_preload(plr->mode);
}
static void ent_draw_player(EntityInterface *ent);
static Enemy* player_spawn_focus_circle(void);
void player_stage_post_init(Player *plr) {
assert(plr->mode != NULL);
@ -49,12 +53,21 @@ void player_stage_post_init(Player *plr) {
}
aniplayer_create(&plr->ani, get_ani(plr->mode->character->player_sprite_name), "main");
plr->ent.draw_layer = LAYER_PLAYER;
plr->ent.draw_func = ent_draw_player;
ent_register(&plr->ent, ENT_PLAYER);
plr->focus_circle = player_spawn_focus_circle();
}
void player_free(Player *plr) {
if(plr->mode->procs.free) {
plr->mode->procs.free(plr);
}
ent_unregister(&plr->ent);
delete_enemy(&plr->focus_circle, plr->focus_circle);
}
static void player_full_power(Player *plr) {
@ -107,44 +120,66 @@ void player_move(Player *plr, complex delta) {
}
}
void player_draw(Player* plr) {
static void ent_draw_player(EntityInterface *ent) {
Player *plr = ENT_CAST(ent, Player);
// FIXME: death animation?
if(plr->deathtime > global.frames)
return;
draw_enemies(plr->slaves);
r_mat_push();
r_mat_translate(creal(plr->pos), cimag(plr->pos), 0);
if(plr->focus) {
r_draw_sprite(&(SpriteParams) {
.sprite = "fairy_circle",
.rotation.angle = DEG2RAD * global.frames * 10,
.color = rgba(1, 1, 1, 0.2 * (clamp(plr->focus, 0, 15) / 15.0)),
});
}
if(global.frames - abs(plr->recovery) < 0 && (global.frames/8)&1) {
r_color4(0.4, 0.4, 1.0, 0.9);
}
Sprite *spr = aniplayer_get_frame(&plr->ani);
if(plr->focus) {
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = spr,
.pos = { 0, 0 },
.sprite = "fairy_circle",
.rotation.angle = DEG2RAD * global.frames * 10,
.color = rgba(1, 1, 1, 0.2 * (clamp(plr->focus, 0, 15) / 15.0)),
.pos = { creal(plr->pos), cimag(plr->pos) },
});
r_color3(1, 1, 1);
}
if(plr->focus) {
r_draw_sprite(&(SpriteParams) {
.sprite = "focus",
.rotation.angle = DEG2RAD * global.frames * -1,
.color = rgba(1, 1, 1, plr->focus / 30.0),
});
}
Color c;
r_mat_pop();
if(global.frames - abs(plr->recovery) < 0 && (global.frames/8)&1) {
c = rgba(0.4, 0.4, 1.0, 0.9);
} else {
c = rgba(1.0, 1.0, 1.0, 1.0);
}
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = aniplayer_get_frame(&plr->ani),
.pos = { creal(plr->pos), cimag(plr->pos) },
.color = c,
});
}
static int player_focus_circle_logic(Enemy *e, int t) {
return 1;
}
static void player_focus_circle_visual(Enemy *e, int t, bool render) {
if(!render || !creal(e->args[0])) {
return;
}
r_draw_sprite(&(SpriteParams) {
.sprite = "focus",
.rotation.angle = DEG2RAD * global.frames,
.color = rgba(1, 1, 1, creal(e->args[0])),
.pos = { creal(e->pos), cimag(e->pos) },
});
r_draw_sprite(&(SpriteParams) {
.sprite = "focus",
.rotation.angle = DEG2RAD * global.frames * -1,
.color = rgba(1, 1, 1, creal(e->args[0])),
.pos = { creal(e->pos), cimag(e->pos) },
});
}
static Enemy* player_spawn_focus_circle(void) {
Enemy *f = NULL;
create_enemy_p(&f, 0, ENEMY_IMMUNE, player_focus_circle_visual, player_focus_circle_logic, 0, 0, 0, 0);
f->ent.draw_layer = LAYER_PLAYER_FOCUS;
return f;
}
static void player_fail_spell(Player *plr) {
@ -184,6 +219,7 @@ void player_logic(Player* plr) {
process_enemies(&plr->slaves);
aniplayer_update(&plr->ani);
if(plr->deathtime < -1) {
plr->deathtime++;
plr->pos -= I;
@ -193,6 +229,9 @@ void player_logic(Player* plr) {
plr->focus = approach(plr->focus, (plr->inputflags & INFLAG_FOCUS) ? 30 : 0, 1);
plr->focus_circle->pos = plr->pos;
plr->focus_circle->args[0] = plr->focus / 30.0;
if(plr->mode->procs.think) {
plr->mode->procs.think(plr);
}

View file

@ -18,6 +18,7 @@
#include "gamepad.h"
#include "aniplayer.h"
#include "resource/animation.h"
#include "entity.h"
enum {
PLR_MAX_POWER = 400,
@ -55,7 +56,11 @@ enum {
INFLAGS_MOVE = INFLAG_UP | INFLAG_DOWN | INFLAG_LEFT | INFLAG_RIGHT
};
typedef struct {
typedef struct Player Player;
struct Player {
ENTITY_INTERFACE_NAMED(Player, ent);
complex pos;
complex deathpos;
short focus;
@ -80,6 +85,7 @@ typedef struct {
struct PlayerMode *mode;
AniPlayer ani;
Enemy *slaves;
Enemy *focus_circle;
int inputflags;
bool gamepadmove;
@ -93,7 +99,7 @@ typedef struct {
#ifdef PLR_DPS_STATS
int total_dmg;
#endif
} Player;
};
// this is used by both player and replay code
enum {
@ -124,7 +130,6 @@ void player_stage_post_init(Player *plr);
void player_free(Player *plr);
void player_draw(Player*);
void player_logic(Player*);
bool player_should_shoot(Player *plr, bool extra);

View file

@ -518,6 +518,7 @@ static void marisa_laser_respawn_slaves(Player *plr, short npow) {
MarisaLaserData *ld = calloc(1, sizeof(MarisaLaserData));
ld->prev_pos = e->pos + plr->pos;
e->args[3] = add_ref(ld);
e->ent.draw_layer = LAYER_PLAYER_SLAVE;
}
}
}
@ -532,6 +533,7 @@ static void marisa_laser_power(Player *plr, short npow) {
static void marisa_laser_init(Player *plr) {
create_enemy_p(&plr->slaves, 0, ENEMY_IMMUNE, marisa_laser_renderer_visual, marisa_laser_renderer, 0, 0, 0, 0);
plr->slaves->ent.draw_layer = LAYER_PLAYER_SHOT;
marisa_laser_respawn_slaves(plr, plr->power);
}

View file

@ -26,7 +26,6 @@ static void marisa_star_trail_draw(Projectile *p, int t) {
r_mat_rotate_deg(p->angle*180/M_PI+90, 0, 0, 1);
r_mat_scale(s, s, 1);
ProjDrawCore(p, clr);
r_color4(1,1,1,1);
r_mat_pop();
}
@ -46,6 +45,7 @@ static int marisa_star_projectile(Projectile *p, int t) {
.draw_rule = marisa_star_trail_draw,
.angle = p->angle,
.flags = PFLAG_DRAWADD | PFLAG_NOREFLECT,
.layer = LAYER_PARTICLE_LOW,
);
if(t == EVENT_DEATH) {
@ -58,6 +58,7 @@ static int marisa_star_projectile(Projectile *p, int t) {
.args = { 40, 2 },
.angle = frand() * 2 * M_PI,
.flags = PFLAG_DRAWADD | PFLAG_NOREFLECT,
.layer = LAYER_PARTICLE_HIGH,
);
}
@ -211,9 +212,6 @@ static void marisa_star_orbit_visual(Enemy *e, int t, bool render) {
r_mat_scale(0.6,0.6,1);
draw_sprite_batched(0,0,"part/lightningball");
r_mat_pop();
r_blend(BLEND_ALPHA);
r_color4(1,1,1,1);
}
@ -284,6 +282,10 @@ static void marisa_star_respawn_slaves(Player *plr, short npow) {
create_enemy_p(&plr->slaves, 30, ENEMY_IMMUNE, marisa_common_slave_visual, marisa_star_slave, 25+30.0*I, -0.6-2.0*I, 2-0.1*I, dmg);
create_enemy_p(&plr->slaves, -30, ENEMY_IMMUNE, marisa_common_slave_visual, marisa_star_slave, -25+30.0*I, 0.6-2.0*I, -2-0.1*I, dmg);
}
for(Enemy *e = plr->slaves; e; e = e->next) {
e->ent.draw_layer = LAYER_PLAYER_SLAVE;
}
}
static void marisa_star_power(Player *plr, short npow) {

View file

@ -73,6 +73,7 @@ static void spawn_stardust(complex pos, Color clr, int timeout, complex v) {
.args = { timeout, v, 0.2 + 0.1 * frand(), 1 },
.angle = M_PI*2*frand(),
.flags = PFLAG_DRAWADD | PFLAG_NOREFLECT,
.layer = LAYER_PARTICLE_LOW | 1,
);
}
@ -117,6 +118,7 @@ static void myon_spawn_trail(Enemy *e, int t) {
.draw_rule = Shrink,
.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
.angle = M_PI*2*frand(),
.layer = LAYER_PARTICLE_LOW,
);
spawn_stardust(pos, rgba(1, 1, 1, 0.1), 60, stardust_v);
@ -338,7 +340,6 @@ static void youmu_mirror_shot(Player *plr) {
}
}
static int youmu_split(Enemy *e, int t) {
if(t < 0)
return 1;
@ -390,7 +391,8 @@ static void youmu_mirror_bomb(Player *plr) {
}
static void youmu_mirror_init(Player *plr) {
create_enemy_p(&plr->slaves, 40.0*I, ENEMY_IMMUNE, NULL, youmu_mirror_myon, 0, 0, 0, 0);
Enemy *myon = create_enemy_p(&plr->slaves, 40.0*I, ENEMY_IMMUNE, NULL, youmu_mirror_myon, 0, 0, 0, 0);
myon->ent.draw_layer = LAYER_PLAYER_SLAVE;
}
static double youmu_mirror_speed_mod(Player *plr, double speed) {
@ -429,12 +431,12 @@ PlayerMode plrmode_youmu_a = {
.character = &character_youmu,
.shot_mode = PLR_SHOT_YOUMU_MIRROR,
.procs = {
.bomb = youmu_mirror_bomb,
.bomb = youmu_mirror_bomb,
.speed_mod = youmu_mirror_speed_mod,
.bomb_shader = youmu_mirror_shader,
.bombbg = youmu_common_bombbg,
.shot = youmu_mirror_shot,
.init = youmu_mirror_init,
.preload = youmu_mirror_preload,
.shot = youmu_mirror_shot,
.init = youmu_mirror_init,
.preload = youmu_mirror_preload,
},
};

View file

@ -81,6 +81,7 @@ static Projectile* youmu_homing_trail(Projectile *p, complex v, int to) {
.args = { to, v },
.flags = PFLAG_NOREFLECT,
.shader_ptr = p->shader,
.layer = LAYER_PARTICLE_LOW,
);
}
@ -122,7 +123,11 @@ static Projectile* youmu_trap_trail(Projectile *p, complex v, int t) {
static int youmu_trap(Projectile *p, int t) {
if(t == EVENT_DEATH) {
PARTICLE("blast", p->pos, 0, blast_timeout, { 15 }, .draw_rule = Blast, .flags = PFLAG_REQUIREDPARTICLE);
PARTICLE("blast", p->pos, 0, blast_timeout, { 15 },
.draw_rule = Blast,
.flags = PFLAG_REQUIREDPARTICLE,
.layer = LAYER_PARTICLE_LOW,
);
return 1;
}
@ -140,8 +145,17 @@ static int youmu_trap(Projectile *p, int t) {
p->shader_custom_param = charge;
if(!(global.plr.inputflags & INFLAG_FOCUS)) {
PARTICLE("blast", p->pos, 0, blast_timeout, { 20 }, .draw_rule = Blast, .flags = PFLAG_REQUIREDPARTICLE);
PARTICLE("blast", p->pos, 0, blast_timeout, { 23 }, .draw_rule = Blast, .flags = PFLAG_REQUIREDPARTICLE);
PARTICLE("blast", p->pos, 0, blast_timeout, { 20 },
.draw_rule = Blast,
.flags = PFLAG_REQUIREDPARTICLE,
.layer = LAYER_PARTICLE_LOW,
);
PARTICLE("blast", p->pos, 0, blast_timeout, { 23 },
.draw_rule = Blast,
.flags = PFLAG_REQUIREDPARTICLE,
.layer = LAYER_PARTICLE_LOW,
);
int cnt = rint(creal(p->args[2]) * (0.25 + 0.75 * charge));
int dmg = cimag(p->args[2]);

View file

@ -23,7 +23,7 @@ static ProjArgs defaults_proj = {
.color = RGB(1, 1, 1),
.blend = BLEND_ALPHA,
.shader = "sprite_bullet",
.insertion_rule = proj_insert_sizeprio,
.layer = LAYER_BULLET,
};
static ProjArgs defaults_part = {
@ -34,8 +34,7 @@ static ProjArgs defaults_part = {
.color = RGB(1, 1, 1),
.blend = BLEND_ALPHA,
.shader = "sprite_default",
.insertion_rule = list_append,
// .insertion_rule = proj_insert_sizeprio,
.layer = LAYER_PARTICLE_HIGH,
};
static void process_projectile_args(ProjArgs *args, ProjArgs *defaults) {
@ -81,13 +80,17 @@ static void process_projectile_args(ProjArgs *args, ProjArgs *defaults) {
args->color = defaults->color;
}
if(!args->insertion_rule) {
args->insertion_rule = defaults->insertion_rule;
}
if(!args->max_viewport_dist && (args->type == Particle || args->type >= PlrProj)) {
args->max_viewport_dist = 300;
}
if(!args->layer) {
if(args->type >= PlrProj) {
args->layer = LAYER_PLAYER_SHOT;
} else {
args->layer = defaults->layer;
}
}
}
static double projectile_rect_area(Projectile *p) {
@ -108,7 +111,10 @@ static void projectile_size(Projectile *p, double *w, double *h) {
}
}
static void ent_draw_projectile(EntityInterface *ent);
static int projectile_sizeprio_func(List *vproj) {
// FIXME: remove this when v1.2 compat is not needed
Projectile *proj = (Projectile*)vproj;
if(proj->priority_override) {
@ -119,29 +125,10 @@ static int projectile_sizeprio_func(List *vproj) {
}
List* proj_insert_sizeprio(List **dest, List *elem) {
// FIXME: remove this when v1.2 compat is not needed
return list_insert_at_priority_tail(dest, elem, projectile_sizeprio_func(elem), projectile_sizeprio_func);
}
static int projectile_colorprio_func(List *vproj) {
Projectile *p = (Projectile*)vproj;
Color c = p->color;
uint32_t c32 = 0;
float r, g, b, a;
// convert color to 32bit
parse_color(c, &r, &g, &b, &a);
c32 |= (((c & CLRMASK_R) >> CLR_R) & 0xFF) << 0;
c32 |= (((c & CLRMASK_G) >> CLR_G) & 0xFF) << 8;
c32 |= (((c & CLRMASK_B) >> CLR_B) & 0xFF) << 16;
return (int)c32;
}
List* proj_insert_colorprio(List **dest, List *elem) {
return list_insert_at_priority_head(dest, elem, projectile_colorprio_func(elem), projectile_colorprio_func);
// return list_push(dest, elem);
}
static Projectile* _create_projectile(ProjArgs *args) {
if(IN_DRAW_CODE) {
log_fatal("Tried to spawn a projectile while in drawing code");
@ -168,12 +155,49 @@ static Projectile* _create_projectile(ProjArgs *args) {
memcpy(p->args, args->args, sizeof(p->args));
p->ent.draw_layer = args->layer;
if(!(p->ent.draw_layer & LAYER_LOW_MASK)) {
switch(p->type) {
case EnemyProj:
case FakeProj: {
// 1. Large projectiles go below smaller ones.
drawlayer_low_t sublayer = (LAYER_LOW_MASK - (drawlayer_low_t)projectile_rect_area(p));
// 2. Additive projectiles go below others.
sublayer <<= 1;
sublayer |= 1 * (p->blend == BLEND_ADD || p->flags & PFLAG_DRAWADD);
// If specific blending order is required, then set up the sublayer manually.
p->ent.draw_layer |= sublayer;
break;
}
case Particle: {
// 1. Additive particles go above others.
drawlayer_low_t sublayer = (p->blend == BLEND_ADD || p->flags & PFLAG_DRAWADD) * PARTICLE_ADDITIVE_SUBLAYER;
// If specific blending order is required, then set up the sublayer manually.
p->ent.draw_layer |= sublayer;
break;
}
default:
break;
}
}
p->ent.draw_func = ent_draw_projectile;
ent_register(&p->ent, ENT_PROJECTILE);
// BUG: this currently breaks some projectiles
// enable this when they're fixed
// assert(rule != NULL);
// rule(p, EVENT_BIRTH);
return (Projectile*)args->insertion_rule((List**)args->dest, (List*)p);
// FIXME: use simpler/faster insertions when v1.2 compat is not needed
// return (Projectile*)list_push(args->dest, p);
return (Projectile*)proj_insert_sizeprio((List**)args->dest, (List*)p);
}
Projectile* create_projectile(ProjArgs *args) {
@ -200,6 +224,7 @@ static void* _delete_projectile(List **projs, List *proj, void *arg) {
p->rule(p, EVENT_DEATH);
del_ref(proj);
ent_unregister(&p->ent);
objpool_release(stage_object_pools.projectiles, (ObjectInterface*)list_unlink(projs, proj));
return NULL;