[RELEASE] u22-v03

This version is submitted to U22 breau.
This commit is contained in:
2020-09-14 00:00:00 +00:00
parent 360595de37
commit 84c3a02b9a
357 changed files with 29223 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
add_library(locharacter
base.c
big_warder.c
cavia.c
encephalon.c
greedy_scientist.c
misc.c
pool.c
scientist.c
theists_child.c
util.c
warder.c
)
target_link_libraries(locharacter
msgpackc
math
memory
mpkutil
lobullet
locommon
loeffect
loentity
loplayer
loshader
)

434
core/locharacter/base.c Normal file
View File

@@ -0,0 +1,434 @@
#include "./base.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "core/lobullet/pool.h"
#include "core/locommon/easing.h"
#include "core/locommon/msgpack.h"
#include "core/locommon/position.h"
#include "core/locommon/ticker.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/character.h"
#include "core/loentity/store.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/character.h"
#include "./big_warder.h"
#include "./cavia.h"
#include "./encephalon.h"
#include "./greedy_scientist.h"
#include "./scientist.h"
#include "./theists_child.h"
#include "./misc.h"
#include "./warder.h"
#define LOCHARACTER_BASE_PARAM_TO_PACK_EACH_( \
PROC, PROC_type, PROC_state, PROC_str) do { \
PROC_str ("subclass", "character"); \
PROC_type ("type", type); \
PROC ("id", super.super.id); \
PROC ("ground", ground); \
PROC ("pos", pos); \
PROC ("direction", direction); \
PROC ("knockback", knockback); \
PROC ("gravity", gravity); \
PROC ("madness", recipient.madness); \
PROC ("effects", recipient.effects); \
PROC_state("state", state); \
PROC ("since", since); \
PROC ("last-update-time", last_update_time); \
PROC ("last-knockback-time", last_knockback_time); \
PROC ("last-hit-time", last_hit_time); \
} while (0)
#define LOCHARACTER_BASE_PARAM_TO_PACK_COUNT 15
static void locharacter_base_convert_to_world_pos_(
const loentity_ground_t* g, locommon_position_t* wpos, const vec2_t* pos) {
assert(g != NULL);
assert(wpos != NULL);
assert(vec2_valid(pos));
vec2_t p = *pos;
p.x *= g->size.x;
p.y += g->size.y;
*wpos = g->super.pos;
vec2_addeq(&wpos->fract, &p);
locommon_position_reduce(wpos);
}
static void locharacter_base_convert_from_world_pos_(
const loentity_ground_t* g, vec2_t* pos, const locommon_position_t* wpos) {
assert(g != NULL);
assert(pos != NULL);
assert(locommon_position_valid(wpos));
locommon_position_sub(pos, wpos, &g->super.pos);
pos->x /= g->size.x;
pos->y -= g->size.y;
}
static loentity_ground_t* locharacter_base_get_ground_(locharacter_base_t* base) {
assert(base != NULL);
loentity_store_iterator_t itr;
if (loentity_store_find_item_by_id(base->entities, &itr, base->ground)) {
return itr.ground;
}
return NULL;
}
static void locharacter_base_handle_knockback_(locharacter_base_t* base) {
assert(base != NULL);
vec2_t v = base->knockback;
v.x /= base->cache.ground->size.x;
vec2_muleq(&v, base->ticker->delta_f);
vec2_addeq(&base->pos, &v);
locommon_easing_linear_float(&base->knockback.x, 0, base->ticker->delta_f/2);
locommon_easing_linear_float(&base->knockback.y, 0, base->ticker->delta_f/2);
}
static void locharacter_base_calculate_world_position_(
locharacter_base_t* base) {
assert(base != NULL);
base->pos.x = MATH_CLAMP(base->pos.x, -1, 1);
base->pos.y = MATH_CLAMP(base->pos.y, 0, 1);
if (base->pos.y < base->cache.height) {
if (base->cache.gravity) base->gravity = 0;
base->pos.y = base->cache.height;
}
locharacter_base_convert_to_world_pos_(
base->cache.ground, &base->super.super.pos, &base->pos);
}
static void locharacter_base_calculate_velocity_(
locharacter_base_t* base, vec2_t* v, const locommon_position_t* oldpos) {
assert(base != NULL);
assert(v != NULL);
assert(locommon_position_valid(oldpos));
locommon_position_sub(v, &base->super.super.pos, oldpos);
vec2_diveq(v, base->ticker->delta_f);
}
static void locharacter_base_execute_bullet_hittest_(
locharacter_base_t* base, const vec2_t* velocity) {
assert(base != NULL);
assert(vec2_valid(velocity));
if (base->last_hit_time + 200 > base->ticker->time) return;
if (loentity_store_affect_bullets_shot_by_one(
base->entities,
&base->super,
base->player->entity.super.super.id,
velocity,
base->ticker->delta_f)) {
base->last_hit_time = base->ticker->time;
}
}
static void locharacter_base_delete_(loentity_t* entity) {
assert(entity != NULL);
locharacter_base_t* base = (typeof(base)) entity;
if (!base->used) return;
base->used = false;
# define each_(NAME, name) do { \
if (base->type == LOCHARACTER_TYPE_##NAME) { \
locharacter_##name##_tear_down(base); \
return; \
} \
} while (0)
LOCHARACTER_TYPE_EACH_(each_);
assert(false);
# undef each_
}
static void locharacter_base_die_(loentity_t* entity) {
assert(entity != NULL);
}
static bool locharacter_base_update_(loentity_t* entity) {
assert(entity != NULL);
static const float gravity_acceleration = 2.f;
locharacter_base_t* base = (typeof(base)) entity;
base->cache = (typeof(base->cache)) {
.time = base->ticker->time,
};
base->cache.ground = locharacter_base_get_ground_(base);
if (base->cache.ground == NULL) return false;
locharacter_base_convert_from_world_pos_(
base->cache.ground,
&base->cache.player_pos,
&base->player->entity.super.super.pos);
locharacter_base_handle_knockback_(base);
locommon_position_t oldpos = base->super.super.pos;
base->pos.y += base->gravity * base->ticker->delta_f;
# define each_(NAME, name) do { \
if (base->type == LOCHARACTER_TYPE_##NAME) { \
if (!locharacter_##name##_update(base)) return false; \
} \
} while (0)
LOCHARACTER_TYPE_EACH_(each_);
# undef each_
locharacter_base_calculate_world_position_(base);
if (base->cache.gravity) {
base->gravity -= base->ticker->delta_f * gravity_acceleration;
} else {
base->gravity = 0;
}
if (base->cache.bullet_hittest) {
vec2_t velocity;
locharacter_base_calculate_velocity_(base, &velocity, &oldpos);
locharacter_base_execute_bullet_hittest_(base, &velocity);
}
base->cache.ground = NULL;
base->last_update_time = base->cache.time;
return true;
}
static void locharacter_base_draw_(
loentity_t* entity, const locommon_position_t* basepos) {
assert(entity != NULL);
assert(locommon_position_valid(basepos));
locharacter_base_t* base = (typeof(base)) entity;
vec2_t v;
locommon_position_sub(&v, &base->super.super.pos, basepos);
vec2_addeq(&base->cache.instance.pos, &v);
loshader_character_drawer_add_instance(base->drawer, &base->cache.instance);
}
static void locharacter_base_apply_effect_(
loentity_character_t* entity, const loeffect_t* effect) {
assert(entity != NULL);
assert(effect != NULL);
locharacter_base_t* base = (typeof(base)) entity;
loeffect_recipient_apply_effect(&base->recipient, effect);
}
static void locharacter_base_knockback_(
loentity_character_t* chara, const vec2_t* knockback) {
assert(chara != NULL);
assert(vec2_valid(knockback));
locharacter_base_t* base = (typeof(base)) chara;
static const float r = .05f;
if (vec2_pow_length(knockback) > r*r) {
base->last_knockback_time = base->ticker->time;
}
vec2_addeq(&base->knockback, knockback);
}
static void locharacter_base_pack_(
const loentity_t* chara, msgpack_packer* packer) {
assert(chara != NULL);
assert(packer != NULL);
const locharacter_base_t* base = (typeof(base)) chara;
msgpack_pack_map(packer, LOCHARACTER_BASE_PARAM_TO_PACK_COUNT+1);
# define pack_(name, var) do { \
mpkutil_pack_str(packer, name); \
LOCOMMON_MSGPACK_PACK_ANY(packer, &base->var); \
} while (0)
# define pack_type_(name, var) do { \
mpkutil_pack_str(packer, name); \
mpkutil_pack_str(packer, locharacter_type_stringify(base->var)); \
} while (0)
# define pack_state_(name, var) do { \
mpkutil_pack_str(packer, name); \
mpkutil_pack_str(packer, locharacter_state_stringify(base->var)); \
} while (0)
# define pack_str_(name, str) do { \
mpkutil_pack_str(packer, name); \
mpkutil_pack_str(packer, str); \
} while (0)
LOCHARACTER_BASE_PARAM_TO_PACK_EACH_(pack_, pack_type_, pack_state_, pack_str_);
# undef pack_str_
# undef pack_state_
# undef pack_type_
# undef pack_
# define each_(NAME, name) do { \
if (base->type == LOCHARACTER_TYPE_##NAME) { \
locharacter_##name##_pack_data(base, packer); \
return; \
} \
} while (0)
mpkutil_pack_str(packer, "data");
LOCHARACTER_TYPE_EACH_(each_);
assert(false);
# undef each_
}
void locharacter_base_initialize(
locharacter_base_t* base,
loresource_set_t* res,
loshader_character_drawer_t* drawer,
const locommon_ticker_t* ticker,
lobullet_pool_t* bullets,
loentity_store_t* entities,
loplayer_t* player) {
assert(base != NULL);
assert(res != NULL);
assert(drawer != NULL);
assert(ticker != NULL);
assert(bullets != NULL);
assert(entities != NULL);
assert(player != NULL);
*base = (typeof(*base)) {
.super = {
.super = {
.vtable = {
.delete = locharacter_base_delete_,
.die = locharacter_base_die_,
.update = locharacter_base_update_,
.draw = locharacter_base_draw_,
.pack = locharacter_base_pack_,
},
.subclass = LOENTITY_SUBCLASS_CHARACTER,
},
.vtable = {
.apply_effect = locharacter_base_apply_effect_,
.knockback = locharacter_base_knockback_,
},
},
.res = res,
.drawer = drawer,
.ticker = ticker,
.bullets = bullets,
.entities = entities,
.player = player,
};
loeffect_recipient_initialize(&base->recipient, ticker);
}
void locharacter_base_reinitialize(locharacter_base_t* base, loentity_id_t id) {
assert(base != NULL);
# define reset_(name, var) do { \
base->var = (typeof(base->var)) {0}; \
} while (0)
# define reset_str_(name, str)
LOCHARACTER_BASE_PARAM_TO_PACK_EACH_(
reset_, reset_, reset_, reset_str_);
# undef reset_str_
# undef reset_
loeffect_recipient_reset(&base->recipient);
base->super.super.id = id;
}
void locharacter_base_deinitialize(locharacter_base_t* base) {
assert(base != NULL);
if (base->used) locharacter_base_delete_(&base->super.super);
loeffect_recipient_deinitialize(&base->recipient);
}
bool locharacter_base_unpack(
locharacter_base_t* base, const msgpack_object* obj) {
assert(base != NULL);
assert(obj != NULL);
locharacter_base_reinitialize(base, 0);
/* id will be overwritten below */
const char* v;
size_t vlen;
const msgpack_object_map* root = mpkutil_get_map(obj);
# define item_(v) mpkutil_get_map_item_by_str(root, v)
# define unpack_(name, var) do { \
if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &base->var)) { \
return NULL; \
} \
} while (0)
# define unpack_type_(name, var) do { \
if (!mpkutil_get_str(item_(name), &v, &vlen) || \
!locharacter_type_unstringify(&base->var, v, vlen)) { \
return NULL; \
} \
} while (0)
# define unpack_state_(name, var) do { \
if (!mpkutil_get_str(item_(name), &v, &vlen) || \
!locharacter_state_unstringify(&base->var, v, vlen)) { \
return NULL; \
} \
} while (0)
# define unpack_str_(name, str) do { \
if (!mpkutil_get_str(item_(name), &v, &vlen) || \
!(strncmp(v, str, vlen) == 0 && str[vlen] == 0)) { \
return NULL; \
} \
} while (0)
LOCHARACTER_BASE_PARAM_TO_PACK_EACH_(
unpack_, unpack_type_, unpack_state_, unpack_str_);
# undef unpack_str_
# undef unpack_state_
# undef unpack_type_
# undef unpack_
const msgpack_object* data = item_("data");
# define each_(NAME, name) do { \
if (base->type == LOCHARACTER_TYPE_##NAME) { \
return locharacter_##name##_unpack_data(base, data); \
} \
} while (0)
LOCHARACTER_TYPE_EACH_(each_);
return false;
# undef each_
# undef item_
}

100
core/locharacter/base.h Normal file
View File

@@ -0,0 +1,100 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/vector.h"
#include "core/lobullet/pool.h"
#include "core/locommon/position.h"
#include "core/locommon/ticker.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/character.h"
#include "core/loentity/store.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/character.h"
#include "./misc.h"
typedef struct {
loentity_character_t super;
bool used;
/* injected deps */
loresource_set_t* res;
loshader_character_drawer_t* drawer;
const locommon_ticker_t* ticker;
lobullet_pool_t* bullets;
loentity_store_t* entities;
loplayer_t* player;
/* temporary params for update */
struct {
/* set before calling update function */
loentity_ground_t* ground;
vec2_t player_pos;
uint64_t time;
/* Defaultly equals to ticker->time.
But characters who have an event with music
overwrites this value for synchronization */
/* set by update function */
float height;
bool bullet_hittest;
bool gravity;
loshader_character_drawer_instance_t instance;
} cache;
/* params to be packed (includes id) */
locharacter_type_t type;
loentity_id_t ground;
vec2_t pos;
float direction;
vec2_t knockback;
float gravity;
loeffect_recipient_t recipient;
locharacter_state_t state;
uint64_t since;
uint64_t last_update_time;
uint64_t last_knockback_time;
uint64_t last_hit_time;
# define LOCHARACTER_BASE_DATA_MAX_SIZE 256
uint8_t data[LOCHARACTER_BASE_DATA_MAX_SIZE];
} locharacter_base_t;
void
locharacter_base_initialize(
locharacter_base_t* base,
loresource_set_t* res,
loshader_character_drawer_t* drawer,
const locommon_ticker_t* ticker,
lobullet_pool_t* bullets,
loentity_store_t* entities,
loplayer_t* player
);
void
locharacter_base_reinitialize(
locharacter_base_t* base,
loentity_id_t id
);
void
locharacter_base_deinitialize(
locharacter_base_t* base
);
bool
locharacter_base_unpack(
locharacter_base_t* base,
const msgpack_object* obj
);

View File

