2011-04-26 12:04:45 +02:00
|
|
|
/*
|
2019-08-03 19:43:48 +02:00
|
|
|
* This software is licensed under the terms of the MIT License.
|
2017-02-11 04:52:08 +01:00
|
|
|
* See COPYING for further information.
|
2011-04-26 12:04:45 +02:00
|
|
|
* ---
|
2019-01-23 21:10:43 +01:00
|
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
2019-07-03 20:00:56 +02:00
|
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
2011-04-26 12:04:45 +02:00
|
|
|
*/
|
|
|
|
|
2017-11-25 20:45:11 +01:00
|
|
|
#include "taisei.h"
|
|
|
|
|
2011-04-26 12:04:45 +02:00
|
|
|
#include "enemy.h"
|
|
|
|
|
|
|
|
#include "global.h"
|
|
|
|
#include "projectile.h"
|
|
|
|
#include "list.h"
|
2017-10-03 17:25:38 +02:00
|
|
|
#include "aniplayer.h"
|
2017-12-13 20:05:12 +01:00
|
|
|
#include "stageobjects.h"
|
2018-05-15 02:27:25 +02:00
|
|
|
#include "util/glm.h"
|
2018-04-13 21:13:48 +02:00
|
|
|
#include "entity.h"
|
2011-04-26 12:04:45 +02:00
|
|
|
|
2017-11-15 07:05:47 +01:00
|
|
|
#ifdef create_enemy_p
|
|
|
|
#undef create_enemy_p
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
Enemy* _enemy_attach_dbginfo(Enemy *e, DebugInfo *dbg) {
|
|
|
|
memcpy(&e->debug, dbg, sizeof(DebugInfo));
|
|
|
|
set_debug_info(dbg);
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
static void ent_draw_enemy(EntityInterface *ent);
|
2018-07-30 09:04:09 +02:00
|
|
|
static DamageResult ent_damage_enemy(EntityInterface *ienemy, const DamageInfo *dmg);
|
2018-04-13 21:13:48 +02:00
|
|
|
|
2018-06-01 23:55:37 +02:00
|
|
|
static void fix_pos0_visual(Enemy *e) {
|
|
|
|
double x = creal(e->pos0_visual);
|
|
|
|
double y = cimag(e->pos0_visual);
|
|
|
|
double ofs = 21;
|
|
|
|
|
2018-06-03 23:47:13 +02:00
|
|
|
if(e->hp == ENEMY_IMMUNE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-06-01 23:55:37 +02:00
|
|
|
if(x <= 0 && x > -ofs) {
|
|
|
|
x = -ofs;
|
|
|
|
} else if(x >= VIEWPORT_W && x < VIEWPORT_W + ofs) {
|
|
|
|
x = VIEWPORT_W + ofs;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(y <= 0 && y > -ofs) {
|
|
|
|
y = -ofs;
|
|
|
|
} else if(y >= VIEWPORT_H && y < VIEWPORT_H + ofs) {
|
|
|
|
y = VIEWPORT_H + ofs;
|
|
|
|
}
|
|
|
|
|
|
|
|
e->pos0_visual = x + y * I;
|
|
|
|
}
|
|
|
|
|
2019-07-15 02:34:38 +02:00
|
|
|
static inline int enemy_call_logic_rule(Enemy *e, int t) {
|
2019-07-17 04:04:49 +02:00
|
|
|
if(t == EVENT_KILLED) {
|
|
|
|
coevent_signal(&e->events.killed);
|
|
|
|
}
|
|
|
|
|
2019-07-15 02:34:38 +02:00
|
|
|
if(e->logic_rule) {
|
|
|
|
return e->logic_rule(e, t);
|
2019-07-27 22:14:48 +02:00
|
|
|
} else {
|
|
|
|
// TODO: backport unified left/right move animations from the obsolete `newart` branch
|
2019-12-04 19:06:42 +01:00
|
|
|
cmplx v = move_update(&e->pos, &e->move);
|
|
|
|
e->moving = fabs(creal(v)) >= 1;
|
2019-07-25 02:31:02 +02:00
|
|
|
e->dir = creal(v) < 0;
|
2019-07-15 02:34:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ACTION_NONE;
|
|
|
|
}
|
|
|
|
|
2019-11-22 04:37:11 +01:00
|
|
|
Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule visual_rule, EnemyLogicRule logic_rule,
|
|
|
|
cmplx a1, cmplx a2, cmplx a3, cmplx a4) {
|
2017-11-15 07:05:47 +01:00
|
|
|
if(IN_DRAW_CODE) {
|
|
|
|
log_fatal("Tried to spawn an enemy while in drawing code");
|
|
|
|
}
|
|
|
|
|
2019-04-12 10:36:40 +02:00
|
|
|
// FIXME: some code relies on the insertion logic (which?)
|
|
|
|
Enemy *e = alist_insert(enemies, enemies->first, (Enemy*)objpool_acquire(stage_object_pools.enemies));
|
|
|
|
// Enemy *e = alist_append(enemies, (Enemy*)objpool_acquire(stage_object_pools.enemies));
|
2017-02-11 04:52:08 +01:00
|
|
|
e->moving = false;
|
2011-06-26 13:45:27 +02:00
|
|
|
e->dir = 0;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2011-04-26 12:04:45 +02:00
|
|
|
e->birthtime = global.frames;
|
|
|
|
e->pos = pos;
|
|
|
|
e->pos0 = pos;
|
2018-06-01 23:55:37 +02:00
|
|
|
e->pos0_visual = pos;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-06-12 21:51:32 +02:00
|
|
|
e->spawn_hp = hp;
|
2011-04-26 12:04:45 +02:00
|
|
|
e->hp = hp;
|
2012-07-18 12:33:37 +02:00
|
|
|
e->alpha = 1.0;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2011-04-26 12:04:45 +02:00
|
|
|
e->logic_rule = logic_rule;
|
2017-11-15 07:05:47 +01:00
|
|
|
e->visual_rule = visual_rule;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2011-06-26 13:45:27 +02:00
|
|
|
e->args[0] = a1;
|
|
|
|
e->args[1] = a2;
|
|
|
|
e->args[2] = a3;
|
|
|
|
e->args[3] = a4;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
e->ent.draw_layer = LAYER_ENEMY;
|
|
|
|
e->ent.draw_func = ent_draw_enemy;
|
2018-07-30 09:04:09 +02:00
|
|
|
e->ent.damage_func = ent_damage_enemy;
|
2018-06-03 23:47:13 +02:00
|
|
|
|
2019-07-17 10:43:49 +02:00
|
|
|
coevent_init(&e->events.killed);
|
2018-06-03 23:47:13 +02:00
|
|
|
fix_pos0_visual(e);
|
2018-04-13 21:13:48 +02:00
|
|
|
ent_register(&e->ent, ENT_ENEMY);
|
|
|
|
|
2019-07-15 02:34:38 +02:00
|
|
|
enemy_call_logic_rule(e, EVENT_BIRTH);
|
2012-07-28 19:45:51 +02:00
|
|
|
return e;
|
2011-04-26 12:04:45 +02:00
|
|
|
}
|
|
|
|
|
2019-01-24 21:21:08 +01:00
|
|
|
static void* _delete_enemy(ListAnchor *enemies, List* enemy, void *arg) {
|
2017-11-21 15:45:01 +01:00
|
|
|
Enemy *e = (Enemy*)enemy;
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-08-27 09:08:14 +02:00
|
|
|
if(e->hp <= 0 && e->hp != ENEMY_IMMUNE && e->hp != ENEMY_BOMB) {
|
2017-01-28 19:56:17 +01:00
|
|
|
play_sound("enemydeath");
|
2017-11-10 21:49:16 +01:00
|
|
|
|
|
|
|
for(int i = 0; i < 10; i++) {
|
2019-11-29 08:38:45 +01:00
|
|
|
RNG_ARRAY(rng, 2);
|
2018-05-02 06:46:48 +02:00
|
|
|
PARTICLE(
|
|
|
|
.sprite = "flare",
|
|
|
|
.pos = e->pos,
|
|
|
|
.timeout = 10,
|
|
|
|
.rule = linear,
|
|
|
|
.draw_rule = Fade,
|
2019-11-29 08:38:45 +01:00
|
|
|
.args = { vrng_range(rng[0], 3, 13) * vrng_dir(rng[1]) },
|
2017-11-10 21:49:16 +01:00
|
|
|
);
|
2012-08-07 02:45:38 +02:00
|
|
|
}
|
2017-11-10 21:49:16 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
PARTICLE(.proto = pp_blast, .pos = e->pos, .timeout = 20, .draw_rule = Blast, .flags = PFLAG_REQUIREDPARTICLE);
|
|
|
|
PARTICLE(.proto = pp_blast, .pos = e->pos, .timeout = 20, .draw_rule = Blast, .flags = PFLAG_REQUIREDPARTICLE);
|
|
|
|
PARTICLE(.proto = pp_blast, .pos = e->pos, .timeout = 15, .draw_rule = GrowFade, .flags = PFLAG_REQUIREDPARTICLE);
|
2019-02-22 00:56:03 +01:00
|
|
|
|
|
|
|
for(Projectile *p = global.projs.first; p; p = p->next) {
|
2019-03-28 17:42:07 +01:00
|
|
|
if(p->type == PROJ_ENEMY && !(p->flags & PFLAG_NOCOLLISION) && cabs(p->pos - e->pos) < 64) {
|
2019-02-22 00:56:03 +01:00
|
|
|
spawn_and_collect_item(e->pos, ITEM_PIV, 1);
|
|
|
|
}
|
|
|
|
}
|
2011-06-28 19:23:02 +02:00
|
|
|
}
|
2017-11-10 21:49:16 +01:00
|
|
|
|
2019-07-15 02:34:38 +02:00
|
|
|
enemy_call_logic_rule(e, EVENT_DEATH);
|
2019-07-17 04:04:49 +02:00
|
|
|
coevent_cancel(&e->events.killed);
|
2018-04-13 21:13:48 +02:00
|
|
|
ent_unregister(&e->ent);
|
2019-04-12 10:36:40 +02:00
|
|
|
objpool_release(stage_object_pools.enemies, alist_unlink(enemies, enemy));
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2017-11-21 15:45:01 +01:00
|
|
|
return NULL;
|
2011-04-26 12:04:45 +02:00
|
|
|
}
|
|
|
|
|
2018-06-01 20:40:18 +02:00
|
|
|
void delete_enemy(EnemyList *enemies, Enemy* enemy) {
|
|
|
|
_delete_enemy((ListAnchor*)enemies, (List*)enemy, NULL);
|
2011-04-26 22:39:50 +02:00
|
|
|
}
|
|
|
|
|
2018-06-01 20:40:18 +02:00
|
|
|
void delete_enemies(EnemyList *enemies) {
|
|
|
|
alist_foreach(enemies, _delete_enemy, NULL);
|
2011-04-26 12:04:45 +02:00
|
|
|
}
|
|
|
|
|
2019-11-22 04:37:11 +01:00
|
|
|
static cmplx enemy_visual_pos(Enemy *enemy) {
|
2018-06-01 23:55:37 +02:00
|
|
|
double t = (global.frames - enemy->birthtime) / 30.0;
|
|
|
|
|
2018-06-03 23:47:13 +02:00
|
|
|
if(t >= 1 || enemy->hp == ENEMY_IMMUNE) {
|
2018-06-01 23:55:37 +02:00
|
|
|
return enemy->pos;
|
|
|
|
}
|
|
|
|
|
2019-11-22 04:37:11 +01:00
|
|
|
cmplx p = enemy->pos - enemy->pos0;
|
2018-06-01 23:55:37 +02:00
|
|
|
p += t * enemy->pos0 + (1 - t) * enemy->pos0_visual;
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void call_visual_rule(Enemy *e, bool render) {
|
2019-11-22 04:37:11 +01:00
|
|
|
cmplx tmp = e->pos;
|
2018-06-01 23:55:37 +02:00
|
|
|
e->pos = enemy_visual_pos(e);
|
|
|
|
e->visual_rule(e, global.frames - e->birthtime, render);
|
|
|
|
e->pos = tmp;
|
|
|
|
}
|
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
static void ent_draw_enemy(EntityInterface *ent) {
|
|
|
|
Enemy *e = ENT_CAST(ent, Enemy);
|
|
|
|
|
|
|
|
if(!e->visual_rule) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-11-15 07:05:47 +01:00
|
|
|
#ifdef ENEMY_DEBUG
|
|
|
|
static Enemy prev_state;
|
|
|
|
memcpy(&prev_state, e, sizeof(Enemy));
|
2018-04-13 21:13:48 +02:00
|
|
|
#endif
|
|
|
|
|
2018-06-01 23:55:37 +02:00
|
|
|
call_visual_rule(e, true);
|
2017-11-15 07:05:47 +01:00
|
|
|
|
2018-04-13 21:13:48 +02:00
|
|
|
#ifdef ENEMY_DEBUG
|
2017-11-15 07:05:47 +01:00
|
|
|
if(memcmp(&prev_state, e, sizeof(Enemy))) {
|
|
|
|
set_debug_info(&e->debug);
|
|
|
|
log_fatal("Enemy modified its own state in draw rule");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
int enemy_flare(Projectile *p, int t) { // a[0] velocity, a[1] ref to enemy
|
|
|
|
if(t == EVENT_DEATH) {
|
|
|
|
free_ref(p->args[1]);
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
return ACTION_ACK;
|
2012-04-04 17:19:53 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
Enemy *owner = REF(p->args[1]);
|
2017-11-10 21:49:16 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
/*
|
|
|
|
if(REF(p->args[1]) == NULL) {
|
|
|
|
return ACTION_DESTROY;
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2018-05-02 06:46:48 +02:00
|
|
|
*/
|
2017-02-11 04:52:08 +01:00
|
|
|
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
int result = ACTION_NONE;
|
|
|
|
|
|
|
|
if(t == EVENT_BIRTH) {
|
|
|
|
t = 0;
|
|
|
|
result = ACTION_ACK;
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
if(owner != NULL) {
|
|
|
|
p->args[3] = owner->pos;
|
2017-11-10 21:49:16 +01:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
p->pos = p->pos0 + p->args[3] + p->args[0]*t;
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
return result;
|
2012-04-04 17:19:53 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 07:05:47 +01:00
|
|
|
void BigFairy(Enemy *e, int t, bool render) {
|
|
|
|
if(!render) {
|
|
|
|
if(!(t % 5)) {
|
2019-11-29 08:38:45 +01:00
|
|
|
cmplx offset = rng_sreal() * 15;
|
|
|
|
offset += rng_sreal() * 10 * I;
|
2017-11-10 21:49:16 +01:00
|
|
|
|
2018-05-02 06:46:48 +02:00
|
|
|
PARTICLE(
|
|
|
|
.sprite = "smoothdot",
|
|
|
|
.pos = offset,
|
2018-07-23 19:07:59 +02:00
|
|
|
.color = RGBA(0.0, 0.2, 0.3, 0.0),
|
2018-05-02 06:46:48 +02:00
|
|
|
.rule = enemy_flare,
|
|
|
|
.draw_rule = Shrink,
|
|
|
|
.timeout = 50,
|
|
|
|
.args = { (-50.0*I-offset)/50.0, add_ref(e) },
|
2017-11-15 07:05:47 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
2012-04-04 17:19:53 +02:00
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2012-04-04 17:19:53 +02:00
|
|
|
float s = sin((float)(global.frames-e->birthtime)/10.f)/6 + 0.8;
|
2018-07-25 04:54:15 +02:00
|
|
|
Color *clr = RGBA_MUL_ALPHA(1, 1, 1, e->alpha);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
2018-07-25 04:54:15 +02:00
|
|
|
.color = clr,
|
2018-04-12 16:08:48 +02:00
|
|
|
.sprite = "fairy_circle",
|
|
|
|
.pos = { creal(e->pos), cimag(e->pos) },
|
|
|
|
.rotation.angle = global.frames * 10 * DEG2RAD,
|
|
|
|
.scale.both = s,
|
|
|
|
});
|
|
|
|
|
|
|
|
const char *seqname = !e->moving ? "main" : (e->dir ? "left" : "right");
|
|
|
|
Animation *ani = get_ani("enemy/bigfairy");
|
|
|
|
Sprite *spr = animation_get_frame(ani,get_ani_sequence(ani, seqname),global.frames);
|
2018-06-01 23:55:37 +02:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
2018-07-25 04:54:15 +02:00
|
|
|
.color = clr,
|
2018-04-12 16:08:48 +02:00
|
|
|
.sprite_ptr = spr,
|
|
|
|
.pos = { creal(e->pos), cimag(e->pos) },
|
|
|
|
});
|
2012-04-04 17:19:53 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 07:05:47 +01:00
|
|
|
void Fairy(Enemy *e, int t, bool render) {
|
|
|
|
if(!render) {
|
|
|
|
return;
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2011-04-26 12:04:45 +02:00
|
|
|
float s = sin((float)(global.frames-e->birthtime)/10.f)/6 + 0.8;
|
2018-07-25 04:54:15 +02:00
|
|
|
Color *clr = RGBA_MUL_ALPHA(1, 1, 1, e->alpha);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
2018-07-25 04:54:15 +02:00
|
|
|
.color = clr,
|
2018-04-12 16:08:48 +02:00
|
|
|
.sprite = "fairy_circle",
|
|
|
|
.pos = { creal(e->pos), cimag(e->pos) },
|
|
|
|
.rotation.angle = global.frames * 10 * DEG2RAD,
|
|
|
|
.scale.both = s,
|
|
|
|
});
|
|
|
|
|
|
|
|
const char *seqname = !e->moving ? "main" : (e->dir ? "left" : "right");
|
|
|
|
Animation *ani = get_ani("enemy/fairy");
|
|
|
|
Sprite *spr = animation_get_frame(ani,get_ani_sequence(ani, seqname),global.frames);
|
2018-06-01 23:55:37 +02:00
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
2018-07-25 04:54:15 +02:00
|
|
|
.color = clr,
|
2018-04-12 16:08:48 +02:00
|
|
|
.sprite_ptr = spr,
|
|
|
|
.pos = { creal(e->pos), cimag(e->pos) },
|
|
|
|
});
|
2011-04-26 12:04:45 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 07:05:47 +01:00
|
|
|
void Swirl(Enemy *e, int t, bool render) {
|
|
|
|
if(!render) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-12 16:08:48 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
2018-07-25 04:54:15 +02:00
|
|
|
.color = RGBA_MUL_ALPHA(1, 1, 1, e->alpha),
|
2018-04-12 16:08:48 +02:00
|
|
|
.sprite = "enemy/swirl",
|
|
|
|
.pos = { creal(e->pos), cimag(e->pos) },
|
|
|
|
.rotation.angle = t * 10 * DEG2RAD,
|
|
|
|
});
|
2011-06-27 13:36:35 +02:00
|
|
|
}
|
|
|
|
|
2018-06-01 22:56:02 +02:00
|
|
|
bool enemy_is_vulnerable(Enemy *enemy) {
|
|
|
|
if(enemy->hp <= ENEMY_IMMUNE) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool enemy_in_viewport(Enemy *enemy) {
|
|
|
|
double s = 60; // TODO: make this adjustable
|
|
|
|
|
|
|
|
return
|
|
|
|
creal(enemy->pos) >= -s &&
|
|
|
|
creal(enemy->pos) <= VIEWPORT_W + s &&
|
|
|
|
cimag(enemy->pos) >= -s &&
|
|
|
|
cimag(enemy->pos) <= VIEWPORT_H + s;
|
|
|
|
}
|
|
|
|
|
|
|
|
void enemy_kill_all(EnemyList *enemies) {
|
|
|
|
for(Enemy *e = enemies->first; e; e = e->next) {
|
|
|
|
e->hp = ENEMY_KILLED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-30 09:04:09 +02:00
|
|
|
static DamageResult ent_damage_enemy(EntityInterface *ienemy, const DamageInfo *dmg) {
|
|
|
|
Enemy *enemy = ENT_CAST(ienemy, Enemy);
|
|
|
|
|
|
|
|
if(!enemy_is_vulnerable(enemy) || dmg->type == DMG_ENEMY_SHOT || dmg->type == DMG_ENEMY_COLLISION) {
|
|
|
|
return DMG_RESULT_IMMUNE;
|
2018-06-01 22:56:02 +02:00
|
|
|
}
|
|
|
|
|
2018-07-30 09:04:09 +02:00
|
|
|
enemy->hp -= dmg->amount;
|
2018-06-12 21:51:32 +02:00
|
|
|
|
|
|
|
if(enemy->hp <= 0) {
|
2018-06-01 22:56:02 +02:00
|
|
|
enemy->hp = ENEMY_KILLED;
|
2019-02-22 00:56:03 +01:00
|
|
|
|
|
|
|
if(dmg->type == DMG_PLAYER_DISCHARGE) {
|
2019-03-03 12:53:45 +01:00
|
|
|
spawn_and_collect_items(enemy->pos, 1, ITEM_VOLTAGE, (int)imax(1, enemy->spawn_hp / 100));
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
2018-06-01 22:56:02 +02:00
|
|
|
}
|
|
|
|
|
2018-07-30 09:04:09 +02:00
|
|
|
if(enemy->hp < enemy->spawn_hp * 0.1) {
|
2018-06-12 21:51:32 +02:00
|
|
|
play_loop("hit1");
|
2018-06-13 21:12:10 +02:00
|
|
|
} else {
|
|
|
|
play_loop("hit0");
|
2018-06-12 21:51:32 +02:00
|
|
|
}
|
2018-06-02 16:33:25 +02:00
|
|
|
|
2018-07-30 09:04:09 +02:00
|
|
|
return DMG_RESULT_OK;
|
2018-06-01 22:56:02 +02:00
|
|
|
}
|
|
|
|
|
2018-06-01 20:40:18 +02:00
|
|
|
void process_enemies(EnemyList *enemies) {
|
2018-06-01 22:56:02 +02:00
|
|
|
for(Enemy *enemy = enemies->first, *next; enemy; enemy = next) {
|
|
|
|
next = enemy->next;
|
|
|
|
|
|
|
|
if(enemy->hp == ENEMY_KILLED) {
|
2019-07-15 02:34:38 +02:00
|
|
|
enemy_call_logic_rule(enemy, EVENT_KILLED);
|
2018-06-01 22:56:02 +02:00
|
|
|
delete_enemy(enemies, enemy);
|
|
|
|
continue;
|
|
|
|
}
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2019-07-15 02:34:38 +02:00
|
|
|
int action = enemy_call_logic_rule(enemy, global.frames - enemy->birthtime);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2019-03-01 17:10:49 +01:00
|
|
|
if(enemy->hp > ENEMY_IMMUNE && enemy->alpha >= 1.0 && cabs(enemy->pos - global.plr.pos) < ENEMY_HURT_RADIUS) {
|
2018-07-30 09:04:09 +02:00
|
|
|
ent_damage(&global.plr.ent, &(DamageInfo) { .type = DMG_ENEMY_COLLISION });
|
2017-11-21 09:10:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
enemy->alpha = approach(enemy->alpha, 1.0, 1.0/60.0);
|
2017-02-11 04:52:08 +01:00
|
|
|
|
2018-06-01 22:56:02 +02:00
|
|
|
if((enemy->hp > ENEMY_IMMUNE && (!enemy_in_viewport(enemy) || enemy->hp <= 0)) || action == ACTION_DESTROY) {
|
|
|
|
delete_enemy(enemies, enemy);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(enemy->visual_rule) {
|
2018-06-01 23:55:37 +02:00
|
|
|
call_visual_rule(enemy, false);
|
2017-02-11 04:52:08 +01:00
|
|
|
}
|
2011-04-26 12:04:45 +02:00
|
|
|
}
|
2012-07-18 12:33:37 +02:00
|
|
|
}
|
2017-03-11 04:41:57 +01:00
|
|
|
|
|
|
|
void enemies_preload(void) {
|
|
|
|
preload_resources(RES_ANIM, RESF_DEFAULT,
|
2018-02-06 07:19:25 +01:00
|
|
|
"enemy/fairy",
|
|
|
|
"enemy/bigfairy",
|
2017-03-11 04:41:57 +01:00
|
|
|
NULL);
|
|
|
|
|
2018-02-06 07:19:25 +01:00
|
|
|
preload_resources(RES_SPRITE, RESF_DEFAULT,
|
|
|
|
"fairy_circle",
|
|
|
|
"enemy/swirl",
|
2017-03-11 04:41:57 +01:00
|
|
|
NULL);
|
|
|
|
|
|
|
|
preload_resources(RES_SFX, RESF_OPTIONAL,
|
|
|
|
"enemydeath",
|
|
|
|
NULL);
|
|
|
|
}
|