@@ -0,0 +1,787 @@
#include "./big_warder.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/mpkutil/get.h"
#include "util/mpkutil/pack.h"
#include "core/lobullet/base.h"
#include "core/lobullet/bomb.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/easing.h"
#include "core/locommon/msgpack.h"
#include "core/loentity/entity.h"
#include "core/loplayer/combat.h"
#include "core/loplayer/event.h"
#include "core/loplayer/player.h"
#include "core/loresource/music.h"
#include "core/loresource/text.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./misc.h"
#include "./util.h"
typedef struct {
locharacter_event_holder_t event;
int32_t phase;
vec2_t from;
vec2_t to;
} locharacter_big_warder_param_t;
_Static_assert(
sizeof(locharacter_big_warder_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE);
#define LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(PROC) do { \
PROC("event-start-time", event.start_time); \
PROC("phase", phase); \
PROC("from", from); \
PROC("to", to); \
} while (0)
#define LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_COUNT 4
static const vec2_t locharacter_big_warder_size_ = vec2(.04f, .07f);
static const loeffect_recipient_status_t
locharacter_big_warder_base_status_ = {
.attack = .1f,
.defence = .85f,
.speed = .1f,
.jump = .1f,
};
#define LOCHARACTER_BIG_WARDER_BEAT (60000/80.f) /* 80 BPM */
#define LOCHARACTER_BIG_WARDER_MUSIC_DURATION \
((uint64_t) LOCHARACTER_BIG_WARDER_BEAT*144)
#define LOCHARACTER_BIG_WARDER_MELODY_B_BEAT 80
#include "./big_warder.private.h"
static void
locharacter_big_warder_start_wait_state_(
locharacter_base_t* c
);
static void
locharacter_big_warder_start_walk_state_(
locharacter_base_t* c
);
static void
locharacter_big_warder_start_shoot_state_(
locharacter_base_t* c
);
static void
locharacter_big_warder_start_combo_state_(
locharacter_base_t* c
);
static void
locharacter_big_warder_start_thrust_state_(
locharacter_base_t* c
);
static void
locharacter_big_warder_start_cooldown_state_(
locharacter_base_t* c
);
static void
locharacter_big_warder_start_stunned_state_(
locharacter_base_t* c
);
static void
locharacter_big_warder_start_dead_state_(
locharacter_base_t* c
);
static void locharacter_big_warder_finalize_event_(locharacter_base_t* c) {
assert(c != NULL);
/* This function must start next state. */
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
assert(p != NULL);
locharacter_event_holder_release_control(&p->event);
if (c->recipient.madness > 0) {
loentity_character_apply_effect(
&c->player->entity.super, &loeffect_curse_trigger());
locharacter_big_warder_start_wait_state_(c);
} else {
loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_UNFINISHER);
locharacter_big_warder_start_dead_state_(c);
}
}
static bool locharacter_big_warder_reset_if_player_left_(
locharacter_base_t* c) {
assert(c != NULL);
if (MATH_ABS(c->cache.player_pos.x) < 1 &&
0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) {
return false;
}
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
locharacter_event_holder_release_control(&p->event);
locharacter_big_warder_start_wait_state_(c);
return true;
}
static void locharacter_big_warder_update_wait_state_(locharacter_base_t* c) {
assert(c != NULL);
static const uint64_t period = 1000;
c->cache.gravity = true;
/* ---- motion ---- */
float t = (c->cache.time - c->since)%period*1.f/period;
t = (t*2) - 1;
t = MATH_ABS(t);
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t*t*(3-2*t);
/* ---- state transition ---- */
if (MATH_ABS(c->cache.player_pos.x) < 1 &&
0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) {
vec2_t diff;
vec2_sub(&diff, &c->cache.player_pos, &c->pos);
if (vec2_pow_length(&diff) < .5f*.5f) {
locharacter_big_warder_start_walk_state_(c);
return;
}
}
}
static void locharacter_big_warder_start_wait_state_(locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_WAIT;
}
static void locharacter_big_warder_update_walk_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
static const float linedur = beat*4;
static const uint64_t period = 800;
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
const bool event = locharacter_event_holder_has_control(&p->event);
const uint64_t min_duration = event? LOCHARACTER_BIG_WARDER_BEAT*16: 0;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &c->cache.instance;
if (c->pos.x != 0) {
float t = (c->cache.time - c->since)%period*1.f/period;
t = (t*2) - 1;
t = MATH_ABS(t);
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK;
instance->motion_time = t;
} else {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
}
/* ---- position ---- */
if (c->pos.x != 0) c->direction = -MATH_SIGN(c->pos.x);
c->pos.y = 0;
locommon_easing_linear_float(&c->pos.x, 0, c->ticker->delta_f/5);
/* ---- event ---- */
if (event) {
p->event.param->cinescope = true;
p->event.param->hide_hud = true;
if (c->since+(p->phase+1)*linedur < c->cache.time) {
static const char* text[] = {
"boss_big_warder_line0",
"boss_big_warder_line1",
};
if (p->phase < (int32_t) (sizeof(text)/sizeof(text[0]))) {
const char* v = loresource_text_get(
c->res->lang, text[(size_t) p->phase]);
loplayer_event_param_set_line(p->event.param, v, strlen(v));
} else {
loplayer_event_param_set_line(p->event.param, "", 0);
}
++p->phase;
}
}
/* ---- state transition ---- */
if (locharacter_big_warder_reset_if_player_left_(c)) return;
if (c->pos.x == 0 && c->since + min_duration <= c->cache.time) {
if (event) {
p->event.param->hide_hud = false;
p->event.param->cinescope = false;
loplayer_event_param_set_line(p->event.param, "", 0);
}
locharacter_big_warder_start_shoot_state_(c);
return;
}
}
static void locharacter_big_warder_start_walk_state_(
locharacter_base_t* c) {
assert(c != NULL);
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_WALK;
p->phase = 0;
loeffect_recipient_reset(&c->recipient);
if (!loeffect_stance_set_has(
&c->player->status.stances, LOEFFECT_STANCE_ID_UNFINISHER)) {
locharacter_event_holder_take_control(&p->event);
}
}
static void locharacter_big_warder_update_shoot_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
static const uint64_t duration = beat*3;
const uint64_t t = c->cache.time - c->since;
c->cache.bullet_hittest = true;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t;
/* ---- shooting ---- */
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
if (p->phase < 4 && p->phase*beat/2 <= c->cache.time - c->since) {
++p->phase;
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_linear_light_build(b, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = c->super.super.pos,
.size = vec2(.04f, .04f),
.velocity = vec2(c->direction*.5f, 0),
.color = vec4(.6f, .6f, .6f, .8f),
.acceleration = vec2(0, 0),
.duration = 2000,
.knockback = .4f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
/* ---- state transition ---- */
if (locharacter_big_warder_reset_if_player_left_(c)) return;
if (c->since + duration <= c->cache.time) {
locharacter_big_warder_start_combo_state_(c);
return;
}
}
static void locharacter_big_warder_start_shoot_state_(locharacter_base_t* c) {
assert(c != NULL);
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_SHOOT;
c->direction = c->cache.player_pos.x - c->pos.x;
c->direction = c->direction > 0? 1: -1;
p->phase = 0;
}
static void locharacter_big_warder_update_combo_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
static const uint64_t step_dur = beat;
static const uint64_t attack_dur = beat*2;
const locharacter_big_warder_param_t* p = (typeof(p)) c->data;
c->cache.bullet_hittest = true;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
if (c->since + step_dur > c->cache.time) {
const float t = (c->cache.time - c->since)*1.f/step_dur;
/* ---- position ---- */
vec2_t dist;
vec2_sub(&dist, &p->to, &p->from);
vec2_muleq(&dist, t*t*(3-2*t));
c->pos = p->from;
vec2_addeq(&c->pos, &dist);
/* ---- motion ---- */
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->motion_time = 1-powf(2*t-1, 4);
} else {
float t = (c->cache.time - c->since - step_dur)*1.f/attack_dur;
t *= 4;
t -= (uint64_t) t;
/* ---- motion ---- */
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->motion_time = t*t;
}
/* ---- state transition ---- */
if (locharacter_big_warder_reset_if_player_left_(c)) return;
if (c->since + step_dur + attack_dur <= c->cache.time) {
locharacter_big_warder_start_thrust_state_(c);
return;
}
}
static void locharacter_big_warder_start_combo_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
static const uint64_t parry = 400;
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
if (c->last_knockback_time+parry > c->cache.time) {
locharacter_big_warder_start_stunned_state_(c);
return;
}
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_COMBO;
c->direction = c->cache.player_pos.x - c->pos.x;
c->direction = c->direction > 0? 1: -1;
c->gravity = 0;
p->from = c->pos;
p->to = c->cache.player_pos;
p->to.x -= c->direction * locharacter_big_warder_size_.x;
p->to.y -= locharacter_big_warder_size_.y*.2f;
for (size_t i = 0; i < 4; ++i) {
loplayer_attack(c->player, &(loplayer_combat_attack_t) {
.attacker = c->super.super.id,
.start = c->ticker->time + (uint64_t) (beat*(i/2.f+1)),
.duration = beat/4,
.knockback = vec2(c->direction*.1f, 0),
.effect = loeffect_immediate_damage(c->recipient.status.attack),
});
}
}
static void locharacter_big_warder_update_thrust_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
static const uint64_t duration = beat;
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
c->cache.bullet_hittest = true;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
const uint64_t ti = c->cache.time - c->since;
if (p->phase <= 0) {
/* ---- disappear ---- */
instance->color.w *= 1 - ti/beat/4;
if (ti > beat/4) {
c->pos = p->to;
if (p->phase < 0) { /* backattack */
c->direction *= -1;
p->phase = 0;
}
++p->phase;
}
} else if (p->phase == 1) {
/* ---- appear ---- */
float t = (ti/beat/2 - .5f)*2;
if (t > 1) t = 1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->motion_time = t;
instance->color.w *= t;
if (ti > beat/2) ++p->phase;
} else {
/* ---- attack ---- */
float t = (ti/beat - .5f)*2;
if (t > 1) t = 1;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->motion_time = t;
}
/* ---- state transition ---- */
if (locharacter_big_warder_reset_if_player_left_(c)) return;
if (c->since + duration < c->cache.time) {
locharacter_big_warder_start_cooldown_state_(c);
return;
}
}
static void locharacter_big_warder_start_thrust_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
static const float bmelo = LOCHARACTER_BIG_WARDER_MELODY_B_BEAT;
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_THRUST;
c->direction = c->cache.player_pos.x - c->pos.x;
c->direction = c->direction > 0? 1: -1;
c->gravity = 0;
const bool backattack =
locharacter_event_holder_has_control(&p->event) &&
(c->cache.time - p->event.start_time >= beat*bmelo);
const float backattack_f = backattack? 1: -1;
p->to = c->cache.player_pos;
p->to.x += c->direction*locharacter_big_warder_size_.x*backattack_f;
p->to.y -= locharacter_big_warder_size_.y*.2f;
p->phase = backattack? -1: 0;
loplayer_attack(c->player, &(loplayer_combat_attack_t) {
.attacker = c->super.super.id,
.start = c->ticker->time + (uint64_t) (beat/2),
.duration = beat/2,
.knockback = vec2(-backattack_f*c->direction*.1f, 0),
.effect = loeffect_immediate_damage(c->recipient.status.attack),
});
}
static void locharacter_big_warder_update_cooldown_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
static const uint64_t duration = beat*4;
const uint64_t ti = c->cache.time - c->since;
const locharacter_big_warder_param_t* p = (typeof(p)) c->data;
c->cache.bullet_hittest = true;
c->cache.gravity = true;
/* ---- motion ---- */
float t = ti*1.f/duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t;
/* ---- position ---- */
if (ti < beat*2) {
t = ti/beat/2;
vec2_sub(&c->pos, &p->to, &p->from);
vec2_muleq(&c->pos, t*t*(3-2*t));
vec2_addeq(&c->pos, &p->from);
t = t*2-1;
t = 1-powf(MATH_ABS(t), 2);
c->pos.y += t*c->recipient.status.jump/2;
} else {
c->pos = vec2(0, 0);
}
/* ---- state transition ---- */
if (locharacter_big_warder_reset_if_player_left_(c)) return;
if (c->since + duration <= c->cache.time) {
locharacter_big_warder_start_shoot_state_(c);
return;
}
}
static void locharacter_big_warder_start_cooldown_state_(
locharacter_base_t* c) {
assert(c != NULL);
locharacter_big_warder_param_t* p = (typeof(p)) c->data;
if (locharacter_event_holder_has_control(&p->event)) {
static const uint64_t dur = LOCHARACTER_BIG_WARDER_MUSIC_DURATION;
if (p->event.start_time+dur < c->cache.time) {
locharacter_big_warder_finalize_event_(c);
return;
}
} else {
if (c->recipient.madness <= 0) {
locharacter_big_warder_start_dead_state_(c);
return;
}
}
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_COOLDOWN;
p->from = c->pos;
p->to = vec2(0, 0);
}
static void locharacter_big_warder_update_stunned_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
static const uint64_t duration = beat*4;
c->cache.gravity = true;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.f/duration;
t *= 6;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
if (t < 1) {
t = 1-powf(1-t, 6);
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->motion_time = t;
} else {
t = (t-1)/5;
if (t > 1) t = 1;
t = t*t*(3-2*t);
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t;
}
/* ---- state transition ---- */
if (c->since + duration <= c->cache.time) {
locharacter_big_warder_start_cooldown_state_(c);
return;
}
}
static void locharacter_big_warder_start_stunned_state_(
locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_STUNNED;
loeffect_recipient_apply_effect(
&c->recipient, &loeffect_immediate_damage(1.f));
}
static void locharacter_big_warder_update_dead_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const uint64_t anime_duration = 4000;
static const uint64_t duration = 30000;
c->cache.gravity = true;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.f/anime_duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->motion_time = t*t;
instance->color.w *= 1-t;
/* ---- state transition ---- */
if (c->since+duration < c->cache.time) {
locharacter_big_warder_start_wait_state_(c);
return;
}
}
static void locharacter_big_warder_start_dead_state_(
locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_DEAD;
loplayer_gain_faith(c->player, .8f);
}
bool locharacter_big_warder_update(locharacter_base_t* base) {
assert(base != NULL);
static const vec2_t size = locharacter_big_warder_size_;
static const float height = size.y * 1.4f;
static const float drawsz = MATH_MAX(size.x, size.y);
locharacter_big_warder_param_t* p = (typeof(p)) base->data;
loeffect_recipient_update(
&base->recipient, &locharacter_big_warder_base_status_);
if (!locharacter_event_holder_update(&p->event)) {
locharacter_big_warder_start_wait_state_(base);
}
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_WARDER,
.marker_offset = vec2(0, height - drawsz),
.pos = vec2(0, drawsz - height),
.size = vec2(drawsz, drawsz),
.color = vec4(.2f, 0, 0, 1),
};
switch (base->state) {
case LOCHARACTER_STATE_WAIT:
locharacter_big_warder_update_wait_state_(base);
break;
case LOCHARACTER_STATE_WALK:
locharacter_big_warder_update_walk_state_(base);
break;
case LOCHARACTER_STATE_SHOOT:
locharacter_big_warder_update_shoot_state_(base);
break;
case LOCHARACTER_STATE_COMBO:
locharacter_big_warder_update_combo_state_(base);
break;
case LOCHARACTER_STATE_THRUST:
locharacter_big_warder_update_thrust_state_(base);
break;
case LOCHARACTER_STATE_COOLDOWN:
locharacter_big_warder_update_cooldown_state_(base);
break;
case LOCHARACTER_STATE_STUNNED:
locharacter_big_warder_update_stunned_state_(base);
break;
case LOCHARACTER_STATE_DEAD:
locharacter_big_warder_update_dead_state_(base);
break;
default:
locharacter_big_warder_start_wait_state_(base);
}
locharacter_big_warder_update_passive_action_(base);
base->cache.height = height;
base->cache.instance.marker = !!base->cache.bullet_hittest;
base->cache.instance.size.x *= base->direction;
return true;
}
void locharacter_big_warder_build(
locharacter_base_t* base, loentity_id_t ground) {
assert(base != NULL);
base->type = LOCHARACTER_TYPE_BIG_WARDER;
base->ground = ground;
base->pos = vec2(.7f, 0);
base->direction = 1;
base->state = LOCHARACTER_STATE_WAIT;
base->since = base->cache.time;
locharacter_big_warder_param_t* p = (typeof(p)) base->data;
*p = (typeof(*p)) {0};
locharacter_event_holder_initialize(
&p->event,
&base->res->music.boss_big_warder,
base,
LOCHARACTER_BIG_WARDER_MUSIC_DURATION,
0);
}
void locharacter_big_warder_tear_down(locharacter_base_t* base) {
assert(base != NULL);
locharacter_big_warder_param_t* p = (typeof(p)) base->data;
locharacter_event_holder_deinitialize(&p->event);
}
void locharacter_big_warder_pack_data(
const locharacter_base_t* base, msgpack_packer* packer) {
assert(base != NULL);
assert(packer != NULL);
const locharacter_big_warder_param_t* p = (typeof(p)) base->data;
msgpack_pack_map(packer, LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_COUNT);
# define pack_(name, var) do { \
mpkutil_pack_str(packer, name); \
LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \
} while (0)
LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(pack_);
# undef pack_
}
bool locharacter_big_warder_unpack_data(
locharacter_base_t* base, const msgpack_object* obj) {
assert(base != NULL);
locharacter_big_warder_param_t* p = (typeof(p)) base->data;
const msgpack_object_map* root = mpkutil_get_map(obj);
# define item_(v) mpkutil_get_map_item_by_str(root, v)
# define unpack_(name, var) do { \
if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \
return false; \
} \
} while (0)
LOCHARACTER_BIG_WARDER_PARAM_TO_PACK_EACH_(unpack_);
# undef unpack_
# undef item_
locharacter_event_holder_initialize(
&p->event,
&base->res->music.boss_big_warder,
base,
LOCHARACTER_BIG_WARDER_MUSIC_DURATION,
p->event.start_time);
return true;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <stdbool.h>
#include <msgpack.h>
#include "core/loentity/entity.h"
#include "./base.h"
bool
locharacter_big_warder_update(
locharacter_base_t* base
);
void
locharacter_big_warder_build(
locharacter_base_t* base,
loentity_id_t ground
);
void
locharacter_big_warder_tear_down(
locharacter_base_t* base
);
void
locharacter_big_warder_pack_data(
const locharacter_base_t* base,
msgpack_packer* packer
);
bool
locharacter_big_warder_unpack_data(
locharacter_base_t* base,
const msgpack_object* obj
);

View File

@@ -0,0 +1,157 @@
static void locharacter_big_warder_update_passive_action_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_BIG_WARDER_BEAT;
const locharacter_big_warder_param_t* p = (typeof(p)) c->data;
if (!locharacter_event_holder_has_control(&p->event)) return;
const uint64_t dt = c->cache.time - c->last_update_time;
const uint64_t t = c->cache.time - p->event.start_time;
const float beats = t/beat;
const float last_beats = t > dt? (t-dt)/beat: 0;
# define name_pos_(name, x, y) \
locommon_position_t name = c->cache.ground->super.pos; \
vec2_addeq(&name.fract, &vec2(x, y)); \
locommon_position_reduce(&name);
name_pos_(top, 0, .8f);
name_pos_(center, 0, .25f);
name_pos_(left, -.3f, .2f);
name_pos_(right, .3f, .2f);
name_pos_(leftbottom, -.4f, .1f);
name_pos_(rightbottom, .4f, .1f);
name_pos_(leftbottom_off, -.6f, .1f);
name_pos_(rightbottom_off, .6f, .1f);
# undef name_pos_
# define trigger_on_(x) (last_beats < (x) && beats >= (x))
/* ---- intro -> A melody ---- */
if (trigger_on_(12)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = center,
.size = vec2(.4f, .4f),
.angle = MATH_PI/4,
.color = vec4(1, 1, 1, .4f),
.silent = true,
.beat = beat,
.step = 4,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/4),
}));
loentity_store_add(c->entities, &b->super.super);
}
/* ---- A melody ---- */
for (size_t i = 48; i < 80; i+=8) {
for (size_t j = 0; j < 2; ++j) {
if (trigger_on_(i-4 + j*4)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = j? leftbottom: rightbottom,
.size = vec2(.05f, .15f),
.angle = j? 0: MATH_PI,
.color = vec4(1, 1, 1, .6f),
.silent = true,
.beat = beat,
.step = 4,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
if (trigger_on_(i + j*4)) {
static const float speed = 1.4f;
static const float accel = .7f / (beat*2);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = j? leftbottom_off: rightbottom_off,
.size = vec2(.05f, .15f),
.velocity = vec2(j? speed: -speed, 0),
.acceleration = vec2(j? -accel: accel, 0),
.color = vec4(1, 1, 1, .8f),
.duration = beat*2,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
}
/* ---- B melody ---- */
static const int32_t bmelo_trigger_beats[] = {92, 108};
static const size_t bmelo_trigger_counts =
sizeof(bmelo_trigger_beats)/sizeof(bmelo_trigger_beats[0]);
for (size_t i = 0; i < bmelo_trigger_counts; ++i) {
const int32_t st = bmelo_trigger_beats[i];
for (int32_t j = 0; j < 4; ++j) {
if (trigger_on_(st + j/2.f)) {
for (int32_t x = -2; x <= 2; ++x) {
locommon_position_t pos = center;
vec2_addeq(&pos.fract, &vec2(x/2.f*.45f, (j-1)/4.f*.3f));
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = pos,
.size = vec2(.1f, .1f),
.angle = MATH_PI/4,
.color = vec4(1, 1, 1, .6f),
.silent = true,
.beat = beat*2,
.step = 2,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
}
}
/* ---- C melody ---- */
for (int32_t i = 0; i < 8; ++i) {
for (int32_t x = -10; x <= 10; ++x) {
if (trigger_on_(112 + i*4 + (x+10)/100.f)) {
locommon_position_t pos = center;
pos.fract.x += x/10.f*.47f;
pos.fract.y -= .13f;
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = pos,
.size = vec2(.06f, .06f),
.angle = MATH_PI/4,
.color = vec4(1, 1, 1, .4f),
.silent = true,
.beat = beat,
.step = 4,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
}
# undef trigger_on_
}

265
core/locharacter/cavia.c Normal file
View File

@@ -0,0 +1,265 @@
#include "./cavia.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/entity.h"
#include "core/loplayer/event.h"
#include "core/loplayer/player.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./misc.h"
static const vec2_t locharacter_cavia_size_ = vec2(.02f, .05f);
static const loeffect_recipient_status_t locharacter_cavia_base_status_ = {
.attack = .2f,
.defence = .1f,
.speed = .05f,
};
static void
locharacter_cavia_start_walk_state_(
locharacter_base_t* base
);
static bool
locharacter_cavia_start_thrust_state_(
locharacter_base_t* base
);
static void
locharacter_cavia_start_cooldown_state_(
locharacter_base_t* base
);
static void
locharacter_cavia_start_dead_state_(
locharacter_base_t* base
);
static void locharacter_cavia_update_walk_state_(locharacter_base_t* base) {
assert(base != NULL);
/* ---- movement ---- */
const vec2_t* gsize = &base->cache.ground->size;
const float s = base->recipient.status.speed;
base->pos.x += base->ticker->delta_f * base->direction * s / gsize->x;
if (MATH_ABS(base->pos.x) > 1) base->direction *= -1;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &base->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
const int32_t p = 70/s;
const float t = (base->ticker->time - base->since)%p*2.0f/p - 1;
instance->motion_time = MATH_ABS(t);
/* ---- dead ---- */
if (base->recipient.madness <= 0) {
locharacter_cavia_start_dead_state_(base);
return;
}
/* ---- trigger thrust ---- */
if (loplayer_event_get_param(base->player->event) == NULL) {
vec2_t dist;
locommon_position_sub(
&dist, &base->player->entity.super.super.pos, &base->super.super.pos);
const float sdist_x = dist.x * base->direction;
if (MATH_ABS(dist.y) < locharacter_cavia_size_.y &&
sdist_x >= locharacter_cavia_size_.x &&
sdist_x <= locharacter_cavia_size_.x*2) {
if (locharacter_cavia_start_thrust_state_(base)) return;
}
}
}
static void locharacter_cavia_start_walk_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_WALK;
}
static void locharacter_cavia_update_thrust_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t premotion = 1200;
static const uint64_t duration = 1500;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &base->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
float t = (base->ticker->time - base->since)*1.0f/premotion;
if (t > 1) t = 1;
instance->motion_time = t*t*t*t;
/* ---- cooldown ---- */
if (base->since+duration <= base->ticker->time) {
/* TODO(catfoot): go to cooldown */
locharacter_cavia_start_cooldown_state_(base);
return;
}
}
static bool locharacter_cavia_start_thrust_state_(locharacter_base_t* base) {
assert(base != NULL);
const loplayer_combat_attack_t attack = {
.attacker = base->super.super.id,
.start = base->ticker->time + 1000,
.duration = 500,
.knockback = vec2(base->direction*.1f, 0),
.effect = loeffect_immediate_damage(base->recipient.status.attack),
};
if (!loplayer_attack(base->player, &attack)) return false;
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_THRUST;
return true;
}
static void locharacter_cavia_update_cooldown_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t duration = 500;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &base->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
float t = (base->ticker->time - base->since)*1.0f/duration;
if (t > 1) t = 1;
instance->motion_time = t*t*(3-2*t);
/* ---- cooldown ---- */
if (base->since+duration <= base->ticker->time) {
if (base->recipient.madness <= 0) {
locharacter_cavia_start_dead_state_(base);
return;
} else {
locharacter_cavia_start_walk_state_(base);
return;
}
}
}
static void locharacter_cavia_start_cooldown_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_COOLDOWN;
}
static void locharacter_cavia_update_dead_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t anime = 500;
static const uint64_t duration = 30000;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &base->cache.instance;
if (base->since+anime > base->ticker->time) {
const float t = (base->ticker->time - base->since)*1.0f/anime;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->motion_time = t*t;
} else if (base->since+anime*2 > base->ticker->time) {
const float t = (base->ticker->time - anime - base->since)*1.0f/anime;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->motion_time = t*t;
} else if (base->ticker->time+anime > base->since+duration) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time =
1 - (base->since + duration - base->ticker->time)*1.0f/anime;
} else {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
}
/* ---- revive ---- */
if (base->since+duration <= base->ticker->time) {
loeffect_recipient_reset(&base->recipient);
locharacter_cavia_start_walk_state_(base);
return;
}
}
static void locharacter_cavia_start_dead_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_DEAD;
loplayer_gain_faith(base->player, .1f);
}
bool locharacter_cavia_update(locharacter_base_t* base) {
assert(base != NULL);
static const vec2_t size = locharacter_cavia_size_;
static const vec4_t color = vec4(.1f, .1f, .1f, 1);
static const float height = size.y*1.4f;
static const float drawsz = MATH_MAX(size.x, size.y);
loeffect_recipient_update(&base->recipient, &locharacter_cavia_base_status_);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_CAVIA,
.marker_offset = vec2(0, height - drawsz),
.pos = vec2(0, drawsz - height),
.size = vec2(drawsz, drawsz),
.color = color,
};
switch (base->state) {
case LOCHARACTER_STATE_WALK:
locharacter_cavia_update_walk_state_(base);
break;
case LOCHARACTER_STATE_THRUST:
locharacter_cavia_update_thrust_state_(base);
break;
case LOCHARACTER_STATE_COOLDOWN:
locharacter_cavia_update_cooldown_state_(base);
break;
case LOCHARACTER_STATE_DEAD:
locharacter_cavia_update_dead_state_(base);
break;
default:
locharacter_cavia_start_walk_state_(base);
}
base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD;
base->cache.height = height;
base->cache.instance.size.x *= base->direction;
base->cache.instance.marker = !!base->cache.bullet_hittest;
return true;
}
void locharacter_cavia_build(
locharacter_base_t* base, const locharacter_cavia_param_t* param) {
assert(base != NULL);
assert(param != NULL);
base->type = LOCHARACTER_TYPE_CAVIA;
base->ground = param->ground;
base->pos = vec2(param->pos, 0);
base->direction = param->direction == 1? 1: -1;
base->state = LOCHARACTER_STATE_WALK;
base->since = base->ticker->time;
}

36
core/locharacter/cavia.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <stdbool.h>
#include <msgpack.h>
#include "util/math/vector.h"
#include "core/loentity/entity.h"
#include "./base.h"
typedef struct {
loentity_id_t ground;
float pos;
float direction;
} locharacter_cavia_param_t;
bool
locharacter_cavia_update(
locharacter_base_t* base
);
void
locharacter_cavia_build(
locharacter_base_t* base,
const locharacter_cavia_param_t* param
);
#define locharacter_cavia_tear_down(base)
#define locharacter_cavia_pack_data(base, packer) \
msgpack_pack_nil(packer)
#define locharacter_cavia_unpack_data(base, obj) \
(obj != NULL)

View File

@@ -0,0 +1,138 @@
#include "./encephalon.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <msgpack.h>
#include "util/chaos/xorshift.h"
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/mpkutil/get.h"
#include "util/mpkutil/pack.h"
#include "core/locommon/msgpack.h"
#include "core/loentity/entity.h"
#include "core/loplayer/player.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./misc.h"
typedef struct {
float progress;
} locharacter_encephalon_param_t;
_Static_assert(
sizeof(locharacter_encephalon_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE);
static const vec2_t locharacter_encephalon_size_ = vec2(.1f, .1f);
#define LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(PROC) do { \
PROC("progress", progress); \
} while (0)
#define LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_COUNT 1
static void locharacter_encephalon_update_progress_(locharacter_base_t* base) {
assert(base != NULL);
locharacter_encephalon_param_t* p = (typeof(p)) base->data;
const vec2_t* player = &base->cache.player_pos;
const bool near =
MATH_ABS(player->x) < 1 &&
0 <= player->y && player->y < locharacter_encephalon_size_.y;
if (near && base->state == LOCHARACTER_STATE_WAIT) {
p->progress += base->ticker->delta_f;
if (p->progress >= 1) {
loplayer_touch_encephalon(base->player);
base->state = LOCHARACTER_STATE_COOLDOWN;
loresource_sound_play(base->res->sound, "touch_gate");
}
} else {
p->progress -= base->ticker->delta_f * 2;
if (!near) base->state = LOCHARACTER_STATE_WAIT;
}
p->progress = MATH_CLAMP(p->progress, 0, 1);
}
bool locharacter_encephalon_update(locharacter_base_t* base) {
assert(base != NULL);
locharacter_encephalon_update_progress_(base);
const locharacter_encephalon_param_t* p = (typeof(p)) base->data;
base->pos = vec2(0, locharacter_encephalon_size_.y);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_ENCEPHALON,
.from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.color = vec4(p->progress*.5f, 0, 0, .95f),
.size = locharacter_encephalon_size_,
};
if (base->state == LOCHARACTER_STATE_COOLDOWN && p->progress > 0) {
base->cache.instance.color.w *=
chaos_xorshift(base->ticker->time)%100/100.f;
}
return true;
}
void locharacter_encephalon_build(
locharacter_base_t* base, loentity_id_t ground) {
assert(base != NULL);
base->type = LOCHARACTER_TYPE_ENCEPHALON;
base->ground = ground;
base->pos = vec2(0, 0);
base->state = LOCHARACTER_STATE_WAIT;
}
void locharacter_encephalon_pack_data(
const locharacter_base_t* base, msgpack_packer* packer) {
assert(base != NULL);
assert(packer != NULL);
const locharacter_encephalon_param_t* p = (typeof(p)) base->data;
msgpack_pack_map(packer, LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_COUNT);
# define pack_(name, var) do { \
mpkutil_pack_str(packer, name); \
LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \
} while (0)
LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(pack_);
# undef pack_
}
bool locharacter_encephalon_unpack_data(
locharacter_base_t* base, const msgpack_object* obj) {
assert(base != NULL);
locharacter_encephalon_param_t* p = (typeof(p)) base->data;
const msgpack_object_map* root = mpkutil_get_map(obj);
# define item_(v) mpkutil_get_map_item_by_str(root, v)
# define unpack_(name, var) do { \
if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \
return false; \
} \
} while (0)
LOCHARACTER_ENCEPHALON_PARAM_TO_PACK_EACH_(unpack_);
return true;
# undef unpack_
# undef item_
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stdbool.h>
#include <msgpack.h>
#include "core/loentity/entity.h"
#include "./base.h"
bool
locharacter_encephalon_update(
locharacter_base_t* base
);
void
locharacter_encephalon_build(
locharacter_base_t* base,
loentity_id_t ground
);
#define locharacter_encephalon_tear_down(base)
void
locharacter_encephalon_pack_data(
const locharacter_base_t* base,
msgpack_packer* packer
);
bool
locharacter_encephalon_unpack_data(
locharacter_base_t* base,
const msgpack_object* obj
);

View File

@@ -0,0 +1,607 @@
#include "./greedy_scientist.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/chaos/xorshift.h"
#include "util/math/algorithm.h"
#include "util/math/constant.h"
#include "util/math/vector.h"
#include "util/mpkutil/get.h"
#include "util/mpkutil/pack.h"
#include "core/lobullet/base.h"
#include "core/lobullet/bomb.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/easing.h"
#include "core/locommon/msgpack.h"
#include "core/loentity/entity.h"
#include "core/loplayer/combat.h"
#include "core/loplayer/event.h"
#include "core/loplayer/player.h"
#include "core/loresource/music.h"
#include "core/loresource/text.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./misc.h"
#include "./util.h"
typedef struct {
locharacter_event_holder_t event;
int32_t phase;
vec2_t from;
vec2_t to;
} locharacter_greedy_scientist_param_t;
_Static_assert(
sizeof(locharacter_greedy_scientist_param_t) <= LOCHARACTER_BASE_DATA_MAX_SIZE);
#define LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(PROC) do { \
PROC("event-start-time", event.start_time); \
PROC("phase", phase); \
PROC("from", from); \
PROC("to", to); \
} while (0)
#define LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_COUNT 4
static const vec2_t locharacter_greedy_scientist_size_ = vec2(.03f, .07f);
static const loeffect_recipient_status_t
locharacter_greedy_scientist_base_status_ = {
.attack = .2f,
.defence = .85f,
.speed = .1f,
.jump = .1f,
};
#define LOCHARACTER_GREEDY_SCIENTIST_BEAT (60000/140.f) /* 140 BPM */
#define LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION \
((uint64_t) LOCHARACTER_GREEDY_SCIENTIST_BEAT*128)
#define LOCHARACTER_GREEDY_SCIENTIST_MELODY_B_BEAT 52
#include "./greedy_scientist.private.h"
static void
locharacter_greedy_scientist_start_wait_state_(
locharacter_base_t* c
);
static void
locharacter_greedy_scientist_start_standup_state_(
locharacter_base_t* c
);
static void
locharacter_greedy_scientist_start_combo_state_(
locharacter_base_t* c
);
static void
locharacter_greedy_scientist_start_cooldown_state_(
locharacter_base_t* c
);
static void
locharacter_greedy_scientist_start_stunned_state_(
locharacter_base_t* c
);
static void
locharacter_greedy_scientist_start_dead_state_(
locharacter_base_t* c
);
static void locharacter_greedy_scientist_finalize_event_(locharacter_base_t* c) {
assert(c != NULL);
/* This function must start next state. */
locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
assert(p != NULL);
locharacter_event_holder_release_control(&p->event);
if (c->recipient.madness > 0) {
loentity_character_apply_effect(
&c->player->entity.super, &loeffect_curse_trigger());
locharacter_greedy_scientist_start_wait_state_(c);
} else {
loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_PHILOSOPHER);
locharacter_greedy_scientist_start_dead_state_(c);
}
}
static bool locharacter_greedy_scientist_reset_if_player_left_(
locharacter_base_t* c) {
assert(c != NULL);
if (MATH_ABS(c->cache.player_pos.x) < 1 &&
0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) {
return false;
}
locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
locharacter_event_holder_release_control(&p->event);
locharacter_greedy_scientist_start_wait_state_(c);
return true;
}
static void locharacter_greedy_scientist_update_wait_state_(locharacter_base_t* c) {
assert(c != NULL);
static const uint64_t min_duration = LOCHARACTER_GREEDY_SCIENTIST_BEAT*4;
c->cache.gravity = true;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = 0;
/* ---- state transition ---- */
if (c->since + min_duration <= c->cache.time) {
if (MATH_ABS(c->cache.player_pos.x) < 1 &&
0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) {
vec2_t diff;
vec2_sub(&diff, &c->cache.player_pos, &c->pos);
if (vec2_pow_length(&diff) < .5f*.5f) {
locharacter_greedy_scientist_start_standup_state_(c);
return;
}
}
}
}
static void locharacter_greedy_scientist_start_wait_state_(locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_WAIT;
}
static void locharacter_greedy_scientist_update_standup_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
static const uint64_t period = beat*4;
locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
const bool event = locharacter_event_holder_has_control(&p->event);
const uint64_t duration = event? beat*20: beat*8;
/* ---- motion ---- */
float t = (c->cache.time - c->since)%period*1.f/period;
t = t*2-1;
t = MATH_ABS(t);
t = t*t*(3-2*t);
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2;
instance->motion_time = t;
/* ---- event ---- */
if (event) {
p->event.param->cinescope = true;
p->event.param->hide_hud = true;
if (c->since+(p->phase*8+4)*beat < c->cache.time) {
static const char* text[] = {
"boss_greedy_scientist_line0",
"boss_greedy_scientist_line1",
};
if (p->phase < (int32_t) (sizeof(text)/sizeof(text[0]))) {
const char* v = loresource_text_get(
c->res->lang, text[(size_t) p->phase]);
loplayer_event_param_set_line(p->event.param, v, strlen(v));
} else {
loplayer_event_param_set_line(p->event.param, "", 0);
}
++p->phase;
}
}
/* ---- state transition ---- */
if (locharacter_greedy_scientist_reset_if_player_left_(c)) return;
if (c->since + duration <= c->cache.time) {
if (event) {
p->event.param->cinescope = false;
p->event.param->hide_hud = false;
loplayer_event_param_set_line(p->event.param, "", 0);
}
locharacter_greedy_scientist_start_combo_state_(c);
return;
}
}
static void locharacter_greedy_scientist_start_standup_state_(
locharacter_base_t* c) {
assert(c != NULL);
locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_STANDUP;
loeffect_recipient_reset(&c->recipient);
if (!loeffect_stance_set_has(
&c->player->status.stances, LOEFFECT_STANCE_ID_PHILOSOPHER)) {
locharacter_event_holder_take_control(&p->event);
}
p->phase = 0;
}
static void locharacter_greedy_scientist_update_combo_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float offset_y = locharacter_greedy_scientist_size_.y*1.5f;
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
static const uint64_t step_dur = beat;
static const uint64_t attack_dur = beat*3;
const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
c->cache.bullet_hittest = true;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2;
if (c->since + step_dur > c->cache.time) {
const float t = (c->cache.time - c->since)*1.f/step_dur;
vec2_t to = p->to;
to.y += offset_y;
/* ---- position ---- */
vec2_t dist;
vec2_sub(&dist, &to, &p->from);
vec2_muleq(&dist, t*t*(3-2*t));
c->pos = p->from;
vec2_addeq(&c->pos, &dist);
/* ---- motion ---- */
instance->motion_time = 1-powf(2*t-1, 4);
} else {
float t = (c->cache.time - c->since - step_dur)*1.f/attack_dur;
t *= 3;
t -= (uint64_t) t;
t = t*t;
/* ---- position ---- */
c->pos.y -= c->ticker->delta_f*t/3 * offset_y;
/* ---- motion ---- */
instance->motion_time = t;
}
/* ---- state transition ---- */
if (locharacter_greedy_scientist_reset_if_player_left_(c)) return;
if (c->since + step_dur + attack_dur <= c->cache.time) {
locharacter_greedy_scientist_start_cooldown_state_(c);
return;
}
}
static void locharacter_greedy_scientist_start_combo_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
static const uint64_t parry = 100;
locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
if (c->last_knockback_time+parry > c->cache.time) {
locharacter_greedy_scientist_start_stunned_state_(c);
return;
}
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_COMBO;
c->direction = c->cache.player_pos.x - c->pos.x;
c->direction = c->direction > 0? 1: -1;
c->gravity = 0;
p->from = c->pos;
p->to = c->cache.player_pos;
const size_t delay_index = chaos_xorshift(c->cache.time)&1? 2: 3;
for (size_t i = 1; i < 4; ++i) {
const uint64_t delay = i >= delay_index? beat/2: 0;
loplayer_attack(c->player, &(loplayer_combat_attack_t) {
.attacker = c->super.super.id,
.start = c->ticker->time + (uint64_t) (beat*i) + delay,
.duration = beat/2,
.effect = loeffect_immediate_damage(c->recipient.status.attack),
});
}
}
static void locharacter_greedy_scientist_update_cooldown_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
static const uint64_t duration = beat*4;
const uint64_t ti = c->cache.time - c->since;
const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
c->cache.bullet_hittest = true;
c->cache.gravity = true;
/* ---- motion ---- */
float t = ti*1.f/duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t;
/* ---- state transition ---- */
if (locharacter_greedy_scientist_reset_if_player_left_(c)) return;
if (c->since + duration <= c->cache.time) {
locharacter_greedy_scientist_start_combo_state_(c);
return;
}
}
static void locharacter_greedy_scientist_start_cooldown_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
static const uint64_t bmelo = LOCHARACTER_GREEDY_SCIENTIST_MELODY_B_BEAT*beat;
locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
if (locharacter_event_holder_has_control(&p->event)) {
static const uint64_t dur = LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION;
if (p->event.start_time+dur < c->cache.time) {
locharacter_greedy_scientist_finalize_event_(c);
return;
}
} else {
if (c->recipient.madness <= 0) {
locharacter_greedy_scientist_start_dead_state_(c);
return;
}
}
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_COOLDOWN;
if (locharacter_event_holder_has_control(&p->event)) {
locharacter_greedy_scientist_trigger_chained_mines_(c);
if (c->cache.time - p->event.start_time > bmelo) {
locharacter_greedy_scientist_shoot_amnesia_bullet_(c);
}
}
}
static void locharacter_greedy_scientist_update_stunned_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
static const uint64_t duration = beat*4;
c->cache.gravity = true;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.f/duration;
t *= 6;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
if (t < 1) {
t = 1-powf(1-t, 6);
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->motion_time = t;
} else {
t = (t-1)/5;
if (t > 1) t = 1;
t = t*t*(3-2*t);
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t;
}
/* ---- state transition ---- */
if (c->since + duration <= c->cache.time) {
locharacter_greedy_scientist_start_cooldown_state_(c);
return;
}
}
static void locharacter_greedy_scientist_start_stunned_state_(
locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_STUNNED;
loeffect_recipient_apply_effect(
&c->recipient, &loeffect_immediate_damage(1.f));
}
static void locharacter_greedy_scientist_update_dead_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const uint64_t anime_duration = 4000;
static const uint64_t duration = 30000;
c->cache.gravity = true;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.f/anime_duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->motion_time = t*t;
instance->color.w *= 1-t;
/* ---- state transition ---- */
if (c->since+duration < c->cache.time) {
locharacter_greedy_scientist_start_wait_state_(c);
return;
}
}
static void locharacter_greedy_scientist_start_dead_state_(
locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_DEAD;
loplayer_gain_faith(c->player, .8f);
}
bool locharacter_greedy_scientist_update(locharacter_base_t* base) {
assert(base != NULL);
static const vec2_t size = locharacter_greedy_scientist_size_;
static const float height = size.y * 1.4f;
static const float drawsz = MATH_MAX(size.x, size.y);
locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data;
loeffect_recipient_update(
&base->recipient, &locharacter_greedy_scientist_base_status_);
if (!locharacter_event_holder_update(&p->event)) {
locharacter_greedy_scientist_start_wait_state_(base);
}
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_SCIENTIST,
.marker_offset = vec2(0, height - drawsz),
.pos = vec2(0, drawsz - height),
.size = vec2(drawsz, drawsz),
.color = vec4(.2f, 0, 0, 1),
};
switch (base->state) {
case LOCHARACTER_STATE_WAIT:
locharacter_greedy_scientist_update_wait_state_(base);
break;
case LOCHARACTER_STATE_STANDUP:
locharacter_greedy_scientist_update_standup_state_(base);
break;
case LOCHARACTER_STATE_COMBO:
locharacter_greedy_scientist_update_combo_state_(base);
break;
case LOCHARACTER_STATE_COOLDOWN:
locharacter_greedy_scientist_update_cooldown_state_(base);
break;
case LOCHARACTER_STATE_STUNNED:
locharacter_greedy_scientist_update_stunned_state_(base);
break;
case LOCHARACTER_STATE_DEAD:
locharacter_greedy_scientist_update_dead_state_(base);
break;
default:
locharacter_greedy_scientist_start_wait_state_(base);
}
locharacter_greedy_scientist_update_passive_action_(base);
base->cache.height = height;
base->cache.instance.marker = !!base->cache.bullet_hittest;
base->cache.instance.size.x *= base->direction;
return true;
}
void locharacter_greedy_scientist_build(
locharacter_base_t* base, loentity_id_t ground) {
assert(base != NULL);
base->type = LOCHARACTER_TYPE_GREEDY_SCIENTIST;
base->ground = ground;
base->pos = vec2(.7f, 0);
base->direction = 1;
base->state = LOCHARACTER_STATE_WAIT;
base->since = base->cache.time;
locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data;
*p = (typeof(*p)) {0};
locharacter_event_holder_initialize(
&p->event,
&base->res->music.boss_greedy_scientist,
base,
LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION,
0);
}
void locharacter_greedy_scientist_tear_down(locharacter_base_t* base) {
assert(base != NULL);
locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data;
locharacter_event_holder_deinitialize(&p->event);
}
void locharacter_greedy_scientist_pack_data(
const locharacter_base_t* base, msgpack_packer* packer) {
assert(base != NULL);
assert(packer != NULL);
const locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data;
msgpack_pack_map(packer, LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_COUNT);
# define pack_(name, var) do { \
mpkutil_pack_str(packer, name); \
LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \
} while (0)
LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(pack_);
# undef pack_
}
bool locharacter_greedy_scientist_unpack_data(
locharacter_base_t* base, const msgpack_object* obj) {
assert(base != NULL);
locharacter_greedy_scientist_param_t* p = (typeof(p)) base->data;
const msgpack_object_map* root = mpkutil_get_map(obj);
# define item_(v) mpkutil_get_map_item_by_str(root, v)
# define unpack_(name, var) do { \
if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \
return false; \
} \
} while (0)
LOCHARACTER_GREEDY_SCIENTIST_PARAM_TO_PACK_EACH_(unpack_);
# undef unpack_
# undef item_
locharacter_event_holder_initialize(
&p->event,
&base->res->music.boss_greedy_scientist,
base,
LOCHARACTER_GREEDY_SCIENTIST_MUSIC_DURATION,
p->event.start_time);
return true;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <stdbool.h>
#include <msgpack.h>
#include "core/loentity/entity.h"
#include "./base.h"
bool
locharacter_greedy_scientist_update(
locharacter_base_t* base
);
void
locharacter_greedy_scientist_build(
locharacter_base_t* base,
loentity_id_t ground
);
void
locharacter_greedy_scientist_tear_down(
locharacter_base_t* base
);
void
locharacter_greedy_scientist_pack_data(
const locharacter_base_t* base,
msgpack_packer* packer
);
bool
locharacter_greedy_scientist_unpack_data(
locharacter_base_t* base,
const msgpack_object* obj
);

View File

@@ -0,0 +1,228 @@
static void locharacter_greedy_scientist_trigger_chained_mines_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
locommon_position_t center = c->cache.ground->super.pos;
center.fract.y += .1f;
locommon_position_reduce(&center);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = center,
.size = vec2(.07f, .07f),
.angle = 0,
.color = vec4(1, .9f, .9f, .8f),
.beat = beat,
.step = 2,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
for (int32_t i = -6; i <= 6; ++i) {
locommon_position_t pos = center;
pos.fract.x += i/6.f*.5f;
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = pos,
.size = vec2(.05f, .05f),
.angle = 0,
.color = vec4(1, 1, 1, .4f),
.silent = true,
.beat = beat*1.5f,
.step = 2,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
static void locharacter_greedy_scientist_shoot_amnesia_bullet_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
locommon_position_t pos = c->super.super.pos;
pos.fract.y += locharacter_greedy_scientist_size_.y*1.5f;
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_linear_light_build(b, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = pos,
.size = vec2(.06f, .06f),
.velocity = vec2(0, .1f),
.color = vec4(.8f, .8f, .6f, .8f),
.acceleration = vec2(0, -2),
.duration = 1000,
.effect = loeffect_amnesia(c->ticker->time, beat*8),
}));
loentity_store_add(c->entities, &b->super.super);
}
static void locharacter_greedy_scientist_update_passive_action_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_GREEDY_SCIENTIST_BEAT;
const locharacter_greedy_scientist_param_t* p = (typeof(p)) c->data;
if (!locharacter_event_holder_has_control(&p->event)) return;
const uint64_t dt = c->cache.time - c->last_update_time;
const uint64_t t = c->cache.time - p->event.start_time;
const float beats = t/beat;
const float last_beats = t > dt? (t-dt)/beat: 0;
# define name_pos_(name, x, y) \
locommon_position_t name = c->cache.ground->super.pos; \
vec2_addeq(&name.fract, &vec2(x, y)); \
locommon_position_reduce(&name);
name_pos_(top, 0, .8f);
name_pos_(lefttop, -.3f, .8f);
name_pos_(righttop, .3f, .8f);
name_pos_(center, 0, .4f);
name_pos_(left, -.3f, .2f);
name_pos_(right, .3f, .2f);
# undef name_pos_
# define trigger_on_(x) (last_beats < (x) && beats >= (x))
/* ---- intro -> A melody ---- */
for (size_t i = 0; i < 2; ++i) {
if (trigger_on_(16)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = i? left: right,
.size = vec2(.1f*cos(MATH_PI/6), .1f),
.angle = -MATH_PI/2,
.color = vec4(1, 1, 1, .4f),
.silent = true,
.beat = beat*2,
.step = 1,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/4),
}));
loentity_store_add(c->entities, &b->super.super);
}
if (trigger_on_(16.5f)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = i? left: right,
.size = vec2(.1f*cos(MATH_PI/6), .1f),
.angle = MATH_PI/2,
.color = vec4(1, 1, 1, .4f),
.silent = true,
.beat = beat*2,
.step = 1,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/4),
}));
loentity_store_add(c->entities, &b->super.super);
}
if (trigger_on_(17)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = i? left: right,
.size = vec2(.12f, .12f),
.angle = MATH_PI/4,
.color = vec4(1, 1, 1, .4f),
.silent = true,
.beat = beat*2,
.step = 1,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/4),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
for (size_t i = 0; i < 4; ++i) {
if (trigger_on_(18 + i*.5f)) {
locommon_position_t pos = center;
pos.fract.y -= .1f * i;
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = pos,
.size = vec2(.05f, .2f),
.angle = -MATH_PI/2,
.color = vec4(1, 1, 1, .4f),
.silent = true,
.beat = beat,
.step = 1,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
/* ---- B melody ---- */
for (size_t i = 52, cnt = 0; i < 84; i+=4, ++cnt) {
if (trigger_on_(i)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = cnt%2? lefttop: righttop,
.size = vec2(.1f, .3f),
.velocity = vec2(0, -1/(beat*4)*1000),
.color = vec4(1, 1, 1, .8f),
.duration = beat*4,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
/* ---- C melody ---- */
for (size_t i = 84, cnt = 0; i < 156; i+=8, ++cnt) {
if (trigger_on_(i)) {
for (int32_t x = -1-cnt%2; x <= 2; x+=2) {
locommon_position_t pos = top;
pos.fract.x += .18f*x;
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = pos,
.size = vec2(.05f, .1f),
.velocity = vec2(0, -1/(beat*4)*1000),
.color = vec4(1, 1, 1, .8f),
.duration = beat*4,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
}
# undef trigger_on_
}

68
core/locharacter/misc.c Normal file
View File

@@ -0,0 +1,68 @@
#include "./misc.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
const char* locharacter_type_stringify(locharacter_type_t type) {
# define each_(NAME, name) do { \
if (type == LOCHARACTER_TYPE_##NAME) return #name; \
} while(0)
LOCHARACTER_TYPE_EACH_(each_);
assert(false);
return NULL;
# undef each_
}
bool locharacter_type_unstringify(
locharacter_type_t* type, const char* v, size_t len) {
assert(type != NULL);
assert(v != NULL || len == 0);
# define each_(NAME, name) do { \
if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \
*type = LOCHARACTER_TYPE_##NAME; \
return true; \
} \
} while(0)
LOCHARACTER_TYPE_EACH_(each_);
return false;
# undef each_
}
const char* locharacter_state_stringify(locharacter_state_t state) {
# define each_(NAME, name) do { \
if (state == LOCHARACTER_STATE_##NAME) return #name; \
} while(0)
LOCHARACTER_STATE_EACH_(each_);
assert(false);
return NULL;
# undef each_
}
bool locharacter_state_unstringify(
locharacter_state_t* state, const char* v, size_t len) {
assert(state != NULL);
assert(v != NULL || len == 0);
# define each_(NAME, name) do { \
if (strncmp(v, #name, len) == 0 && #name[len] == 0) { \
*state = LOCHARACTER_STATE_##NAME; \
return true; \
} \
} while(0)
LOCHARACTER_STATE_EACH_(each_);
return false;
# undef each_
}

76
core/locharacter/misc.h Normal file
View File

@@ -0,0 +1,76 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
/* dont forget to update EACH macro */
typedef enum {
LOCHARACTER_TYPE_ENCEPHALON,
LOCHARACTER_TYPE_CAVIA,
LOCHARACTER_TYPE_SCIENTIST,
LOCHARACTER_TYPE_WARDER,
LOCHARACTER_TYPE_THEISTS_CHILD,
LOCHARACTER_TYPE_BIG_WARDER,
LOCHARACTER_TYPE_GREEDY_SCIENTIST,
} locharacter_type_t;
#define LOCHARACTER_TYPE_EACH_(PROC) do { \
PROC(ENCEPHALON, encephalon); \
PROC(CAVIA, cavia); \
PROC(SCIENTIST, scientist); \
PROC(WARDER, warder); \
PROC(THEISTS_CHILD, theists_child); \
PROC(BIG_WARDER, big_warder); \
PROC(GREEDY_SCIENTIST, greedy_scientist); \
} while (0)
/* dont forget to update EACH macro */
typedef enum {
LOCHARACTER_STATE_WAIT,
LOCHARACTER_STATE_STANDUP,
LOCHARACTER_STATE_WALK,
LOCHARACTER_STATE_SHOOT,
LOCHARACTER_STATE_RUSH,
LOCHARACTER_STATE_THRUST,
LOCHARACTER_STATE_COMBO,
LOCHARACTER_STATE_COOLDOWN,
LOCHARACTER_STATE_STUNNED,
LOCHARACTER_STATE_DEAD,
} locharacter_state_t;
#define LOCHARACTER_STATE_EACH_(PROC) do { \
PROC(WAIT, wait); \
PROC(STANDUP, standup); \
PROC(WALK, walk); \
PROC(SHOOT, shoot); \
PROC(RUSH, rush); \
PROC(THRUST, thrust); \
PROC(COMBO, combo); \
PROC(COOLDOWN, cooldown); \
PROC(STUNNED, stunned); \
PROC(DEAD, dead); \
} while (0)
const char*
locharacter_type_stringify(
locharacter_type_t type
);
bool
locharacter_type_unstringify(
locharacter_type_t* type,
const char* v,
size_t len
);
const char*
locharacter_state_stringify(
locharacter_state_t state
);
bool
locharacter_state_unstringify(
locharacter_state_t* state,
const char* v,
size_t len
);

122
core/locharacter/pool.c Normal file
View File

@@ -0,0 +1,122 @@
#include "./pool.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <msgpack.h>
#include "util/math/vector.h"
#include "util/memory/memory.h"
#include "core/lobullet/pool.h"
#include "core/locommon/counter.h"
#include "core/locommon/ticker.h"
#include "core/loentity/store.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/character.h"
#include "./base.h"
struct locharacter_pool_t {
loresource_set_t* res;
loshader_character_drawer_t* drawer;
locommon_counter_t* idgen;
const locommon_ticker_t* ticker;
lobullet_pool_t* bullets;
loentity_store_t* entities;
loplayer_t* player;
size_t length;
locharacter_base_t items[1];
};
static size_t locharacter_pool_find_unused_item_index_(
const locharacter_pool_t* pool) {
assert(pool != NULL);
for (size_t i = 0; i < pool->length; ++i) {
if (!pool->items[i].used) return i;
}
fprintf(stderr, "character pool overflow\n");
abort();
}
locharacter_pool_t* locharacter_pool_new(
loresource_set_t* res,
loshader_character_drawer_t* drawer,
locommon_counter_t* idgen,
const locommon_ticker_t* ticker,
lobullet_pool_t* bullets,
loentity_store_t* entities,
loplayer_t* player,
size_t length) {
assert(res != NULL);
assert(drawer != NULL);
assert(idgen != NULL);
assert(ticker != NULL);
assert(bullets != NULL);
assert(entities != NULL);
assert(player != NULL);
assert(length > 0);
locharacter_pool_t* pool = memory_new(
sizeof(*pool) + (length-1)*sizeof(pool->items[0]));
*pool = (typeof(*pool)) {
.res = res,
.drawer = drawer,
.idgen = idgen,
.ticker = ticker,
.bullets = bullets,
.entities = entities,
.player = player,
.length = length,
};
for (size_t i = 0; i < pool->length; ++i) {
locharacter_base_initialize(
&pool->items[i],
res,
drawer,
ticker,
bullets,
entities,
player);
}
return pool;
}
void locharacter_pool_delete(locharacter_pool_t* pool) {
assert(pool != NULL);
for (size_t i = 0; i < pool->length; ++i) {
locharacter_base_deinitialize(&pool->items[i]);
}
memory_delete(pool);
}
locharacter_base_t* locharacter_pool_create(locharacter_pool_t* pool) {
assert(pool != NULL);
const size_t i = locharacter_pool_find_unused_item_index_(pool);
locharacter_base_reinitialize(
&pool->items[i], locommon_counter_count(pool->idgen));
pool->items[i].used = true;
return &pool->items[i];
}
locharacter_base_t* locharacter_pool_unpack_item(
locharacter_pool_t* pool, const msgpack_object* obj) {
assert(pool != NULL);
const size_t i = locharacter_pool_find_unused_item_index_(pool);
if (!locharacter_base_unpack(&pool->items[i], obj)) return NULL;
pool->items[i].used = true;
return &pool->items[i];
}

48
core/locharacter/pool.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <stddef.h>
#include <msgpack.h>
#include "util/math/vector.h"
#include "core/lobullet/pool.h"
#include "core/locommon/counter.h"
#include "core/locommon/ticker.h"
#include "core/loentity/store.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/character.h"
#include "./base.h"
struct locharacter_pool_t;
typedef struct locharacter_pool_t locharacter_pool_t;
locharacter_pool_t* /* OWNERSHIP */
locharacter_pool_new(
loresource_set_t* res,
loshader_character_drawer_t* drawer,
locommon_counter_t* idgen,
const locommon_ticker_t* ticker,
lobullet_pool_t* bullets,
loentity_store_t* entities,
loplayer_t* player,
size_t length
);
void
locharacter_pool_delete(
locharacter_pool_t* pool /* OWNERSHIP */
);
locharacter_base_t*
locharacter_pool_create(
locharacter_pool_t* pool
);
locharacter_base_t*
locharacter_pool_unpack_item(
locharacter_pool_t* pool,
const msgpack_object* obj
);

View File

@@ -0,0 +1,305 @@
#include "./scientist.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "core/lobullet/base.h"
#include "core/lobullet/bomb.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/entity.h"
#include "core/loentity/store.h"
#include "core/loplayer/event.h"
#include "core/loplayer/player.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./misc.h"
static const vec2_t locharacter_scientist_size_ = vec2(.02f, .05f);
static const loeffect_recipient_status_t locharacter_scientist_base_status_ = {
.attack = .2f,
.defence = .1f,
};
static void locharacter_scientist_trigger_bomb_(locharacter_base_t* c) {
assert(c != NULL);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = c->player->entity.super.super.pos,
.size = vec2(.15f, .15f),
.angle = 0,
.color = vec4(1, 1, 1, .6f),
.beat = 500,
.step = 2,
.knockback = .1f,
.effect = loeffect_immediate_damage(c->recipient.status.attack),
}));
loentity_store_add(c->entities, &b->super.super);
loresource_sound_play(c->res->sound, "enemy_trigger");
}
static void
locharacter_scientist_start_wait_state_(
locharacter_base_t* base
);
static void
locharacter_scientist_start_shoot_state_(
locharacter_base_t* base
);
static void
locharacter_scientist_start_combo_state_(
locharacter_base_t* base
);
static void
locharacter_scientist_start_dead_state_(
locharacter_base_t* base
);
static void locharacter_scientist_update_wait_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t duration = 1000;
static const uint64_t period = 1000;
const uint64_t elapsed = base->ticker->time - base->since;
/* ---- motion ---- */
float t = elapsed%period*1.f/period;
t = t*2 - 1;
t = MATH_ABS(t);
t = t*t*(3-2*t);
loshader_character_drawer_instance_t* instance = &base->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND2;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t;
/* ---- state transition ---- */
if (elapsed >= duration) {
if (base->recipient.madness <= 0) {
locharacter_scientist_start_dead_state_(base);
return;
}
if (loplayer_event_get_param(base->player->event) == NULL) {
vec2_t disp;
vec2_sub(&disp, &base->cache.player_pos, &base->pos);
disp.x *= base->cache.ground->size.x;
const float pdist = vec2_pow_length(&disp);
if (MATH_ABS(disp.y) < locharacter_scientist_size_.y &&
pdist < .2f*.2f) {
static const float r = locharacter_scientist_size_.x*3;
if (pdist < r*r) {
locharacter_scientist_start_combo_state_(base);
} else if (disp.x*base->direction > 0) {
locharacter_scientist_start_shoot_state_(base);
}
}
return;
}
}
}
static void locharacter_scientist_start_wait_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_WAIT;
}
static void locharacter_scientist_update_shoot_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t duration = 500;
const uint64_t elapsed = base->ticker->time - base->since;
/* ---- motion ---- */
float t = elapsed*1.f / duration;
if (t > 1) t = 1;
t = t*t;
loshader_character_drawer_instance_t* instance = &base->cache.instance;
if (t < .5f) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->motion_time = t*2;
} else {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = (t-.5f)*2;
}
/* ---- state transition ---- */
if (elapsed >= duration) {
locharacter_scientist_trigger_bomb_(base);
locharacter_scientist_start_wait_state_(base);
return;
}
}
static void locharacter_scientist_start_shoot_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_SHOOT;
}
static void locharacter_scientist_update_combo_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t duration = 1000;
const uint64_t elapsed = base->ticker->time - base->since;
base->cache.gravity = false;
/* ---- motion ---- */
float t = elapsed*1.f/duration;
if (t > 1) t = 1;
t = t*t;
loshader_character_drawer_instance_t* instance = &base->cache.instance;
if (t < .5f) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->motion_time = t*2;
} else {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = (t-.5f)*2;
}
/* ---- state transition ---- */
if (elapsed >= duration) {
locharacter_scientist_start_wait_state_(base);
return;
}
}
static void locharacter_scientist_start_combo_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_COMBO;
const float diff = base->cache.player_pos.x - base->pos.x;
base->direction = MATH_SIGN(diff);
const loplayer_combat_attack_t attack = {
.attacker = base->super.super.id,
.start = base->ticker->time + 600,
.duration = 400,
.knockback = vec2(base->direction*.1f, 0),
.effect = loeffect_immediate_damage(base->recipient.status.attack),
};
loplayer_attack(base->player, &attack);
}
static void locharacter_scientist_update_dead_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t anime_duration = 500;
static const uint64_t duration = 30000;
const uint64_t elapsed = base->ticker->time - base->since;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &base->cache.instance;
if (elapsed > duration - anime_duration) { /* wake up */
float t = 1-(duration - elapsed)*1.f/anime_duration;
if (t < 0) t = 0;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = 1-powf(1-t, 2);
} else { /* down */
float t = elapsed*1.f/anime_duration;
if (t > 1) t = 1;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->motion_time = powf(t, 2.);
}
/* ---- state transition ---- */
if (elapsed >= duration) {
loeffect_recipient_reset(&base->recipient);
locharacter_scientist_start_wait_state_(base);
return;
}
}
static void locharacter_scientist_start_dead_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_DEAD;
loplayer_gain_faith(base->player, .3f);
}
bool locharacter_scientist_update(locharacter_base_t* base) {
assert(base != NULL);
static const vec2_t size = locharacter_scientist_size_;
static const vec4_t color = vec4(.1f, .1f, .1f, 1);
static const float height = size.y*1.4f;
static const float drawsz = MATH_MAX(size.x, size.y);
loeffect_recipient_update(&base->recipient, &locharacter_scientist_base_status_);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_SCIENTIST,
.marker_offset = vec2(0, height - drawsz),
.pos = vec2(0, drawsz - height),
.size = vec2(drawsz, drawsz),
.color = color,
};
base->cache.gravity = true;
switch (base->state) {
case LOCHARACTER_STATE_WAIT:
locharacter_scientist_update_wait_state_(base);
break;
case LOCHARACTER_STATE_SHOOT:
locharacter_scientist_update_shoot_state_(base);
break;
case LOCHARACTER_STATE_COMBO:
locharacter_scientist_update_combo_state_(base);
break;
case LOCHARACTER_STATE_DEAD:
locharacter_scientist_update_dead_state_(base);
break;
default:
locharacter_scientist_start_wait_state_(base);
}
base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD;
base->cache.height = height;
base->cache.instance.size.x *= base->direction;
base->cache.instance.marker = !!base->cache.bullet_hittest;
return true;
}
void locharacter_scientist_build(
locharacter_base_t* base, const locharacter_scientist_param_t* param) {
assert(base != NULL);
assert(param != NULL);
base->type = LOCHARACTER_TYPE_SCIENTIST;
base->ground = param->ground;
base->pos = vec2(param->pos, 0);
base->direction = param->direction == 1? 1: -1;
locharacter_scientist_start_wait_state_(base);
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stdbool.h>
#include <msgpack.h>
#include "core/loentity/entity.h"
#include "./base.h"
typedef struct {
loentity_id_t ground;
float pos;
float direction;
} locharacter_scientist_param_t;
bool
locharacter_scientist_update(
locharacter_base_t* base
);
void
locharacter_scientist_build(
locharacter_base_t* base,
const locharacter_scientist_param_t* param
);
#define locharacter_scientist_tear_down(base)
#define locharacter_scientist_pack_data(base, packer) \
msgpack_pack_nil(packer)
#define locharacter_scientist_unpack_data(base, obj) \
(obj != NULL)

View File

@@ -0,0 +1,763 @@
#include "./theists_child.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <msgpack.h>
#include "util/chaos/xorshift.h"
#include "util/math/algorithm.h"
#include "util/math/constant.h"
#include "util/math/rational.h"
#include "util/math/vector.h"
#include "util/mpkutil/get.h"
#include "util/mpkutil/pack.h"
#include "core/lobullet/base.h"
#include "core/lobullet/bomb.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/msgpack.h"
#include "core/locommon/ticker.h"
#include "core/loeffect/recipient.h"
#include "core/loeffect/stance.h"
#include "core/loentity/bullet.h"
#include "core/loentity/store.h"
#include "core/loplayer/event.h"
#include "core/loplayer/player.h"
#include "core/loresource/music.h"
#include "core/loresource/set.h"
#include "core/loresource/text.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./misc.h"
#include "./util.h"
typedef struct {
locharacter_event_holder_t event;
uint64_t phase;
vec2_t from;
vec2_t to;
} locharacter_theists_child_param_t;
#define LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(PROC) do { \
PROC("event-start-time", event.start_time); \
PROC("phase", phase); \
PROC("from", from); \
PROC("to", to); \
} while (0)
#define LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_COUNT 4
static const vec2_t locharacter_theists_child_size_ = vec2(.02f, .06f);
static const loeffect_recipient_status_t
locharacter_theists_child_base_status_ = {
.attack = .1f,
.defence = .9f,
.speed = .1f,
.jump = .2f,
};
#define LOCHARACTER_THEISTS_CHILD_BEAT (60000/140.f) /* 140 BPM */
#define LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION \
((uint64_t) LOCHARACTER_THEISTS_CHILD_BEAT*236)
#define LOCHARACTER_THEISTS_CHILD_MELODY_B_START_BEAT 128
#define LOCHARACTER_THEISTS_CHILD_MELODY_B_END_BEAT 192
#include "./theists_child.private.h"
static void
locharacter_theists_child_start_wait_state_(
locharacter_base_t* c
);
static void
locharacter_theists_child_start_standup_state_(
locharacter_base_t* c
);
static void
locharacter_theists_child_start_rush_state_(
locharacter_base_t* c
);
static void
locharacter_theists_child_start_combo_state_(
locharacter_base_t* c
);
static void
locharacter_theists_child_start_cooldown_state_(
locharacter_base_t* c
);
static void
locharacter_theists_child_start_stunned_state_(
locharacter_base_t* c
);
static void
locharacter_theists_child_start_dead_state_(
locharacter_base_t* c
);
static void locharacter_theists_child_finalize_event_(locharacter_base_t* c) {
assert(c != NULL);
/* This function must start next state. */
locharacter_theists_child_param_t* p = (typeof(p)) c->data;
assert(p != NULL);
locharacter_event_holder_release_control(&p->event);
if (c->recipient.madness > 0) {
loentity_character_apply_effect(
&c->player->entity.super, &loeffect_curse_trigger());
locharacter_theists_child_start_wait_state_(c);
} else {
loplayer_gain_stance(c->player, LOEFFECT_STANCE_ID_REVOLUTIONER);
locharacter_theists_child_start_dead_state_(c);
}
}
static bool locharacter_theists_child_reset_if_player_left_(
locharacter_base_t* c) {
assert(c != NULL);
locharacter_theists_child_param_t* p = (typeof(p)) c->data;
if (MATH_ABS(c->cache.player_pos.x) < 1 &&
0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) {
return false;
}
locharacter_event_holder_release_control(&p->event);
locharacter_theists_child_start_wait_state_(c);
return true;
}
static void locharacter_theists_child_fire_bullets_(locharacter_base_t* c) {
assert(c != NULL);
static const float len = .3f;
static const float accel = .6f;
static const uint64_t dur = 1000;
for (size_t i = 0; i < 30; ++i) {
const float t = MATH_PI/15*i;
const vec2_t v = vec2(cos(t), sin(t));
locommon_position_t pos = c->super.super.pos;
pos.fract.x += v.x*len;
pos.fract.y += v.y*len;
locommon_position_reduce(&pos);
lobullet_base_t* bullet = lobullet_pool_create(c->bullets);
lobullet_linear_light_build(bullet, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = pos,
.size = vec2(.015f, .015f),
.acceleration = vec2(-v.x*accel, -v.y*accel),
.color = vec4(1, 1, 1, .8f),
.duration = dur,
.knockback = .1f,
.effect = loeffect_immediate_damage(c->recipient.status.attack),
}));
loentity_store_add(c->entities, &bullet->super.super);
}
}
static void locharacter_theists_child_update_wait_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float standup_range = .5f;
static const int32_t sit_duration = 4000;
c->cache.gravity = true;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.f/sit_duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->motion_time = t;
/* ---- state transition ---- */
if (c->since+sit_duration <= c->cache.time) {
if (MATH_ABS(c->cache.player_pos.x) < 1 &&
0 < c->cache.player_pos.y && c->cache.player_pos.y < 1) {
vec2_t diff;
vec2_sub(&diff, &c->cache.player_pos, &c->pos);
if (vec2_pow_length(&diff) < standup_range*standup_range) {
locharacter_theists_child_start_standup_state_(c);
return;
}
}
}
}
static void locharacter_theists_child_start_wait_state_(locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_WAIT;
}
static void locharacter_theists_child_update_standup_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT;
static const uint64_t line_duration = beat*10;
locharacter_theists_child_param_t* p = (typeof(p)) c->data;
const bool event = locharacter_event_holder_has_control(&p->event);
const uint64_t standup_duration = event? beat*64: 1000;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.0f/standup_duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
if (t < .5f) {
t *= 2;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->motion_time = t*t*(3-2*t);
} else {
t = (t-.5f)*2;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t*t*(3-2*t);
}
/* ---- event ---- */
if (event) {
p->event.param->cinescope = true;
p->event.param->hide_hud = true;
if (c->since+(p->phase+2)*line_duration < c->cache.time) {
static const char* text[] = {
"boss_theists_child_line0",
"boss_theists_child_line1",
"boss_theists_child_line2",
};
if (p->phase < sizeof(text)/sizeof(text[0])) {
const char* v = loresource_text_get(
c->res->lang, text[(size_t) p->phase]);
loplayer_event_param_set_line(p->event.param, v, strlen(v));
} else {
loplayer_event_param_set_line(p->event.param, "", 0);
}
++p->phase;
}
}
/* ---- state transition ---- */
if (locharacter_theists_child_reset_if_player_left_(c)) return;
if (c->since+standup_duration < c->cache.time) {
if (event) {
p->event.param->hide_hud = false;
p->event.param->cinescope = false;
loplayer_event_param_set_line(p->event.param, "", 0);
}
locharacter_theists_child_start_rush_state_(c);
return;
}
}
static void locharacter_theists_child_start_standup_state_(
locharacter_base_t* c) {
assert(c != NULL);
locharacter_theists_child_param_t* p = (typeof(p)) c->data;
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_STANDUP;
p->phase = 0;
loeffect_recipient_reset(&c->recipient);
if (!loeffect_stance_set_has(
&c->player->status.stances, LOEFFECT_STANCE_ID_REVOLUTIONER)) {
locharacter_event_holder_take_control(&p->event);
}
}
static void locharacter_theists_child_update_rush_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT;
static const uint64_t premotion_duration = beat*2;
static const uint64_t whole_duration = beat*4;
const locharacter_theists_child_param_t* p = (typeof(p)) c->data;
const uint64_t elapsed = c->cache.time - c->since;
/* ---- motion ---- */
float t = elapsed*1.f/premotion_duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
if (t < .1f) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK;
instance->motion_time = 1-powf(1-t*10, 2);
} else if (t < .5f) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_WALK;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->motion_time = 1-powf(1-(t-.1f)/4*10, 2);
} else {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->motion_time = powf((t-.5f)*2, 4);
}
/* ---- position ---- */
vec2_sub(&c->pos, &p->to, &p->from);
c->direction = MATH_SIGN(c->pos.x);
vec2_muleq(&c->pos, powf(t, 2));
c->pos.y += (1-MATH_ABS(t*2-1))*c->recipient.status.jump*.1f;
vec2_addeq(&c->pos, &p->from);
/* ---- state transition ---- */
if (locharacter_theists_child_reset_if_player_left_(c)) return;
if (c->since+whole_duration < c->cache.time) {
locharacter_theists_child_start_cooldown_state_(c);
return;
}
}
static void locharacter_theists_child_start_rush_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT;
static const uint64_t parry = 300;
locharacter_theists_child_param_t* p = (typeof(p)) c->data;
if (c->last_knockback_time + parry > c->cache.time) {
locharacter_theists_child_start_stunned_state_(c);
return;
}
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_RUSH;
const vec2_t* player = &c->cache.player_pos;
const float diffx = player->x - c->pos.x;
p->from = c->pos;
p->to = vec2(
player->x - MATH_SIGN(diffx)*locharacter_theists_child_size_.x*2,
player->y - .02f);
const loplayer_combat_attack_t attack = {
.attacker = c->super.super.id,
.start = c->ticker->time + (uint64_t) beat,
.duration = beat*3,
.knockback = vec2(MATH_SIGN(player->x)*.2f, 0),
.effect = loeffect_immediate_damage(c->recipient.status.attack*2),
};
loplayer_attack(c->player, &attack);
}
static void locharacter_theists_child_update_combo_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT;
static const uint64_t premotion_duration = beat;
static const uint64_t attack_duration = beat;
static const uint64_t whole_duration = beat*4;
const locharacter_theists_child_param_t* p = (typeof(p)) c->data;
const uint64_t elapsed = c->cache.time - c->since;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &c->cache.instance;
if (elapsed < premotion_duration) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->motion_time = elapsed*1.f / premotion_duration;
} else {
const uint64_t attack_elapsed = elapsed - premotion_duration;
float t = 1;
if (attack_elapsed < attack_duration*p->phase) {
t = attack_elapsed%attack_duration*1.f / attack_duration;
}
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->motion_time = 1-powf(1-t, 4);
}
/* ---- position ---- */
if (elapsed < premotion_duration) {
const float t = elapsed*1.f/premotion_duration;
vec2_sub(&c->pos, &p->to, &p->from);
vec2_muleq(&c->pos, t*t);
vec2_addeq(&c->pos, &p->from);
}
/* ---- state transition ---- */
if (locharacter_theists_child_reset_if_player_left_(c)) return;
if (elapsed >= whole_duration) {
locharacter_theists_child_start_cooldown_state_(c);
return;
}
}
static void locharacter_theists_child_start_combo_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT;
static const uint64_t parry = 200;
locharacter_theists_child_param_t* p = (typeof(p)) c->data;
if (c->last_knockback_time + parry > c->cache.time) {
locharacter_theists_child_start_stunned_state_(c);
return;
}
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_COMBO;
const float diffx = c->cache.player_pos.x - c->pos.x;
c->direction = MATH_SIGN(diffx);
p->phase = 2 + chaos_xorshift(c->since)%2;
p->from = c->pos;
p->to = c->cache.player_pos;
p->to.x -= c->direction*locharacter_theists_child_size_.x*2;
p->to.y -= .02f;
const loplayer_combat_attack_t attack1 = {
.attacker = c->super.super.id,
.start = c->ticker->time + (uint64_t) beat,
.duration = beat/2,
.knockback = vec2(c->direction*.1f, 0),
.effect = loeffect_immediate_damage(c->recipient.status.attack*.8f),
};
loplayer_attack(c->player, &attack1);
const loplayer_combat_attack_t attack2 = {
.attacker = c->super.super.id,
.start = c->ticker->time + (uint64_t) (beat*2),
.duration = p->phase == 2? beat*1.5: beat/2,
.knockback = vec2(c->direction*.1f, 0),
.effect = loeffect_immediate_damage(c->recipient.status.attack*1.1f),
};
loplayer_attack(c->player, &attack2);
if (p->phase >= 3) {
const loplayer_combat_attack_t attack3 = {
.attacker = c->super.super.id,
.start = c->ticker->time + (uint64_t) (beat*3),
.duration = beat/2,
.knockback = vec2(c->direction*.1f, 0),
.effect = loeffect_immediate_damage(c->recipient.status.attack*1.3f),
};
loplayer_attack(c->player, &attack3);
}
}
static void locharacter_theists_child_update_cooldown_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT;
static const uint64_t duration = beat*4;
const locharacter_theists_child_param_t* p = (typeof(p)) c->data;
c->cache.bullet_hittest = true;
c->cache.gravity = true;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.0f/duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t;
/* ---- state transition ---- */
if (locharacter_theists_child_reset_if_player_left_(c)) return;
if (c->since+duration < c->cache.time) {
if (locharacter_event_holder_has_control(&p->event)) {
static const uint64_t dur = LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION;
if (p->event.start_time+dur < c->cache.time) {
locharacter_theists_child_finalize_event_(c);
return;
}
} else {
if (c->recipient.madness <= 0) {
locharacter_theists_child_start_dead_state_(c);
return;
}
}
vec2_t diff;
vec2_sub(&diff, &c->cache.player_pos, &c->pos);
if (vec2_pow_length(&diff) < .5f*.5f) {
locharacter_theists_child_start_combo_state_(c);
return;
}
locharacter_theists_child_start_rush_state_(c);
return;
}
}
static void locharacter_theists_child_start_cooldown_state_(
locharacter_base_t* c) {
assert(c != NULL);
const locharacter_theists_child_param_t* p = (typeof(p)) c->data;
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_COOLDOWN;
bool skip_firing = false;
if (locharacter_event_holder_has_control(&p->event)) {
const float beat =
(c->cache.time - p->event.start_time)/LOCHARACTER_THEISTS_CHILD_BEAT;
skip_firing =
LOCHARACTER_THEISTS_CHILD_MELODY_B_START_BEAT <= beat &&
beat < LOCHARACTER_THEISTS_CHILD_MELODY_B_END_BEAT;
}
if (!skip_firing) locharacter_theists_child_fire_bullets_(c);
}
static void locharacter_theists_child_update_stunned_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT;
static const uint64_t duration = beat*4;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.f/duration;
t *= 6;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
if (t < 1) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->motion_time = 1-powf(1-t, 6);
} else {
t = (t-1)/5;
if (t > 1) t = 1;
t = t*t*(3-2*t);
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_SIT;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->motion_time = t;
}
/* ---- state transition ---- */
if (c->since+duration < c->cache.time) {
locharacter_theists_child_start_cooldown_state_(c);
return;
}
}
static void locharacter_theists_child_start_stunned_state_(
locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_STUNNED;
loeffect_recipient_apply_effect(
&c->recipient, &loeffect_immediate_damage(1.f));
}
static void locharacter_theists_child_update_dead_state_(
locharacter_base_t* c) {
assert(c != NULL);
static const uint64_t anime_duration = 4000;
static const uint64_t duration = 30000;
c->cache.gravity = true;
/* ---- motion ---- */
float t = (c->cache.time - c->since)*1.f/anime_duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &c->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->motion_time = t*t;
instance->color.w = 1-t;
/* ---- state transition ---- */
if (c->since+duration < c->cache.time) {
c->pos = vec2(0, 0);
locharacter_theists_child_start_wait_state_(c);
return;
}
}
static void locharacter_theists_child_start_dead_state_(
locharacter_base_t* c) {
assert(c != NULL);
c->since = c->cache.time;
c->state = LOCHARACTER_STATE_DEAD;
loplayer_gain_faith(c->player, .5f);
}
bool locharacter_theists_child_update(locharacter_base_t* base) {
assert(base != NULL);
static const vec2_t size = locharacter_theists_child_size_;
static const vec4_t color = vec4(.05f, 0, 0, 1);
static const float height = size.y * 1.4f;
static const float drawsz = MATH_MAX(size.x, size.y);
locharacter_theists_child_param_t* p = (typeof(p)) base->data;
loeffect_recipient_update(
&base->recipient, &locharacter_theists_child_base_status_);
if (!locharacter_event_holder_update(&p->event)) {
locharacter_theists_child_start_wait_state_(base);
}
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_CAVIA,
.marker_offset = vec2(0, height - drawsz),
.pos = vec2(0, drawsz - height),
.size = vec2(drawsz, drawsz),
.color = color,
};
switch (base->state) {
case LOCHARACTER_STATE_WAIT:
locharacter_theists_child_update_wait_state_(base);
break;
case LOCHARACTER_STATE_STANDUP:
locharacter_theists_child_update_standup_state_(base);
break;
case LOCHARACTER_STATE_RUSH:
locharacter_theists_child_update_rush_state_(base);
break;
case LOCHARACTER_STATE_COMBO:
locharacter_theists_child_update_combo_state_(base);
break;
case LOCHARACTER_STATE_COOLDOWN:
locharacter_theists_child_update_cooldown_state_(base);
break;
case LOCHARACTER_STATE_STUNNED:
locharacter_theists_child_update_stunned_state_(base);
break;
case LOCHARACTER_STATE_DEAD:
locharacter_theists_child_update_dead_state_(base);
break;
default:
locharacter_theists_child_start_wait_state_(base);
}
locharacter_theists_child_update_passive_action_(base);
base->cache.height = height;
base->cache.instance.marker = !!base->cache.bullet_hittest;
base->cache.instance.size.x *= base->direction;
return true;
}
void locharacter_theists_child_build(locharacter_base_t* base, loentity_id_t ground) {
assert(base != NULL);
base->type = LOCHARACTER_TYPE_THEISTS_CHILD;
base->ground = ground;
base->pos = vec2(0, 0);
base->direction = 1;
base->state = LOCHARACTER_STATE_WAIT;
base->since = base->cache.time;
locharacter_theists_child_param_t* p = (typeof(p)) base->data;
*p = (typeof(*p)) {0};
locharacter_event_holder_initialize(
&p->event,
&base->res->music.boss_theists_child,
base,
LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION,
0);
}
void locharacter_theists_child_tear_down(locharacter_base_t* base) {
assert(base != NULL);
locharacter_theists_child_param_t* p = (typeof(p)) base->data;
locharacter_event_holder_deinitialize(&p->event);
}
void locharacter_theists_child_pack_data(
const locharacter_base_t* base, msgpack_packer* packer) {
assert(base != NULL);
assert(packer != NULL);
const locharacter_theists_child_param_t* p = (typeof(p)) base->data;
msgpack_pack_map(packer, LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_COUNT);
# define pack_(name, var) do { \
mpkutil_pack_str(packer, name); \
LOCOMMON_MSGPACK_PACK_ANY(packer, &p->var); \
} while (0)
LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(pack_);
# undef pack_
}
bool locharacter_theists_child_unpack_data(
locharacter_base_t* base, const msgpack_object* obj) {
assert(base != NULL);
locharacter_theists_child_param_t* p = (typeof(p)) base->data;
const msgpack_object_map* root = mpkutil_get_map(obj);
# define item_(v) mpkutil_get_map_item_by_str(root, v)
# define unpack_(name, var) do { \
if (!LOCOMMON_MSGPACK_UNPACK_ANY(item_(name), &p->var)) { \
return false; \
} \
} while (0)
LOCHARACTER_THEISTS_CHILD_PARAM_TO_PACK_EACH_(unpack_);
# undef unpack_
# undef item_
locharacter_event_holder_initialize(
&p->event,
&base->res->music.boss_theists_child,
base,
LOCHARACTER_THEISTS_CHILD_MUSIC_DURATION,
p->event.start_time);
return true;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <stdbool.h>
#include <msgpack.h>
#include "core/loentity/entity.h"
#include "./base.h"
bool
locharacter_theists_child_update(
locharacter_base_t* base
);
void
locharacter_theists_child_build(
locharacter_base_t* base,
loentity_id_t ground
);
void
locharacter_theists_child_tear_down(
locharacter_base_t* base
);
void
locharacter_theists_child_pack_data(
const locharacter_base_t* base,
msgpack_packer* packer
);
bool
locharacter_theists_child_unpack_data(
locharacter_base_t* base,
const msgpack_object* obj
);

View File

@@ -0,0 +1,150 @@
static void locharacter_theists_child_update_passive_action_(
locharacter_base_t* c) {
assert(c != NULL);
static const float beat = LOCHARACTER_THEISTS_CHILD_BEAT;
const locharacter_theists_child_param_t* p = (typeof(p)) c->data;
if (!locharacter_event_holder_has_control(&p->event)) return;
const uint64_t dt = c->cache.time - c->last_update_time;
const uint64_t t = c->cache.time - p->event.start_time;
const float beats = t/beat;
const float last_beats = t > dt? (t-dt)/beat: 0;
# define name_pos_(name, x, y) \
locommon_position_t name = c->cache.ground->super.pos; \
vec2_addeq(&name.fract, &vec2(x, y)); \
locommon_position_reduce(&name);
name_pos_(top, 0, .8f);
name_pos_(lefttop, -.25f, .8f);
name_pos_(righttop, .25f, .8f);
name_pos_(center, 0, .25f);
name_pos_(left, -.3f, .2f);
name_pos_(right, .3f, .2f);
# undef name_pos_
# define trigger_on_(x) (last_beats < (x) && beats >= (x))
/* ---- intro -> A melody ---- */
if (trigger_on_(56)) {
for (size_t i = 0; i < 2; ++i) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_triangle_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = i? left: right,
.size = vec2(.05f, .15f),
.angle = -MATH_PI/2,
.color = vec4(1, 1, 1, .8f),
.silent = true,
.beat = beat,
.step = 8,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
if (trigger_on_(64)) {
for (size_t i = 0; i < 2; ++i) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = i? lefttop: righttop,
.size = vec2(.05f, .15f),
.velocity = vec2(0, -1.4f/(beat/1000*2)),
.acceleration = vec2(0, 1/(beat/1000*2)),
.color = vec4(1, 1, 1, .8f),
.duration = beat*2,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
/* ---- B melody ---- */
for (size_t i = 128, cnt = 0; i < 192; i+=4, ++cnt) {
if (trigger_on_(i)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = cnt%2 == 0? left: right,
.size = vec2(.13f, .13f),
.angle = MATH_PI/4,
.color = vec4(1, 1, 1, .8f),
.silent = true,
.beat = LOCHARACTER_THEISTS_CHILD_BEAT,
.step = 4,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack/2),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
for (size_t i = 128; i < 192; i+=4) {
if (trigger_on_(i)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_linear_triangle_build(b, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = top,
.size = vec2(.05f, .2f),
.velocity = vec2(0, -1.4f/(beat/1000*2)),
.acceleration = vec2(0, 1/(beat/1000*2)),
.color = vec4(1, 1, 1, .8f),
.duration = beat*2,
.knockback = .1f,
.effect = loeffect_immediate_damage(
c->recipient.status.attack),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
/* ---- fill-in ---- */
if (trigger_on_(192)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.pos = center,
.size = vec2(.2f, .2f),
.angle = MATH_PI/4,
.color = vec4(1, 1, .4f, .8f),
.silent = true,
.beat = LOCHARACTER_THEISTS_CHILD_BEAT,
.step = 8,
.knockback = .1f,
.effect = loeffect_amnesia(
c->ticker->time + (uint64_t) (8*beat), beat*4),
}));
loentity_store_add(c->entities, &b->super.super);
}
/* ---- C melody ---- */
for (size_t i = 200, cnt = 0; i < 232; i+=2, ++cnt) {
if (trigger_on_(i)) {
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_bomb_square_build(b, (&(lobullet_bomb_param_t) {
.owner = c->super.super.id,
.size = vec2(.16f, .16f),
.pos = cnt%2 == 0? left: right,
.angle = MATH_PI/4,
.color = vec4(1, 1, 1, .8f),
.silent = true,
.beat = LOCHARACTER_THEISTS_CHILD_BEAT,
.step = 2,
.knockback = .1f,
.effect = loeffect_immediate_damage(c->recipient.status.attack),
}));
loentity_store_add(c->entities, &b->super.super);
}
}
# undef trigger_on_
}

139
core/locharacter/util.c Normal file
View File

@@ -0,0 +1,139 @@
#include "./util.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/rational.h"
#include "util/math/vector.h"
#include "core/locommon/position.h"
#include "core/loresource/music.h"
#include "core/loplayer/event.h"
#include "./base.h"
static void locharacter_event_holder_handle_control_lost_(
locharacter_event_holder_t* holder) {
assert(holder != NULL);
if (holder->music != NULL) {
jukebox_amp_change_volume(&holder->music->amp, 0, &rational(1, 1));
jukebox_decoder_stop_after(holder->music->decoder, &rational(1, 1));
}
holder->param = NULL;
holder->start_time = 0;
}
void locharacter_event_holder_initialize(
locharacter_event_holder_t* holder,
loresource_music_player_t* music,
locharacter_base_t* owner,
uint64_t duration,
uint64_t start_time) {
assert(holder != NULL);
assert(music != NULL);
assert(owner != NULL);
assert(duration > 0);
*holder = (typeof(*holder)) {
.music = music,
.owner = owner,
.duration = duration,
.start_time = start_time,
};
}
void locharacter_event_holder_deinitialize(
locharacter_event_holder_t* holder) {
assert(holder != NULL);
locharacter_event_holder_release_control(holder);
}
bool locharacter_event_holder_take_control(
locharacter_event_holder_t* holder) {
assert(holder != NULL);
assert(holder->owner != NULL);
assert(holder->owner->cache.ground != NULL);
const locharacter_base_t* owner = holder->owner;
const bool recover = holder->start_time > 0;
const uint64_t t = recover? owner->cache.time - holder->start_time: 0;
if (recover && t >= holder->duration) return false;
holder->param = loplayer_event_take_control(
owner->player->event, owner->super.super.id);
if (holder->param == NULL) return false;
loplayer_event_param_t* p = holder->param;
p->area_pos = owner->cache.ground->super.pos;
p->area_pos.fract.y += .4f;
locommon_position_reduce(&p->area_pos);
p->area_size = vec2(.45f, .45f);
p->music = holder->music;
if (!recover) {
loentity_character_apply_effect(
&owner->player->entity.super,
&loeffect_curse(owner->ticker->time, holder->duration));
holder->start_time = owner->cache.time;
}
if (holder->music != NULL) {
jukebox_decoder_play(holder->music->decoder, &rational(t, 1000), false);
jukebox_amp_change_volume(&holder->music->amp, .8f, &rational(1, 1));
}
return true;
}
void locharacter_event_holder_release_control(
locharacter_event_holder_t* holder) {
assert(holder != NULL);
if (holder->param == NULL) return;
loplayer_event_param_release_control(holder->param);
locharacter_event_holder_handle_control_lost_(holder);
}
bool locharacter_event_holder_update(locharacter_event_holder_t* holder) {
assert(holder != NULL);
if (holder->start_time > holder->owner->ticker->time) {
holder->start_time = 0;
}
loplayer_event_param_t* p = holder->param;
if (p == NULL) {
if (holder->start_time > 0) {
return locharacter_event_holder_take_control(holder);
}
return true;
}
if (!p->controlled || p->controlled_by != holder->owner->super.super.id) {
locharacter_event_holder_handle_control_lost_(holder);
return false;
}
if (holder->music != NULL) {
rational_t r;
jukebox_decoder_get_seek_position(holder->music->decoder, &r);
rational_normalize(&r, 1000);
holder->owner->cache.time = r.num + holder->start_time;
}
return true;
}
bool locharacter_event_holder_has_control(
const locharacter_event_holder_t* holder) {
assert(holder != NULL);
return holder->param != NULL;
}

54
core/locharacter/util.h Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "core/loentity/entity.h"
#include "core/loresource/music.h"
#include "core/loplayer/event.h"
#include "./base.h"
typedef struct {
loresource_music_player_t* music;
locharacter_base_t* owner;
uint64_t duration;
loplayer_event_param_t* param;
uint64_t start_time;
} locharacter_event_holder_t;
void
locharacter_event_holder_initialize(
locharacter_event_holder_t* holder,
loresource_music_player_t* music,
locharacter_base_t* owner,
uint64_t duration,
uint64_t start_time
);
void
locharacter_event_holder_deinitialize(
locharacter_event_holder_t* holder
);
bool
locharacter_event_holder_take_control(
locharacter_event_holder_t* holder
);
void
locharacter_event_holder_release_control(
locharacter_event_holder_t* holder
);
bool /* false: event was aborted by others */
locharacter_event_holder_update(
locharacter_event_holder_t* holder
);
bool
locharacter_event_holder_has_control(
const locharacter_event_holder_t* holder
);

299
core/locharacter/warder.c Normal file
View File

@@ -0,0 +1,299 @@
#include "./warder.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "core/lobullet/base.h"
#include "core/lobullet/linear.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/entity.h"
#include "core/loentity/store.h"
#include "core/loplayer/event.h"
#include "core/loplayer/player.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./misc.h"
static const vec2_t locharacter_warder_size_ = vec2(.02f, .05f);
static const loeffect_recipient_status_t locharacter_warder_base_status_ = {
.attack = .1f,
.defence = -.8f,
};
static void locharacter_warder_shoot_(locharacter_base_t* c) {
assert(c != NULL);
lobullet_base_t* b = lobullet_pool_create(c->bullets);
lobullet_linear_light_build(b, (&(lobullet_linear_param_t) {
.owner = c->super.super.id,
.pos = c->super.super.pos,
.size = vec2(.04f, .04f),
.velocity = vec2(c->direction*.5f, 0),
.color = vec4(.6f, .6f, .6f, .8f),
.acceleration = vec2(0, 0),
.duration = 2000,
.knockback = .4f,
.effect = loeffect_immediate_damage(c->recipient.status.attack),
}));
loentity_store_add(c->entities, &b->super.super);
loresource_sound_play(c->res->sound, "enemy_shoot");
}
static void
locharacter_warder_start_wait_state_(
locharacter_base_t* base
);
static void
locharacter_warder_start_shoot_state_(
locharacter_base_t* base
);
static void
locharacter_warder_start_combo_state_(
locharacter_base_t* base
);
static void
locharacter_warder_start_dead_state_(
locharacter_base_t* base
);
static void locharacter_warder_update_wait_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t duration = 1000;
const uint64_t elapsed = base->ticker->time - base->since;
/* ---- motion ---- */
float t = elapsed*1.f / duration;
if (t > 1) t = 1;
loshader_character_drawer_instance_t* instance = &base->cache.instance;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = t*t*(3-2*t);
/* ---- state transition ---- */
if (elapsed >= duration) {
if (base->recipient.madness <= 0) {
locharacter_warder_start_dead_state_(base);
return;
}
if (loplayer_event_get_param(base->player->event) == NULL) {
vec2_t disp;
vec2_sub(&disp, &base->cache.player_pos, &base->pos);
disp.x *= base->cache.ground->size.x;
const float pdist = vec2_pow_length(&disp);
if (MATH_ABS(disp.y) < locharacter_warder_size_.y && pdist < .4f*.4f) {
static const float r = locharacter_warder_size_.x*3;
if (pdist < r*r) {
locharacter_warder_start_combo_state_(base);
} else if (disp.x*base->direction > 0) {
locharacter_warder_start_shoot_state_(base);
}
}
return;
}
}
}
static void locharacter_warder_start_wait_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_WAIT;
}
static void locharacter_warder_update_shoot_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t duration = 500;
const uint64_t elapsed = base->ticker->time - base->since;
/* ---- motion ---- */
float t = elapsed*1.f / duration;
if (t > 1) t = 1;
t = t*t;
loshader_character_drawer_instance_t* instance = &base->cache.instance;
if (t < .5f) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->motion_time = t*2;
} else {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->motion_time = (t-.5f)*2;
}
/* ---- state transition ---- */
if (elapsed >= duration) {
locharacter_warder_shoot_(base);
locharacter_warder_start_wait_state_(base);
return;
}
}
static void locharacter_warder_start_shoot_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_SHOOT;
}
static void locharacter_warder_update_combo_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t duration = 1000;
const uint64_t elapsed = base->ticker->time - base->since;
/* ---- motion ---- */
float t = elapsed*1.f/duration;
if (t > 1) t = 1;
t = t*t;
loshader_character_drawer_instance_t* instance = &base->cache.instance;
if (t < .5f) {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->motion_time = t*2;
} else {
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->motion_time = (t-.5f)*2;
}
/* ---- state transition ---- */
if (elapsed >= duration) {
locharacter_warder_start_wait_state_(base);
return;
}
}
static void locharacter_warder_start_combo_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_COMBO;
const float diff = base->cache.player_pos.x - base->pos.x;
base->direction = MATH_SIGN(diff);
const loplayer_combat_attack_t attack = {
.attacker = base->super.super.id,
.start = base->ticker->time + 500,
.duration = 500,
.knockback = vec2(base->direction*.1f, 0),
.effect = loeffect_immediate_damage(base->recipient.status.attack),
};
loplayer_attack(base->player, &attack);
}
static void locharacter_warder_update_dead_state_(locharacter_base_t* base) {
assert(base != NULL);
static const uint64_t anime_duration = 500;
static const uint64_t duration = 30000;
const uint64_t elapsed = base->ticker->time - base->since;
/* ---- motion ---- */
loshader_character_drawer_instance_t* instance = &base->cache.instance;
if (elapsed > duration - anime_duration) { /* wake up */
float t = 1-(duration - elapsed)*1.f/anime_duration;
if (t < 0) t = 0;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_STAND1;
instance->motion_time = 1-powf(1-t, 2);
} else { /* down */
float t = elapsed*1.f/anime_duration;
if (t > 1) t = 1;
instance->from_motion_id = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
instance->to_motion_id = LOSHADER_CHARACTER_MOTION_ID_DOWN;
instance->motion_time = t;
}
/* ---- state transition ---- */
if (elapsed >= duration) {
loeffect_recipient_reset(&base->recipient);
locharacter_warder_start_wait_state_(base);
return;
}
}
static void locharacter_warder_start_dead_state_(locharacter_base_t* base) {
assert(base != NULL);
base->since = base->ticker->time;
base->state = LOCHARACTER_STATE_DEAD;
loplayer_gain_faith(base->player, .1f);
}
bool locharacter_warder_update(locharacter_base_t* base) {
assert(base != NULL);
static const vec2_t size = locharacter_warder_size_;
static const vec4_t color = vec4(.1f, .1f, .1f, 1);
static const float height = size.y*1.4f;
static const float drawsz = MATH_MAX(size.x, size.y);
loeffect_recipient_update(&base->recipient, &locharacter_warder_base_status_);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_WARDER,
.marker_offset = vec2(0, height - drawsz),
.pos = vec2(0, drawsz - height),
.size = vec2(drawsz, drawsz),
.color = color,
};
switch (base->state) {
case LOCHARACTER_STATE_WAIT:
locharacter_warder_update_wait_state_(base);
break;
case LOCHARACTER_STATE_SHOOT:
locharacter_warder_update_shoot_state_(base);
break;
case LOCHARACTER_STATE_COMBO:
locharacter_warder_update_combo_state_(base);
break;
case LOCHARACTER_STATE_DEAD:
locharacter_warder_update_dead_state_(base);
break;
default:
locharacter_warder_start_wait_state_(base);
}
base->cache.bullet_hittest = base->state != LOCHARACTER_STATE_DEAD;
base->cache.gravity = true;
base->cache.height = height;
base->cache.instance.size.x *= base->direction;
base->cache.instance.marker = !!base->cache.bullet_hittest;
return true;
}
void locharacter_warder_build(
locharacter_base_t* base, const locharacter_warder_param_t* param) {
assert(base != NULL);
assert(param != NULL);
base->type = LOCHARACTER_TYPE_WARDER;
base->ground = param->ground;
base->pos = vec2(param->pos, 0);
base->direction = -MATH_SIGN(param->pos);
if (base->direction == 0) base->direction = 1;
locharacter_warder_start_wait_state_(base);
}

33
core/locharacter/warder.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <stdbool.h>
#include <msgpack.h>
#include "core/loentity/entity.h"
#include "./base.h"
typedef struct {
loentity_id_t ground;
float pos;
} locharacter_warder_param_t;
bool
locharacter_warder_update(
locharacter_base_t* base
);
void
locharacter_warder_build(
locharacter_base_t* base,
const locharacter_warder_param_t* param
);
#define locharacter_warder_tear_down(base)
#define locharacter_warder_pack_data(base, packer) \
msgpack_pack_nil(packer)
#define locharacter_warder_unpack_data(base, obj) \
(obj != NULL)