[RELEASE] u22-v04

This version is submitted for U22 final presentation. (squashed 158 commits)
This commit is contained in:
2020-10-09 00:00:00 +00:00
parent 84c3a02b9a
commit 80b3b82332
277 changed files with 12154 additions and 13836 deletions

View File

@@ -0,0 +1,37 @@
add_library(lochara
base.c
big_warder.c
cavia.c
combat.c
encephalon.c
player.c
pool.c
state.c
strategy.c
theists_child.c
warder.c
)
target_benum_sources(lochara
state.h
strategy.h
type.h
)
target_crial_sources(lochara
base.crial
)
target_link_libraries(lochara
msgpackc
chaos
math
statman
lobullet
locommon
loeffect
loentity
loplayer
loresource
loshader
)

333
core/lochara/base.c Normal file
View File

@@ -0,0 +1,333 @@
#include "./base.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <msgpack.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/mpkutil/get.h"
#include "util/mpkutil/pack.h"
#include "util/statman/statman.h"
#include "core/lobullet/pool.h"
#include "core/locommon/easing.h"
#include "core/locommon/msgpack.h"
#include "core/locommon/physics.h"
#include "core/locommon/ticker.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/character.h"
#include "core/loentity/ground.h"
#include "core/loentity/store.h"
#include "core/loplayer/player.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./big_warder.h"
#include "./cavia.h"
#include "./encephalon.h"
#include "./player.h"
#include "./state.h"
#include "./theists_child.h"
#include "./type.h"
#include "./warder.h"
/* generated serializer */
#include "core/lochara/crial/base.h"
#define GRAVITY_ACCELERATION_ 2.2f
#define KNOCKBACK_RECOVERY_ACCELERATION_ 4.4f
#define BULLET_INVINCIBLE_DURATION_ 500
static bool
(*const update_function_vtable_[LOCHARA_TYPE_COUNT])(lochara_base_t* base) = {
[LOCHARA_TYPE_PLAYER] = lochara_player_update,
[LOCHARA_TYPE_ENCEPHALON] = lochara_encephalon_update,
[LOCHARA_TYPE_CAVIA] = lochara_cavia_update,
[LOCHARA_TYPE_WARDER] = lochara_warder_update,
[LOCHARA_TYPE_BIG_WARDER] = lochara_big_warder_update,
[LOCHARA_TYPE_THEISTS_CHILD] = lochara_theists_child_update,
};
static loentity_ground_t* lochara_base_find_ground_(
lochara_base_t* base, loentity_id_t id, vec2_t* pos) {
assert(base != NULL);
assert(pos != NULL);
loentity_store_iterator_t itr;
if (!loentity_store_find_item_by_id(base->entities, &itr, id) ||
itr.ground == NULL) {
return NULL;
}
locommon_position_sub(pos, &base->super.super.pos, &itr.ground->super.pos);
pos->x /= itr.ground->size.x;
pos->y -= itr.ground->size.y;
return itr.ground;
}
static void lochara_base_delete_(loentity_t* entity) {
assert(entity != NULL);
lochara_base_t* base = (typeof(base)) entity;
loeffect_recipient_deinitialize(&base->param.recipient);
base->used = false;
}
static void lochara_base_die_(loentity_t* entity) {
assert(entity != NULL);
}
static bool lochara_base_update_(loentity_t* entity) {
assert(entity != NULL);
lochara_base_t* base = (typeof(base)) entity;
base->cache = (typeof(base->cache)) {0};
base->super.velocity = vec2(0, 0);
base->cache.ground = lochara_base_find_ground_(
base, base->param.ground, &base->cache.ground_pos);
assert(update_function_vtable_[base->param.type] != NULL);
return update_function_vtable_[base->param.type](base);
}
static void lochara_base_draw_(
loentity_t* entity, const locommon_position_t* basepos) {
assert(entity != NULL);
assert(locommon_position_valid(basepos));
lochara_base_t* base = (typeof(base)) entity;
vec2_t pos;
locommon_position_sub(&pos, &base->super.super.pos, basepos);
vec2_addeq(&base->cache.instance.pos, &pos);
loshader_character_drawer_add_instance(
&base->shaders->drawer.character, &base->cache.instance);
}
static void lochara_base_pack_(
const loentity_t* entity, msgpack_packer* packer) {
assert(entity != NULL);
assert(packer != NULL);
lochara_base_t* base = (typeof(base)) entity;
msgpack_pack_map(packer, CRIAL_PROPERTY_COUNT_);
CRIAL_SERIALIZER_;
}
static void lochara_base_apply_effect_(
loentity_character_t* chara, const loeffect_t* effect) {
assert(chara != NULL);
assert(effect != NULL);
lochara_base_t* base = (typeof(base)) chara;
const bool player = base->param.type == LOCHARA_TYPE_PLAYER;
loeffect_recipient_apply_effect(&base->param.recipient, effect);
switch (effect->id) {
case LOEFFECT_ID_DAMAGE:
if (player) {
loresource_sound_set_play(&base->res->sound, LORESOURCE_SOUND_ID_DAMAGE);
}
break;
default:
break;
}
}
static void lochara_base_knockback_(
loentity_character_t* chara, const vec2_t* v) {
assert(chara != NULL);
assert(vec2_valid(v));
lochara_base_t* base = (typeof(base)) chara;
base->param.gravity += v->y;
base->param.knockback += v->x;
if (vec2_pow_length(v) > 0) {
base->param.last_knockback = base->ticker->time;
}
}
void lochara_base_initialize(
lochara_base_t* base,
loresource_set_t* res,
loshader_set_t* shaders,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
loplayer_t* player,
lobullet_pool_t* bullet) {
assert(base != NULL);
assert(res != NULL);
assert(shaders != NULL);
assert(ticker != NULL);
assert(entities != NULL);
assert(player != NULL);
assert(bullet != NULL);
*base = (typeof(*base)) {
.res = res,
.shaders = shaders,
.ticker = ticker,
.entities = entities,
.player = player,
.bullet = bullet,
};
}
void lochara_base_reinitialize(lochara_base_t* base, loentity_id_t id) {
assert(base != NULL);
assert(!base->used);
base->super = (typeof(base->super)) {
.super = {
.vtable = {
.delete = lochara_base_delete_,
.die = lochara_base_die_,
.update = lochara_base_update_,
.draw = lochara_base_draw_,
.pack = lochara_base_pack_,
},
.id = id,
.subclass = LOENTITY_SUBCLASS_CHARACTER,
},
.vtable = {
.apply_effect = lochara_base_apply_effect_,
.knockback = lochara_base_knockback_,
},
};
base->param = (typeof(base->param)) {0};
}
void lochara_base_deinitialize(lochara_base_t* base) {
assert(base != NULL);
assert(!base->used);
}
void lochara_base_calculate_physics(
lochara_base_t* base, const vec2_t* size, const vec2_t* offset) {
assert(base != NULL);
assert(vec2_valid(size));
assert(vec2_valid(offset));
const float dt = base->ticker->delta_f;
vec2_t velocity = base->param.movement;
velocity.y += base->param.gravity;
velocity.x += base->param.knockback;
base->param.gravity -= GRAVITY_ACCELERATION_*dt;
locommon_easing_linear_float(
&base->param.knockback, 0, KNOCKBACK_RECOVERY_ACCELERATION_*dt);
vec2_t disp;
vec2_mul(&disp, &velocity, base->ticker->delta_f);
vec2_addeq(&base->super.super.pos.fract, &disp);
vec2_subeq(&base->super.super.pos.fract, offset);
locommon_position_reduce(&base->super.super.pos);
locommon_physics_entity_t e = {
.size = *size,
.pos = base->super.super.pos,
.velocity = velocity,
};
loentity_store_solve_collision_between_ground(
base->entities, &e, base->ticker->delta_f);
base->super.super.pos = e.pos;
vec2_addeq(&base->super.super.pos.fract, offset);
locommon_position_reduce(&base->super.super.pos);
base->param.on_ground = false;
if (e.velocity.y == 0) {
if (velocity.y <= 0) {
base->param.on_ground = true;
}
if (base->param.gravity*velocity.y > 0) {
base->param.gravity = 0;
}
}
if (e.velocity.x == 0 && velocity.x != 0) {
if (base->param.knockback*velocity.x >= 0) {
base->param.knockback = 0;
}
}
base->super.velocity = velocity = e.velocity;
}
void lochara_base_bind_on_ground(lochara_base_t* base, const vec2_t* offset) {
assert(base != NULL);
assert(vec2_valid(offset));
if (base->cache.ground == NULL) return;
vec2_t p;
locommon_position_sub(
&p, &base->super.super.pos, &base->cache.ground->super.pos);
const vec2_t sz = base->cache.ground->size;
p.x = MATH_CLAMP(p.x, -sz.x+offset->x, sz.x-offset->x);
p.y = MATH_CLAMP(p.y, sz.y+offset->y, 1);
base->super.super.pos = base->cache.ground->super.pos;
vec2_addeq(&base->super.super.pos.fract, &p);
locommon_position_reduce(&base->super.super.pos);
}
bool lochara_base_affect_bullets(lochara_base_t* base) {
assert(base != NULL);
const uint64_t t = base->ticker->time;
if (base->param.last_bullet_hit + BULLET_INVINCIBLE_DURATION_ > t) {
return false;
}
const bool hit = loentity_store_affect_bullets_shot_by_others(
base->entities,
&base->super,
&base->super.velocity,
base->ticker->delta_f);
if (hit) {
base->param.last_bullet_hit = base->ticker->time;
}
return true;
}
bool lochara_base_unpack(lochara_base_t* base, const msgpack_object* obj) {
assert(base != NULL);
assert(obj != NULL);
assert(!base->used);
lochara_base_reinitialize(base, 0); /* id will be overwritten */
loeffect_recipient_initialize(&base->param.recipient, base->ticker, NULL);
const msgpack_object_map* root = mpkutil_get_map(obj);
if (root == NULL) goto FAIL;
CRIAL_DESERIALIZER_;
return true;
FAIL:
lochara_base_delete_(&base->super.super);
return false;
}

60
core/lochara/base.crial Normal file
View File

@@ -0,0 +1,60 @@
/* CRIAL
SERIALIZER_BEGIN
mpkutil_pack_str(packer, "$name");
mpkutil_pack_str(packer, $code);
END
DESERIALIZER_BEGIN
const char* v;
size_t vlen;
if (!mpkutil_get_str(
mpkutil_get_map_item_by_str(root, "$name"), &v, &vlen) ||
strncmp(v, $code, vlen) != 0 || $code[vlen] != 0) {
goto FAIL;
}
END
PROPERTY subclass = "character"
SERIALIZER_BEGIN
mpkutil_pack_str(packer, "$name");
LOCOMMON_MSGPACK_PACK_ANY(packer, &base->$code);
END
DESERIALIZER_BEGIN
if (!LOCOMMON_MSGPACK_UNPACK_ANY(
mpkutil_get_map_item_by_str(root, "$name"), &base->$code)) {
goto FAIL;
}
END
PROPERTY id = super.super.id
PROPERTY pos = super.super.pos
PROPERTY velocity = super.velocity
PROPERTY type = param.type
PROPERTY recipient = param.recipient
PROPERTY last_state_changed = param.last_state_changed
PROPERTY last_strategy_changed = param.last_strategy_changed
PROPERTY ground = param.ground
PROPERTY on_ground = param.on_ground
PROPERTY direction = param.direction
PROPERTY movement = param.movement
PROPERTY gravity = param.gravity
PROPERTY knockback = param.knockback
PROPERTY last_knockback = param.last_knockback
PROPERTY last_bullet_hit = param.last_bullet_hit
PROPERTY anchor_pos = param.anchor.pos
PROPERTY anchor_vec = param.anchor.vec
SERIALIZER_BEGIN
const $code v = base->param.$name;
mpkutil_pack_str(packer, "$name");
LOCOMMON_MSGPACK_PACK_ANY(packer, &v);
END
DESERIALIZER_BEGIN
$code v;
if (!LOCOMMON_MSGPACK_UNPACK_ANY(
mpkutil_get_map_item_by_str(root, "$name"), &v)) {
goto FAIL;
}
base->param.$name = v;
END
PROPERTY state = lochara_state_t
PROPERTY strategy = lochara_strategy_t
*/

116
core/lochara/base.h Normal file
View File

@@ -0,0 +1,116 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <msgpack.h>
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/pool.h"
#include "core/locommon/ticker.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/character.h"
#include "core/loentity/entity.h"
#include "core/loentity/ground.h"
#include "core/loentity/store.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/set.h"
#include "./state.h"
#include "./strategy.h"
#include "./type.h"
typedef struct lochara_base_t {
loentity_character_t super;
bool used;
loresource_set_t* res;
loshader_set_t* shaders;
const locommon_ticker_t* ticker;
loentity_store_t* entities;
loplayer_t* player;
lobullet_pool_t* bullet;
struct {
lochara_type_t type;
loeffect_recipient_t recipient;
statman_state_t state; /* actual type is lochara_state_t */
uint64_t last_state_changed;
statman_state_t strategy; /* actual type is lochara_strategy_t */
uint64_t last_strategy_changed;
loentity_id_t ground;
bool on_ground;
vec2_t direction;
vec2_t movement;
float gravity;
float knockback;
uint64_t last_knockback;
uint64_t last_bullet_hit;
/* some character uses these params for some strategy */
struct {
locommon_position_t pos;
vec2_t vec;
} anchor;
} param;
struct {
loentity_ground_t* ground;
vec2_t ground_pos;
loshader_character_drawer_instance_t instance;
} cache;
} lochara_base_t;
void
lochara_base_initialize(
lochara_base_t* base,
loresource_set_t* res,
loshader_set_t* shaders,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
loplayer_t* player,
lobullet_pool_t* bullet
);
void
lochara_base_reinitialize(
lochara_base_t* base,
loentity_id_t id
);
void
lochara_base_deinitialize(
lochara_base_t* base
);
void
lochara_base_calculate_physics(
lochara_base_t* base,
const vec2_t* size,
const vec2_t* offset
);
void
lochara_base_bind_on_ground(
lochara_base_t* base,
const vec2_t* offset
);
bool
lochara_base_affect_bullets(
lochara_base_t* base
);
bool
lochara_base_unpack(
lochara_base_t* base,
const msgpack_object* obj
);

728
core/lochara/big_warder.c Normal file
View File

@@ -0,0 +1,728 @@
#include "./big_warder.h"
#include <stdbool.h>
#include <stdint.h>
#include "util/chaos/xorshift.h"
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/position.h"
#include "core/loentity/ground.h"
#include "core/loplayer/event.h"
#include "core/loplayer/stance.h"
#include "core/loresource/music.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./state.h"
#include "./strategy.h"
#include "./type.h"
#define WIDTH_ .025f
#define HEIGHT_ .06f
#define MARKER_ .03f
#define COLOR_ vec4(0, 0, 0, 1)
#define BPM_ 80
#define BEAT_ (60.f/BPM_)
#define BEAT_MS_ (BEAT_*1000)
#define MUSIC_DURATION_ (BEAT_MS_*144)
#define WAKE_UP_RANGE_ (WIDTH_*8)
#define REWARD_STANCE_ LOPLAYER_STANCE_UNFINISHER
static const loeffect_recipient_status_t base_status_ = {
.attack = .2f,
.defence = .92f,
.speed = .31f,
.jump = 1.1f,
};
static void lochara_big_warder_initialize_shoot_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
vec2_t dir;
locommon_position_sub(
&dir, &base->player->entity->super.super.pos, &base->super.super.pos);
if (dir.x == 0 && dir.y == 0) dir = vec2(1, 0);
vec2_diveq(&dir, vec2_length(&dir));
const vec2_t invdir = vec2(dir.y, -dir.x);
base->param.direction = vec2(MATH_SIGN_NOZERO(dir.x), 0);
for (int32_t i = -4; i <= 4; ++i) {
vec2_t a;
vec2_mul(&a, &dir, 1.5f-MATH_ABS(i)/4.f*.5f);
vec2_t v;
vec2_mul(&v, &dir, .5f);
vec2_t p;
vec2_mul(&p, &invdir, i*.02f);
locommon_position_t pos = base->super.super.pos;
vec2_addeq(&pos.fract, &p);
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_circle_build(b,
.owner = base->super.super.id,
.basepos = pos,
.size = vec2(.02f, .02f),
.color = vec4(1, 1, 1, 1),
.acceleration = a,
.velocity = v,
.knockback = 1,
.effect = loeffect_damage(
base->param.recipient.status.attack*.6f),
.duration = 2000,
);
loentity_store_add(base->entities, &b->super.super);
}
}
static void lochara_big_warder_update_thrust_in_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = BEAT_MS_/4;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
static void lochara_big_warder_update_thrust_out_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = BEAT_MS_/4;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = 1-powf(1-tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
}
static void lochara_big_warder_update_down_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
const bool fast =
meta->state == LOCHARA_STATE_DOWN ||
meta->state == LOCHARA_STATE_REVIVE;
const bool reverse =
meta->state == LOCHARA_STATE_REVIVE ||
meta->state == LOCHARA_STATE_RESUSCITATE;
const uint64_t dur = fast? reverse? BEAT_MS_*3: BEAT_MS_: BEAT_MS_*4;
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
if (reverse) t = dur - t;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_DOWN;
base->cache.instance.motion.time = powf(t*1.f/dur, 6);
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = BEAT_MS_*2,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_ATTACK1,
),
lochara_state_walk(
.period = BEAT_MS_,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
lochara_state_dodge(
.duration = BEAT_MS_/2,
.speed = 1,
.acceleration = {{3, 3}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_WALK,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
),
lochara_state_teleport(
.duration = BEAT_MS_,
.offset = {{WIDTH_*2, 0}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_ATTACK2,
),
{
.state = LOCHARA_STATE_THRUST_IN,
.name = "THRUST_IN",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_THRUST_OUT,
.name = "THRUST_OUT",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_thrust_out_state_,
},
{
.state = LOCHARA_STATE_SHOOT,
.name = "SHOOT",
.initialize = lochara_big_warder_initialize_shoot_state_,
.update = lochara_big_warder_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_DOWN,
.name = "DOWN",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_down_state_,
},
{
.state = LOCHARA_STATE_REVIVE,
.name = "REVIVE",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_down_state_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_down_state_,
},
{
.state = LOCHARA_STATE_RESUSCITATE,
.name = "RESUSCITATE",
.initialize = lochara_state_initialize_any_,
.update = lochara_big_warder_update_down_state_,
},
{0},
};
static void lochara_big_warder_update_wait_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
static const float range2 = WAKE_UP_RANGE_*WAKE_UP_RANGE_;
lochara_base_t* base = instance;
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
if (fabsf(disp.y) < HEIGHT_/2 && vec2_pow_length(&disp) < range2) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
if (loplayer_stance_set_has(&base->player->stances, REWARD_STANCE_)) {
*next = LOCHARA_STRATEGY_WAKE_UP;
} else {
*next = LOCHARA_STRATEGY_WAKE_UP_EVENT;
}
return;
}
statman_transition_to(
state_table_, instance, &base->param.state, LOCHARA_STATE_DEAD);
}
static void lochara_big_warder_initialize_wake_up_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_strategy_initialize_any_(meta, instance, next);
if (meta->state != LOCHARA_STRATEGY_WAKE_UP_EVENT) return;
lochara_base_t* base = instance;
loentity_character_apply_effect(
&base->super, &loeffect_fanatic(MUSIC_DURATION_));
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_curse(MUSIC_DURATION_));
}
static void lochara_big_warder_update_wake_up_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const bool ev = meta->state == LOCHARA_STRATEGY_WAKE_UP_EVENT;
const uint64_t dur = (ev? BEAT_MS_*16: BEAT_MS_*4);
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_strategy_changed;
if (t >= dur) {
*next = LOCHARA_STRATEGY_APPROACH;
return;
}
statman_transition_to(
state_table_, instance, &base->param.state, LOCHARA_STATE_RESUSCITATE);
}
static void lochara_big_warder_update_approach_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
if (!loeffect_recipient_is_alive(&base->param.recipient)) {
*next = LOCHARA_STRATEGY_DEAD;
return;
}
const uint64_t since = base->param.last_strategy_changed;
uint64_t until = since + BEAT_MS_;
if (base->player->event.executor == base->super.super.id) {
const uint64_t msince = base->player->event.ctx.music.since;
if (msince < since) {
const uint64_t beats = (since - msince)/BEAT_MS_ + 1;
until = msince + beats*BEAT_MS_;
}
}
/* ---- strategy transition ---- */
const locommon_position_t* player = &base->player->entity->super.super.pos;
vec2_t disp;
locommon_position_sub(&disp, player, &base->super.super.pos);
if (player->chunk.x != base->super.super.pos.chunk.x ||
player->chunk.y != base->super.super.pos.chunk.y ||
disp.y < -HEIGHT_) {
*next = LOCHARA_STRATEGY_WAIT;
return;
}
const float dist = MATH_ABS(disp.x);
if (base->ticker->time >= until) {
if (MATH_ABS(disp.y) > HEIGHT_) {
*next = LOCHARA_STRATEGY_SHOOT1;
} else if (dist < WIDTH_*5) {
*next = LOCHARA_STRATEGY_COMBO1;
if (chaos_xorshift(base->ticker->time)%3 == 0) {
*next = LOCHARA_STRATEGY_COMBO2;
}
} else if (dist < WIDTH_*10) {
*next = LOCHARA_STRATEGY_COMBO2;
} else {
*next = LOCHARA_STRATEGY_SHOOT1;
}
return;
}
if (dist > WIDTH_*6) {
base->param.direction = vec2(MATH_SIGN_NOZERO(disp.x), 0);
}
}
static const lochara_combat_action_t combo1_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_LEFT,
),
{0},
};
static const lochara_combat_action_t combo2_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_TELEPORT_FRONT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_RIGHT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_TELEPORT_BEHIND,
),
{0},
};
static const lochara_combat_action_t combo3_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_RIGHT,
),
{0},
};
static const lochara_combat_action_t shoot1_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_SHOOT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_RIGHT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_SHOOT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_DODGE_LEFT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2*3,
.state = LOCHARA_STATE_STAND,
),
{0},
};
static const lochara_combat_action_t down_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_DOWN,
),
lochara_combat_action_rest(
.duration = BEAT_MS_*3,
.state = LOCHARA_STATE_REVIVE,
),
{0},
};
static const lochara_combat_action_t kill_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_*4,
.state = LOCHARA_STATE_DEAD,
),
{0},
};
static const lochara_combat_action_t dead_[] = {
lochara_combat_action_rest(
.duration = 30000,
.state = LOCHARA_STATE_DEAD,
),
{0},
};
static const statman_meta_t strategy_table_[] = {
{
.state = LOCHARA_STRATEGY_WAIT,
.name = "WAIT",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_big_warder_update_wait_strategy_,
},
{
.state = LOCHARA_STRATEGY_WAKE_UP,
.name = "WAKE_UP",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_big_warder_update_wake_up_strategy_,
},
{
.state = LOCHARA_STRATEGY_WAKE_UP_EVENT,
.name = "WAKE_UP_EVENT",
.initialize = lochara_big_warder_initialize_wake_up_strategy_,
.update = lochara_big_warder_update_wake_up_strategy_,
},
{
.state = LOCHARA_STRATEGY_APPROACH,
.name = "APPROACH",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_big_warder_update_approach_strategy_,
},
lochara_strategy_combat(COMBO1,
.state_table = state_table_,
.actions = combo1_,
.parry_window = 100,
.parried_next = LOCHARA_STRATEGY_COMBO3,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(COMBO2,
.state_table = state_table_,
.actions = combo2_,
.parry_window = 100,
.parried_next = LOCHARA_STRATEGY_DOWN,
.next = LOCHARA_STRATEGY_COMBO3,
),
lochara_strategy_combat(COMBO3,
.state_table = state_table_,
.actions = combo3_,
.parried_next = LOCHARA_STRATEGY_DOWN,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(SHOOT1,
.state_table = state_table_,
.actions = shoot1_,
.parry_window = 200,
.parried_next = LOCHARA_STRATEGY_DOWN,
.gravity = true,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(DOWN,
.state_table = state_table_,
.actions = down_,
.gravity = true,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(KILL,
.state_table = state_table_,
.actions = kill_,
.gravity = true,
.next = LOCHARA_STRATEGY_WAIT,
),
lochara_strategy_combat(DEAD,
.state_table = state_table_,
.actions = dead_,
.gravity = true,
.next = LOCHARA_STRATEGY_WAIT,
),
{0},
};
static void lochara_big_warder_update_event_(lochara_base_t* base) {
assert(base != NULL);
static const loplayer_event_command_t wake_up[] = {
loplayer_event_command_play_music(LORESOURCE_MUSIC_ID_BOSS_BIG_WARDER),
loplayer_event_command_set_area(.49f, .45f),
loplayer_event_command_set_cinescope(1),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_set_line("boss_big_warder_line0"),
loplayer_event_command_wait(BEAT_MS_*8),
loplayer_event_command_set_line("boss_big_warder_line1"),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_set_line(NULL),
loplayer_event_command_set_cinescope(0),
{0},
};
static const loplayer_event_command_t kill[] = {
loplayer_event_command_set_area(0, 0),
loplayer_event_command_set_cinescope(1),
loplayer_event_command_wait(BEAT_MS_),
loplayer_event_command_stop_music(),
loplayer_event_command_set_line("boss_big_warder_kill_line"),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_finalize(),
{0},
};
static const loplayer_event_command_t dead[] = {
loplayer_event_command_set_area(0, 0),
loplayer_event_command_wait(BEAT_MS_),
loplayer_event_command_stop_music(),
loplayer_event_command_finalize(),
{0},
};
const uint64_t t = base->ticker->time;
loplayer_event_t* event = &base->player->event;
const loentity_id_t id = base->super.super.id;
locommon_position_t basepos = base->super.super.pos;
basepos.fract = vec2(.5f, .5f);
if (base->param.strategy == LOCHARA_STRATEGY_WAKE_UP_EVENT) {
loplayer_event_execute_commands(event, id, &basepos, wake_up);
return;
}
if (event->executor != id) return;
if (base->player->entity->param.state == LOCHARA_STATE_DEAD) {
statman_transition_to(
strategy_table_, base, &base->param.strategy, LOCHARA_STRATEGY_KILL);
loplayer_event_execute_commands(event, id, &basepos, kill);
return;
}
if (base->param.strategy == LOCHARA_STRATEGY_DEAD) {
loplayer_event_execute_commands(event, id, &basepos, dead);
if (!loplayer_stance_set_has(&base->player->stances, REWARD_STANCE_)) {
loplayer_stance_set_add(&base->player->stances, REWARD_STANCE_);
loplayer_popup_queue_new_stance(&base->player->popup, REWARD_STANCE_);
}
return;
}
if (base->player->event.basetime+MUSIC_DURATION_ <= t &&
loeffect_recipient_is_alive(&base->param.recipient)) {
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_curse_trigger());
return;
}
loplayer_event_execute_commands(event, id, &basepos, wake_up);
}
bool lochara_big_warder_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
statman_update(strategy_table_, base, &base->param.strategy);
lochara_big_warder_update_event_(base);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_WARDER,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
if (base->param.strategy != LOCHARA_STRATEGY_WAIT &&
base->param.strategy != LOCHARA_STRATEGY_WAKE_UP &&
base->param.strategy != LOCHARA_STRATEGY_WAKE_UP_EVENT &&
base->param.strategy != LOCHARA_STRATEGY_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
lochara_base_bind_on_ground(base, &vec2(WIDTH_, HEIGHT_+MARKER_));
return true;
}
void lochara_big_warder_build(
lochara_base_t* base, loentity_ground_t* gnd) {
assert(base != NULL);
assert(gnd != NULL);
base->super.super.pos = gnd->super.pos;
vec2_addeq(&base->super.super.pos.fract, &vec2(0, gnd->size.y));
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_BIG_WARDER,
.state = LOCHARA_STATE_DEAD,
.last_state_changed = base->ticker->time,
.strategy = LOCHARA_STRATEGY_WAIT,
.last_strategy_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

18
core/lochara/big_warder.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_big_warder_update(
lochara_base_t* base
);
void
lochara_big_warder_build(
lochara_base_t* base,
loentity_ground_t* gnd
);

305
core/lochara/cavia.c Normal file
View File

@@ -0,0 +1,305 @@
#include "./cavia.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/base.h"
#include "core/lobullet/linear.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/ground.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./combat.h"
#define WIDTH_ .02f
#define HEIGHT_ .05f
#define MARKER_ .02f
#define COLOR_ vec4(0, 0, 0, 1)
static const loeffect_recipient_status_t base_status_ = {
.attack = .1f,
.defence = .3f,
.speed = .05f,
.jump = 1.f,
};
static void lochara_cavia_update_thrust_in_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = 100;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
static void lochara_cavia_update_thrust_out_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = 100;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
}
static void lochara_cavia_initialize_dead_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
for (size_t i = 0; i < 12; ++i) {
const float t = MATH_PI/6*i;
vec2_t v, a, p;
p = vec2(cos(t), sin(t));
vec2_mul(&v, &p, .7f);
vec2_mul(&a, &p, -1.4f);
vec2_muleq(&p, .1f);
locommon_position_t pos = base->super.super.pos;
vec2_addeq(&pos.fract, &p);
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_triangle_build(b,
.owner = b->super.super.id,
.basepos = pos,
.velocity = v,
.acceleration = a,
.size = vec2(.01f, .045f),
.color = vec4(0, 0, 0, .8f),
.angle = t-MATH_PI,
.knockback = 1,
.effect = loeffect_damage(.1f),
.duration = 1200,
);
loentity_store_add(base->entities, &b->super.super);
}
loresource_sound_set_play(&base->res->sound, LORESOURCE_SOUND_ID_ENEMY_TRIGGER);
}
static void lochara_cavia_update_dead_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > 2000) {
t = 2000;
}
if (meta->state == LOCHARA_STATE_RESUSCITATE) {
if (t == 0) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
}
t = 2000 - t;
}
if (t < 1000) {
const float tf = t/1000.f;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_SIT;
base->cache.instance.motion.time = powf(tf, 6);
} else {
const float tf = (t-1000)/1000.f;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_SIT;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_DOWN;
base->cache.instance.motion.time = powf(tf, 4);
}
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = 4000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND2,
),
lochara_state_walk(
.period = 1000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
{
.state = LOCHARA_STATE_THRUST_IN,
.name = "THRUST_IN",
.initialize = lochara_state_initialize_any_,
.update = lochara_cavia_update_thrust_in_,
},
{
.state = LOCHARA_STATE_THRUST_OUT,
.name = "THRUST_OUT",
.initialize = lochara_state_initialize_any_,
.update = lochara_cavia_update_thrust_out_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_cavia_initialize_dead_,
.update = lochara_cavia_update_dead_,
},
{
.state = LOCHARA_STATE_RESUSCITATE,
.name = "RESUSCITATE",
.initialize = lochara_state_initialize_any_,
.update = lochara_cavia_update_dead_,
},
{0},
};
static const lochara_combat_action_t combo1_[] = {
lochara_combat_action_rest(
.duration = 300,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_attack(
.duration = 300,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = 100,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 150,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .8f,
),
lochara_combat_action_rest(
.duration = 150,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 150,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = 1000,
.state = LOCHARA_STATE_THRUST_OUT,
),
{0},
};
static const lochara_combat_action_t dead_[] = {
lochara_combat_action_rest(
.duration = 30000,
.state = LOCHARA_STATE_DEAD,
),
lochara_combat_action_rest(
.duration = 2000,
.state = LOCHARA_STATE_RESUSCITATE,
),
{0},
};
static const statman_meta_t strategy_table_[] = {
lochara_strategy_scouting(
.state_table = state_table_,
.period = 2000,
.stagger = .5f,
.range_close = WIDTH_*2,
.found_close = LOCHARA_STRATEGY_COMBO1,
.range_back = 1,
),
lochara_strategy_combat(COMBO1,
.state_table = state_table_,
.actions = combo1_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
lochara_strategy_combat(DEAD,
.state_table = state_table_,
.actions = dead_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
{0},
};
bool lochara_cavia_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
statman_update(strategy_table_, base, &base->param.strategy);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_CAVIA,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
if (base->param.state != LOCHARA_STATE_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
lochara_base_bind_on_ground(base, &vec2(WIDTH_, HEIGHT_+MARKER_));
return true;
}
void lochara_cavia_build(
lochara_base_t* base, const loentity_ground_t* gnd, float pos) {
assert(base != NULL);
assert(gnd != NULL);
assert(MATH_FLOAT_VALID(pos));
base->super.super.pos = gnd->super.pos;
vec2_addeq(
&base->super.super.pos.fract,
&vec2(gnd->size.x*pos, gnd->size.y));
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_CAVIA,
.state = LOCHARA_STATE_STAND,
.last_state_changed = base->ticker->time,
.strategy = LOCHARA_STRATEGY_SCOUTING,
.last_strategy_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

19
core/lochara/cavia.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_cavia_update(
lochara_base_t* base
);
void
lochara_cavia_build(
lochara_base_t* base,
const loentity_ground_t* gnd,
float pos
);

79
core/lochara/combat.c Normal file
View File

@@ -0,0 +1,79 @@
#include "./combat.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "core/loeffect/effect.h"
#include "core/loentity/character.h"
#include "core/loplayer/combat.h"
#include "./base.h"
#include "./state.h"
static void lochara_combat_action_handle_attack_(
const loplayer_combat_attack_t* attack,
loplayer_combat_attack_result_t result) {
assert(attack != NULL);
lochara_base_t* attacker = attack->data1;
/* TODO(catfoot): store the result in the attacker */
if (result != LOPLAYER_COMBAT_ATTACK_RESULT_EXECUTED) return;
const float v = attacker->param.recipient.status.attack*
MATH_SIGN_NOZERO(attacker->param.direction.x);
loentity_character_knockback(&attacker->player->entity->super, &vec2(v, 0));
const lochara_combat_action_t* action = attack->data2;
switch (action->type) {
case LOCHARA_COMBAT_ACTION_TYPE_REST:
break;
case LOCHARA_COMBAT_ACTION_TYPE_ATTACK:
loentity_character_apply_effect(
&attacker->player->entity->super,
&loeffect_damage(
action->damage*attacker->param.recipient.status.attack));
break;
case LOCHARA_COMBAT_ACTION_TYPE_ATTACK_EFFECT:
loentity_character_apply_effect(
&attacker->player->entity->super, &action->effect);
break;
}
}
void lochara_combat_action_execute_all_attacks(
const lochara_combat_action_t* actions, lochara_base_t* attacker) {
assert(actions != NULL);
assert(attacker != NULL);
loplayer_combat_t* combat = &attacker->player->combat;
uint64_t t = attacker->ticker->time;
for (; actions->duration != 0; t += actions->duration, ++actions) {
if (actions->type == LOCHARA_COMBAT_ACTION_TYPE_REST) continue;
loplayer_combat_add_attack(
combat,
&(loplayer_combat_attack_t) {
.attacker = attacker->super.super.id,
.start = t,
.duration = actions->duration,
.data1 = attacker,
.data2 = (void*) actions,
.handle = lochara_combat_action_handle_attack_,
});
}
}
const lochara_combat_action_t* lochara_combat_action_find_by_time(
const lochara_combat_action_t* actions, uint64_t time) {
assert(actions != NULL);
while (actions->duration != 0 && actions->duration <= time) {
time -= actions->duration;
++actions;
}
return actions->duration > 0? actions: NULL;
}

51
core/lochara/combat.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include <stdint.h>
#include "core/loeffect/effect.h"
#include "./state.h"
typedef struct lochara_base_t lochara_base_t;
typedef enum {
LOCHARA_COMBAT_ACTION_TYPE_REST,
LOCHARA_COMBAT_ACTION_TYPE_ATTACK,
LOCHARA_COMBAT_ACTION_TYPE_ATTACK_EFFECT,
} lochara_combat_action_type_t;
typedef struct {
lochara_combat_action_type_t type;
lochara_state_t state;
uint64_t duration;
union {
float damage;
loeffect_t effect;
};
} lochara_combat_action_t;
#define lochara_combat_action_rest(...) { \
.type = LOCHARA_COMBAT_ACTION_TYPE_REST, \
__VA_ARGS__ \
}
#define lochara_combat_action_attack(...) { \
.type = LOCHARA_COMBAT_ACTION_TYPE_ATTACK, \
__VA_ARGS__ \
}
#define lochara_combat_action_attack_effect(...) { \
.type = LOCHARA_COMBAT_ACTION_TYPE_ATTACK_EFFECT, \
__VA_ARGS__ \
}
void
lochara_combat_action_execute_all_attacks(
const lochara_combat_action_t* actions,
lochara_base_t* attacker
);
const lochara_combat_action_t* /* NULLABLE */
lochara_combat_action_find_by_time(
const lochara_combat_action_t* actions,
uint64_t t
);

85
core/lochara/encephalon.c Normal file
View File

@@ -0,0 +1,85 @@
#include "./encephalon.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/chaos/xorshift.h"
#include "util/math/vector.h"
#include "core/locommon/position.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/ground.h"
#include "core/loshader/character.h"
#include "./base.h"
#define WIDTH_ .1f
#define HEIGHT_ .1f
#define RANGE_ .15f
#define COLOR_ vec4(0, 0, 0, .8f)
bool lochara_encephalon_update(lochara_base_t* base) {
assert(base != NULL);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_ENCEPHALON,
.size = vec2(WIDTH_, HEIGHT_),
.color = COLOR_,
};
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
const bool active = fabsf(disp.x) < RANGE_ && fabsf(disp.y) < HEIGHT_;
if (active) {
if (base->param.state != LOCHARA_STATE_SPELL) {
loresource_sound_set_play(
&base->res->sound, LORESOURCE_SOUND_ID_TOUCH_GATE);
base->param.last_state_changed = base->ticker->time;
}
base->param.state = LOCHARA_STATE_SPELL;
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_resuscitate());
base->player->entity->param.anchor.pos =
base->player->entity->super.super.pos;
if (base->param.last_state_changed+500 > base->ticker->time) {
base->cache.instance.color.w = chaos_xorshift(base->ticker->time)%3 >= 1;
}
} else {
base->param.state = LOCHARA_STATE_STAND;
}
if (base->cache.ground == NULL) return false;
base->super.super.pos = base->cache.ground->super.pos;
base->super.super.pos.fract.y += base->cache.ground->size.y + HEIGHT_;
locommon_position_reduce(&base->super.super.pos);
return true;
}
void lochara_encephalon_build(
lochara_base_t* base, const loentity_ground_t* gnd) {
assert(base != NULL);
assert(gnd != NULL);
base->super.super.pos = gnd->super.pos;
base->super.super.pos.fract.y += gnd->size.y + HEIGHT_;
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_ENCEPHALON,
.state = LOCHARA_STATE_STAND,
.last_state_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient,
base->ticker,
&(loeffect_recipient_status_t) {0});
}

18
core/lochara/encephalon.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_encephalon_update(
lochara_base_t* base
);
void
lochara_encephalon_build(
lochara_base_t* base,
const loentity_ground_t* gnd
);

492
core/lochara/player.c Normal file
View File

@@ -0,0 +1,492 @@
#include "./player.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/easing.h"
#include "core/locommon/position.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/store.h"
#include "core/loplayer/stance.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./state.h"
#include "./type.h"
#define INITIAL_POS_ locommon_position(0, 0, vec2(.5f, .8f))
#define WIDTH_ .02f
#define HEIGHT_ .05f
#define MARKER_ .03f
#define COLOR_ vec4(0, 0, 0, 1)
#define RESPAWN_DURATION_ 4000
#define BULLET_SIZE_ vec2(.02f, .02f)
#define BULLET_COLOR_ vec4(1, .9f, .9f, 1)
#define BULLET_KNOCKBACK_ 4
#define BULLET_DAMAGE_ .2f
#define BULLET_DURATION_ 1000
#define BULLET_COST_ .06f
#define CAMERA_SPEED_ 10
#define CAMERA_SHIFT_Y_ .1f
#define CAMERA_FADE_RADIAL_ .6f
#define CAMERA_DEAD_FADE_ 2
#define CAMERA_DEAD_SPEED_ .3f
#define CAMERA_DEAD_RECOVERY_SPEED_ 10
#define CAMERA_COMBAT_SPEED_ 10
#define CAMERA_COMBAT_RECOVERY_SPEED_ 5
#define CAMERA_ENEMY_ATTACK_INTENSITY_ .2f
#define CAMERA_ENEMY_ATTACK_SPEED_ 4
#define CAMERA_DAMAGE_INTENSITY_ .5f
#define CAMERA_DAMAGE_DURATION_ 1000
#define CAMERA_ABERRATION_ 1
#define CAMERA_CORRUPTION_THRESH_ .2f
#define CAMERA_CORRUPTION_PERIOD_ 4000
#define DIRECTION_X_EPSILON_ .005f
#define GUARD_THRESHOLD_ .8f
#define BACKGUARD_THRESHOLD_ .95f
static const loeffect_recipient_status_t base_status_ = {
.attack = .2f,
.defence = .2f,
.speed = .3f,
.jump = 1.f,
};
static void lochara_player_update_dead_state_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
const uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t >= RESPAWN_DURATION_ && base->player->event.executor == 0) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
base->super.super.pos = base->param.anchor.pos;
*next = LOCHARA_STATE_STAND;
return;
}
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.time = 0;
}
static void lochara_player_update_shoot_state_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
static const uint64_t dur = 200;
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_state_changed;
const float dt = base->ticker->delta_f;
locommon_easing_linear_float(&base->param.movement.x, 0, dt);
locommon_easing_linear_float(&base->param.movement.y, 0, dt);
const bool has_stance = loplayer_stance_set_has(
&base->player->stances, LOPLAYER_STANCE_REVOLUTIONER);
if (has_stance && t >= dur && base->param.recipient.faith > 0) {
locommon_position_t pos = base->super.super.pos;
pos.fract.x += WIDTH_*MATH_SIGN_NOZERO(base->param.direction.x);
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_circle_build(b,
.owner = base->super.super.id,
.basepos = pos,
.size = BULLET_SIZE_,
.color = BULLET_COLOR_,
.velocity = base->param.direction,
.knockback = BULLET_KNOCKBACK_,
.effect = loeffect_damage(
base->param.recipient.status.attack*BULLET_DAMAGE_),
.duration = BULLET_DURATION_,);
loentity_store_add(base->entities, &b->super.super);
loentity_character_apply_effect(
&base->super, &loeffect_lost(BULLET_COST_));
base->param.last_state_changed = base->ticker->time;
}
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_STAND2;
base->cache.instance.motion.time = t > dur? 1: t*1.f / dur;
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = 2000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND2,
),
lochara_state_walk(
.period = 350,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
lochara_state_sprint(
.period = 300,
.acceleration = {{5, 5}},
.speed = 1.4f,
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
lochara_state_dodge(
.duration = 100,
.acceleration = {{3, 3}},
.speed = 1,
.motion1 = LOSHADER_CHARACTER_MOTION_ID_WALK,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
),
lochara_state_jump(),
{
.state = LOCHARA_STATE_GUARD,
.name = "GUARD",
.data = &(lochara_state_move_param_t) {
.speed = 0,
.period = 1000,
.acceleration = {{10, 10}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
},
.initialize = lochara_state_initialize_any_,
.update = lochara_state_update_move_,
},
{
.state = LOCHARA_STATE_SHOOT,
.name = "SHOOT",
.initialize = lochara_state_initialize_any_,
.update = lochara_player_update_shoot_state_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_state_initialize_any_,
.update = lochara_player_update_dead_state_,
.finalize = lochara_state_cancel_transition_,
},
{0},
};
static void lochara_player_handle_controller_(lochara_base_t* base) {
assert(base != NULL);
lochara_state_t next = base->param.state;
switch (base->player->controller.state) {
case LOPLAYER_CONTROLLER_STATE_NONE:
next = LOCHARA_STATE_STAND;
break;
case LOPLAYER_CONTROLLER_STATE_WALK_LEFT:
next = LOCHARA_STATE_WALK_LEFT;
break;
case LOPLAYER_CONTROLLER_STATE_WALK_RIGHT:
next = LOCHARA_STATE_WALK_RIGHT;
break;
case LOPLAYER_CONTROLLER_STATE_SPRINT_LEFT:
next = LOCHARA_STATE_SPRINT_LEFT;
break;
case LOPLAYER_CONTROLLER_STATE_SPRINT_RIGHT:
next = LOCHARA_STATE_SPRINT_RIGHT;
break;
case LOPLAYER_CONTROLLER_STATE_DODGE_FORWARD:
next =
base->param.direction.x < 0?
LOCHARA_STATE_DODGE_LEFT:
LOCHARA_STATE_DODGE_RIGHT;
break;
case LOPLAYER_CONTROLLER_STATE_DODGE_LEFT:
next = LOCHARA_STATE_DODGE_LEFT;
break;
case LOPLAYER_CONTROLLER_STATE_DODGE_RIGHT:
next = LOCHARA_STATE_DODGE_RIGHT;
break;
case LOPLAYER_CONTROLLER_STATE_JUMP:
if (base->param.on_ground) next = LOCHARA_STATE_JUMP;
break;
case LOPLAYER_CONTROLLER_STATE_GUARD:
next = LOCHARA_STATE_GUARD;
break;
case LOPLAYER_CONTROLLER_STATE_SHOOT:
next = LOCHARA_STATE_SHOOT;
break;
}
if (loplayer_combat_is_attack_pending(&base->player->combat)) {
if (next != LOCHARA_STATE_STAND &&
next != LOCHARA_STATE_DODGE_LEFT &&
next != LOCHARA_STATE_DODGE_RIGHT &&
next != LOCHARA_STATE_GUARD) {
next = LOCHARA_STATE_STAND;
}
} else {
vec2_t dir;
locommon_position_sub(
&dir, &base->player->controller.cursor, &base->super.super.pos);
if (fabsf(dir.x) > DIRECTION_X_EPSILON_) {
vec2_div(&base->param.direction, &dir, vec2_length(&dir));
}
}
statman_transition_to(state_table_, base, &base->param.state, next);
}
static void lochara_player_update_combat_(lochara_base_t* base) {
assert(base != NULL);
const lochara_state_t state = base->param.state;
if (state == LOCHARA_STATE_DEAD) {
loplayer_combat_set_accepting(
&base->player->combat,
false,
LOPLAYER_COMBAT_ATTACK_RESULT_ABORTED);
} else {
loplayer_combat_set_accepting(
&base->player->combat,
state != LOCHARA_STATE_DODGE_LEFT &&
state != LOCHARA_STATE_DODGE_RIGHT,
LOPLAYER_COMBAT_ATTACK_RESULT_DODGED);
}
loplayer_combat_set_guarding(
&base->player->combat,
state == LOCHARA_STATE_GUARD);
if (loplayer_combat_is_attack_pending(&base->player->combat)) {
base->param.movement = vec2(0, 0);
base->param.gravity = 0;
}
float r;
loplayer_combat_attack_t a;
if (!loplayer_combat_pop_attack(&base->player->combat, &a, &r)) return;
loentity_store_iterator_t itr;
if (!loentity_store_find_item_by_id(base->entities, &itr, a.attacker) ||
itr.character == NULL) {
return;
}
vec2_t disp;
locommon_position_sub(
&disp, &itr.character->super.pos, &base->super.super.pos);
const float dir = disp.x * base->param.direction.x;
const bool reflected =
r > (dir < 0? BACKGUARD_THRESHOLD_: GUARD_THRESHOLD_);
loplayer_combat_attack_handle(
&a,
reflected?
LOPLAYER_COMBAT_ATTACK_RESULT_REFLECTED:
LOPLAYER_COMBAT_ATTACK_RESULT_EXECUTED);
if (!reflected) return;
loresource_sound_set_play(&base->res->sound, LORESOURCE_SOUND_ID_REFLECTION);
loentity_character_apply_effect(
itr.character,
&loeffect_damage(base->param.recipient.status.attack));
loentity_character_knockback(
itr.character,
&vec2(MATH_SIGN(disp.x)*base->param.recipient.status.attack, 0));
}
static void lochara_player_update_camera_(lochara_base_t* base) {
assert(base != NULL);
const loeffect_recipient_t* r = &base->param.recipient;
const uint64_t t = base->ticker->time;
const uint64_t pt = base->ticker->prev_time;
const float dt = base->ticker->delta_f;
const bool combat = loplayer_combat_is_attack_pending(&base->player->combat);
loplayer_camera_t* camera = &base->player->camera;
vec2_t sz = base->player->camera.chunk_winsz;
vec2_diveq(&sz,
base->player->camera.scale*
(camera->posteffect.distortion_radial+1));
locommon_position_t pos = base->super.super.pos;
if (!combat) {
pos.fract.y += CAMERA_SHIFT_Y_;
locommon_position_reduce(&pos);
}
loplayer_event_bind_rect_in_area(&base->player->event, &pos, &sz);
vec2_t disp;
locommon_position_sub(&disp, &pos, &camera->pos);
if (vec2_pow_length(&disp) < 1) {
locommon_easing_smooth_position(&camera->pos, &pos, dt*CAMERA_SPEED_);
} else {
camera->pos = pos;
}
locommon_easing_smooth_float(&camera->scale, 1, dt);
/* ---- dead ---- */
if (base->param.state == LOCHARA_STATE_DEAD) {
locommon_easing_smooth_float(
&camera->posteffect.fade_radial,
CAMERA_DEAD_FADE_,
dt*CAMERA_DEAD_SPEED_);
} else {
locommon_easing_smooth_float(
&camera->posteffect.fade_radial,
CAMERA_FADE_RADIAL_,
dt*CAMERA_DEAD_RECOVERY_SPEED_);
}
/* ---- combat ---- */
if (combat) {
locommon_easing_smooth_float(
&camera->posteffect.distortion_radial,
1,
dt*CAMERA_COMBAT_SPEED_);
} else {
locommon_easing_smooth_float(
&camera->posteffect.distortion_radial,
0,
dt*CAMERA_COMBAT_RECOVERY_SPEED_);
}
/* ---- enemy attack ---- */
const loplayer_combat_attack_t* attack = base->player->combat.first_attack;
if (attack != NULL && pt < attack->start && attack->start <= t) {
camera->posteffect.distortion_urgent = CAMERA_ENEMY_ATTACK_INTENSITY_;
} else {
locommon_easing_smooth_float(
&camera->posteffect.distortion_urgent,
0,
dt*CAMERA_ENEMY_ATTACK_SPEED_);
}
/* ---- damage ----- */
if (0 < r->last_damage &&
r->last_damage < t && t < r->last_damage+CAMERA_DAMAGE_DURATION_) {
camera->posteffect.raster_whole =
(1 - (t - r->last_damage)*1.f/CAMERA_DAMAGE_DURATION_)*
CAMERA_DAMAGE_INTENSITY_;
} else {
camera->posteffect.raster_whole = 0;
}
/* ---- amnesia ---- */
locommon_easing_smooth_float(
&camera->posteffect.distortion_amnesia,
!!(r->effects.amnesia.start < t &&
t < r->effects.amnesia.start+r->effects.amnesia.duration),
dt);
/* ---- corruption ---- */
if (r->madness <= CAMERA_CORRUPTION_THRESH_) {
if (camera->corruption_since == 0) camera->corruption_since = t;
const uint64_t p =
(t - camera->corruption_since)%CAMERA_CORRUPTION_PERIOD_;
camera->pixsort = 1-powf(p*1.f/CAMERA_CORRUPTION_PERIOD_, 2);
} else {
camera->corruption_since = 0;
locommon_easing_smooth_float(&camera->pixsort, 0, dt);
}
/* ---- passive ---- */
locommon_easing_smooth_float(
&camera->posteffect.aberration_radial, CAMERA_ABERRATION_, dt);
}
bool lochara_player_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
if (!loeffect_recipient_is_alive(&base->param.recipient)) {
statman_transition_to(
state_table_, base, &base->param.state, LOCHARA_STATE_DEAD);
}
if (loplayer_stance_set_has(
&base->player->stances, LOPLAYER_STANCE_UNFINISHER)) {
if (base->param.recipient.faith > .5f) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_heal(base->ticker->delta_f*.01f));
}
}
lochara_player_handle_controller_(base);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_PLAYER,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
lochara_player_update_combat_(base);
if (base->param.state != LOCHARA_STATE_DODGE_LEFT &&
base->param.state != LOCHARA_STATE_DODGE_RIGHT &&
base->param.state != LOCHARA_STATE_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
loplayer_event_bind_rect_in_area(
&base->player->event,
&base->super.super.pos,
&vec2(WIDTH_*.8f, HEIGHT_));
lochara_player_update_camera_(base);
return true;
}
void lochara_player_build(lochara_base_t* base) {
assert(base != NULL);
base->super.super.pos = base->param.anchor.pos = INITIAL_POS_;
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_PLAYER,
.state = LOCHARA_STATE_STAND,
.last_state_changed = base->ticker->time,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

15
core/lochara/player.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
#include "./base.h"
bool
lochara_player_update(
lochara_base_t* base
);
void
lochara_player_build(
lochara_base_t* base
);

58
core/lochara/pool.c Normal file
View File

@@ -0,0 +1,58 @@
#include "./pool.h"
#include <assert.h>
#include <stddef.h>
#include <msgpack.h>
#include "util/memory/memory.h"
#include "core/lobullet/pool.h"
#include "core/locommon/counter.h"
#include "core/locommon/ticker.h"
#include "core/loentity/pool.h"
#include "core/loentity/entity.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/set.h"
#include "./base.h"
LOENTITY_POOL_SOURCE_TEMPLATE(lochara)
lochara_pool_t* lochara_pool_new(
loresource_set_t* res,
loshader_set_t* shaders,
locommon_counter_t* idgen,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
loplayer_t* player,
lobullet_pool_t* bullet,
size_t length) {
assert(res != NULL);
assert(shaders != NULL);
assert(idgen != NULL);
assert(ticker != NULL);
assert(entities != NULL);
assert(player != NULL);
assert(bullet != NULL);
assert(length > 0);
lochara_pool_t* pool =
memory_new(sizeof(*pool) + (length-1)*sizeof(pool->items[0]));
*pool = (typeof(*pool)) {
.idgen = idgen,
.length = length,
};
for (size_t i = 0; i < pool->length; ++i) {
lochara_base_initialize(
&pool->items[i],
res,
shaders,
ticker,
entities,
player,
bullet);
}
return pool;
}

46
core/lochara/pool.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <stddef.h>
#include <msgpack.h>
#include "core/lobullet/pool.h"
#include "core/locommon/counter.h"
#include "core/locommon/ticker.h"
#include "core/loentity/entity.h"
#include "core/loplayer/player.h"
#include "core/loresource/set.h"
#include "core/loshader/set.h"
#include "./base.h"
struct lochara_pool_t;
typedef struct lochara_pool_t lochara_pool_t;
lochara_pool_t* /* OWNERSHIP */
lochara_pool_new(
loresource_set_t* res,
loshader_set_t* shaders,
locommon_counter_t* idgen,
const locommon_ticker_t* ticker,
loentity_store_t* entities,
loplayer_t* player,
lobullet_pool_t* bullet,
size_t length
);
void
lochara_pool_delete(
lochara_pool_t* pool /* OWNERSHIP */
);
lochara_base_t*
lochara_pool_create(
lochara_pool_t* pool
);
lochara_base_t*
lochara_pool_unpack_item(
lochara_pool_t* pool,
const msgpack_object* obj
);

179
core/lochara/state.c Normal file
View File

@@ -0,0 +1,179 @@
#include "./state.h"
#include <assert.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/locommon/easing.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#define MIDAIR_SPEED_ .8f
#define BACKWALK_SPEED_ .6f
void lochara_state_initialize_any_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
base->param.last_state_changed = base->ticker->time;
}
void lochara_state_cancel_transition_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
*state = meta->state;
}
void lochara_state_update_move_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
const lochara_state_move_param_t* p = meta->data;
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_state_changed;
const float dt = base->ticker->delta_f;
assert(vec2_valid(&p->acceleration));
assert(MATH_FLOAT_VALID(p->speed));
assert(vec2_valid(&p->velocity));
assert(p->period > 0);
/* ---- acceleration ---- */
float speed = p->speed*base->param.recipient.status.speed;
if (!base->param.on_ground) {
speed *= MIDAIR_SPEED_;
}
if (vec2_dot(&p->velocity, &base->param.direction) < 0) {
speed *= BACKWALK_SPEED_;
}
vec2_t velocity = p->velocity;
vec2_muleq(&velocity, speed);
locommon_easing_linear_float(
&base->param.movement.x, velocity.x, p->acceleration.x*dt);
locommon_easing_linear_float(
&base->param.movement.y, velocity.y, p->acceleration.y*dt);
/* ---- periodic motion ---- */
base->cache.instance.motion.time =
(!!base->param.on_ground)*(1-fabs(t%p->period*1.f/p->period*2 - 1));
base->cache.instance.motion.from = p->motion1;
base->cache.instance.motion.to = p->motion2;
}
void lochara_state_initialize_dodge_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
loresource_sound_set_play(&base->res->sound, LORESOURCE_SOUND_ID_DODGE);
}
void lochara_state_update_dodge_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
const lochara_state_dodge_param_t* p = meta->data;
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_state_changed;
assert(p->duration > 0);
assert(vec2_valid(&p->acceleration));
assert(vec2_valid(&p->velocity));
if (t > p->duration) {
*state = LOCHARA_STATE_STAND;
return;
}
/* ---- acceleration ---- */
base->param.movement = p->acceleration;
vec2_muleq(&base->param.movement, t/1000.f);
base->param.movement.x *= -MATH_SIGN(p->velocity.x);
base->param.movement.y *= -MATH_SIGN(p->velocity.y);
vec2_addeq(&base->param.movement, &p->velocity);
/* ---- motion ---- */
base->cache.instance.motion.time = powf(t*1.f/p->duration, 1.2f);
base->cache.instance.motion.from = p->motion1;
base->cache.instance.motion.to = p->motion2;
const float a = powf(t*2.f/p->duration-1, 2);
base->cache.instance.size.x *= (1-a)*.8f+1;
base->cache.instance.color.w *= MATH_MIN(a+.2f, 1);
}
void lochara_state_initialize_jump_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_state_initialize_any_(meta, instance, next);
lochara_base_t* base = instance;
base->param.gravity += base->param.recipient.status.jump;
*next = LOCHARA_STATE_STAND;
}
void lochara_state_update_teleport_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const lochara_state_teleport_param_t* p = meta->data;
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
const uint64_t t = base->ticker->time - base->param.last_state_changed;
const uint64_t pt =
t == 0? 0: base->ticker->prev_time - base->param.last_state_changed;
if (pt < p->duration/2 && p->duration/2 <= t) {
const lochara_base_t* player = base->player->entity;
const float pdir =
MATH_SIGN_NOZERO(player->param.direction.x)*p->direction;
base->param.direction = vec2(-pdir, 0);
vec2_t offset = p->offset;
offset.x *= pdir;
base->super.super.pos = player->super.super.pos;
vec2_addeq(&base->super.super.pos.fract, &offset);
locommon_position_reduce(&base->super.super.pos);
}
const float tf = fabsf(t*1.f/p->duration*2 - 1);
base->cache.instance.motion.from = p->motion2;
base->cache.instance.motion.to = p->motion1;
base->cache.instance.motion.time = tf;
base->cache.instance.color.w = tf;
}

233
core/lochara/state.h Normal file
View File

@@ -0,0 +1,233 @@
#pragma once
#include <stddef.h>
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/loshader/character.h"
typedef enum {
/* BENUM BEGIN lochara_state */
LOCHARA_STATE_STAND,
LOCHARA_STATE_WALK_LEFT,
LOCHARA_STATE_WALK_RIGHT,
LOCHARA_STATE_SPRINT_LEFT,
LOCHARA_STATE_SPRINT_RIGHT,
LOCHARA_STATE_DODGE_LEFT,
LOCHARA_STATE_DODGE_RIGHT,
LOCHARA_STATE_JUMP,
LOCHARA_STATE_TELEPORT_BEHIND,
LOCHARA_STATE_TELEPORT_FRONT,
LOCHARA_STATE_THRUST_IN,
LOCHARA_STATE_THRUST_OUT,
LOCHARA_STATE_SLASH,
LOCHARA_STATE_SPELL,
LOCHARA_STATE_SHOOT,
LOCHARA_STATE_DOWN,
LOCHARA_STATE_DEAD,
LOCHARA_STATE_REVIVE,
LOCHARA_STATE_RESUSCITATE,
LOCHARA_STATE_GUARD,
/* BENUM END */
} lochara_state_t;
/* generated benum utils */
#include "core/lochara/benum/state.h"
/* ---- default state handlers ---- */
void
lochara_state_initialize_any_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_cancel_transition_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_update_move_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_initialize_dodge_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_update_dodge_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_initialize_jump_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_state_update_teleport_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
/* ---- state meta constructor ---- */
typedef struct {
vec2_t acceleration;
float speed;
vec2_t velocity;
uint64_t period;
loshader_character_motion_id_t motion1;
loshader_character_motion_id_t motion2;
} lochara_state_move_param_t;
#define lochara_state_stand(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_STAND, \
.name = "STAND", \
.data = &(lochara_state_move_param_t) { \
.speed = 0, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}
#define lochara_state_walk(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_WALK_LEFT, \
.name = "WALK_LEFT", \
.data = &(lochara_state_move_param_t) { \
.speed = 1, \
.velocity = {{-1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}, \
(statman_meta_t) { \
.state = LOCHARA_STATE_WALK_RIGHT, \
.name = "WALK_RIGHT", \
.data = &(lochara_state_move_param_t) { \
.speed = 1, \
.velocity = {{1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}
#define lochara_state_sprint(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_SPRINT_LEFT, \
.name = "SPRINT_LEFT", \
.data = &(lochara_state_move_param_t) { \
.velocity = {{-1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}, \
(statman_meta_t) { \
.state = LOCHARA_STATE_SPRINT_RIGHT, \
.name = "SPRINT_RIGHT", \
.data = &(lochara_state_move_param_t) { \
.velocity = {{1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_move_, \
}
typedef struct {
uint64_t duration;
float speed;
vec2_t acceleration;
vec2_t velocity;
loshader_character_motion_id_t motion1;
loshader_character_motion_id_t motion2;
} lochara_state_dodge_param_t;
#define lochara_state_dodge(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_DODGE_LEFT, \
.name = "DODGE_LEFT", \
.data = &(lochara_state_dodge_param_t) { \
.velocity = {{-1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_dodge_, \
.update = lochara_state_update_dodge_, \
.finalize = lochara_state_cancel_transition_, \
}, \
(statman_meta_t) { \
.state = LOCHARA_STATE_DODGE_RIGHT, \
.name = "DODGE_RIGHT", \
.data = &(lochara_state_dodge_param_t) { \
.velocity = {{1, 0}}, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_dodge_, \
.update = lochara_state_update_dodge_, \
.finalize = lochara_state_cancel_transition_, \
}
#define lochara_state_jump() \
(statman_meta_t) { \
.state = LOCHARA_STATE_JUMP, \
.name = "JUMP", \
.initialize = lochara_state_initialize_jump_, \
}
typedef struct {
uint64_t duration;
float direction;
vec2_t offset;
loshader_character_motion_id_t motion1;
loshader_character_motion_id_t motion2;
} lochara_state_teleport_param_t;
#define lochara_state_teleport(...) \
(statman_meta_t) { \
.state = LOCHARA_STATE_TELEPORT_FRONT, \
.name = "TELEPORT_FRONT", \
.data = &(lochara_state_teleport_param_t) { \
.direction = 1, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_teleport_, \
}, \
(statman_meta_t) { \
.state = LOCHARA_STATE_TELEPORT_BEHIND, \
.name = "TELEPORT_BEHIND", \
.data = &(lochara_state_teleport_param_t) { \
.direction = -1, \
__VA_ARGS__ \
}, \
.initialize = lochara_state_initialize_any_, \
.update = lochara_state_update_teleport_, \
}

184
core/lochara/strategy.c Normal file
View File

@@ -0,0 +1,184 @@
#include "./strategy.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/chaos/xorshift.h"
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "./base.h"
#include "./combat.h"
#include "./state.h"
#define SEED_ 3467
#define PARRY_DAMAGE_ 1
void lochara_strategy_initialize_any_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
base->param.last_strategy_changed = base->ticker->time;
}
void lochara_strategy_cancel_transition_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
*next = meta->state;
}
void lochara_strategy_initialize_scouting_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_strategy_initialize_any_(meta, instance, next);
const lochara_strategy_scouting_param_t* p = meta->data;
lochara_base_t* base = instance;
statman_transition_to(
p->state_table, base, &base->param.state, LOCHARA_STATE_STAND);
}
void lochara_strategy_update_scouting_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const lochara_strategy_scouting_param_t* p = meta->data;
assert(p->state_table != NULL);
assert(p->period > 0);
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time;
const bool knockback = base->param.last_knockback+500 > t;
const bool event = base->player->event.executor != 0;
/* ---- die ---- */
if (!loeffect_recipient_is_alive(&base->param.recipient)) {
*next = LOCHARA_STRATEGY_DEAD;
return;
}
/* ---- strategy transition ---- */
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
if (!knockback && MATH_ABS(disp.y) < .1f && !event) {
disp.x *= MATH_SIGN_NOZERO(base->param.direction.x);
const float b = p->range_back;
if (-p->range_close*b < disp.x && disp.x < p->range_close) {
*next = p->found_close;
} else if (-p->range_mid*b < disp.x && disp.x < p->range_mid) {
*next = p->found_mid;
} else if (-p->range_long*b < disp.x && disp.x < p->range_long) {
*next = p->found_long;
}
if (*next != LOCHARA_STRATEGY_SCOUTING) return;
}
/* ---- state transition ---- */
const uint64_t since = base->param.last_state_changed;
uint64_t seed = 1+since*SEED_;
# define rand_() (seed = chaos_xorshift(seed))
const uint64_t dur = rand_()%p->period + p->period/2;
lochara_state_t state = base->param.state;
if (knockback) {
state = LOCHARA_STATE_STAND;
} else if (since+dur < t) {
seed = 1+t*SEED_;
if (state == LOCHARA_STATE_STAND && rand_()%100/100.f < p->stagger) {
const float r = .5f + base->cache.ground_pos.x*.5f;
if (rand_()%100/100.f < r) {
base->param.direction = vec2(-1, 0);
state = LOCHARA_STATE_WALK_LEFT;
} else {
base->param.direction = vec2(1, 0);
state = LOCHARA_STATE_WALK_RIGHT;
}
} else {
state = LOCHARA_STATE_STAND;
}
}
statman_transition_to(p->state_table, base, &base->param.state, state);
# undef rand_
}
void lochara_strategy_initialize_combat_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
const lochara_strategy_combat_param_t* p = meta->data;
assert(p->actions != NULL);
if (base->param.last_knockback+p->parry_window > base->ticker->time) {
loentity_character_apply_effect(
&base->super, &loeffect_damage(PARRY_DAMAGE_));
*next = p->parried_next;
return;
}
lochara_strategy_initialize_any_(meta, instance, next);
lochara_combat_action_execute_all_attacks(p->actions, base);
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
base->param.direction = vec2(MATH_SIGN_NOZERO(disp.x), 0);
}
void lochara_strategy_update_combat_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const lochara_strategy_combat_param_t* p = meta->data;
assert(p->actions != NULL);
assert(p->next != meta->state);
lochara_base_t* base = instance;
if (!p->gravity) base->param.gravity = 0;
const uint64_t t = base->ticker->time - base->param.last_strategy_changed;
const lochara_combat_action_t* action =
lochara_combat_action_find_by_time(p->actions, t);
if (action == NULL) {
*next = p->next;
return;
}
statman_transition_to(
p->state_table, base, &base->param.state, action->state);
}

131
core/lochara/strategy.h Normal file
View File

@@ -0,0 +1,131 @@
#pragma once
#include "util/statman/statman.h"
#include "./combat.h"
typedef enum {
/* BENUM BEGIN lochara_strategy */
LOCHARA_STRATEGY_WAIT,
LOCHARA_STRATEGY_WAKE_UP,
LOCHARA_STRATEGY_WAKE_UP_EVENT,
LOCHARA_STRATEGY_SCOUTING,
LOCHARA_STRATEGY_APPROACH,
LOCHARA_STRATEGY_COMBO1,
LOCHARA_STRATEGY_COMBO2,
LOCHARA_STRATEGY_COMBO3,
LOCHARA_STRATEGY_SHOOT1,
LOCHARA_STRATEGY_SHOOT2,
LOCHARA_STRATEGY_SHOOT3,
LOCHARA_STRATEGY_SPELL1,
LOCHARA_STRATEGY_SPELL2,
LOCHARA_STRATEGY_SPELL3,
LOCHARA_STRATEGY_DOWN,
LOCHARA_STRATEGY_DEAD,
LOCHARA_STRATEGY_KILL,
/* BENUM END */
} lochara_strategy_t;
/* generated benum header */
#include "core/lochara/benum/strategy.h"
/* ---- default strategy handlers ---- */
void
lochara_strategy_initialize_any_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_cancel_transition_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_initialize_scouting_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_update_scouting_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_initialize_combat_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
void
lochara_strategy_update_combat_(
const statman_meta_t* meta,
void* instance,
statman_state_t* next
);
/* ---- default strategy constructors ---- */
typedef struct {
const statman_meta_t* state_table;
uint64_t period;
float stagger; /* 0~1 */
float range_back;
float range_close;
float range_mid;
float range_long;
lochara_strategy_t found_close;
lochara_strategy_t found_mid;
lochara_strategy_t found_long;
} lochara_strategy_scouting_param_t;
#define lochara_strategy_scouting(...) \
(statman_meta_t) { \
.state = LOCHARA_STRATEGY_SCOUTING, \
.name = "SCOUTING", \
.data = &(lochara_strategy_scouting_param_t) { \
__VA_ARGS__ \
}, \
.initialize = lochara_strategy_initialize_scouting_, \
.update = lochara_strategy_update_scouting_, \
}
typedef struct {
const statman_meta_t* state_table;
const lochara_combat_action_t* actions;
uint64_t parry_window;
lochara_strategy_t parried_next;
bool gravity;
lochara_strategy_t next;
} lochara_strategy_combat_param_t;
#define lochara_strategy_combat(NAME, ...) \
(statman_meta_t) { \
.state = LOCHARA_STRATEGY_##NAME, \
.name = #NAME, \
.data = &(lochara_strategy_combat_param_t) { \
__VA_ARGS__ \
}, \
.initialize = lochara_strategy_initialize_combat_, \
.update = lochara_strategy_update_combat_, \
.finalize = lochara_strategy_cancel_transition_, \
}

View File

@@ -0,0 +1,718 @@
#include "./theists_child.h"
#include <stdbool.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/linear.h"
#include "core/lobullet/pool.h"
#include "core/locommon/position.h"
#include "core/loentity/ground.h"
#include "core/loplayer/event.h"
#include "core/loplayer/stance.h"
#include "core/loresource/music.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./state.h"
#include "./strategy.h"
#include "./type.h"
#define WIDTH_ .025f
#define HEIGHT_ .06f
#define MARKER_ .03f
#define COLOR_ vec4(0, 0, 0, 1)
#define BPM_ 140
#define BEAT_ (60.f/BPM_)
#define BEAT_MS_ (BEAT_*1000)
#define MUSIC_DURATION_ (BEAT_MS_*232)
#define WAKE_UP_RANGE_ (WIDTH_*6)
#define REWARD_STANCE_ LOPLAYER_STANCE_REVOLUTIONER
static const loeffect_recipient_status_t base_status_ = {
.attack = .3f,
.defence = .92f,
.speed = .31f,
.jump = 1.1f,
};
static void lochara_theists_child_initialize_shoot_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
vec2_t dir;
locommon_position_sub(
&dir, &base->player->entity->super.super.pos, &base->super.super.pos);
if (dir.x == 0 && dir.y == 0) dir = vec2(1, 0);
vec2_diveq(&dir, vec2_length(&dir));
const vec2_t invdir = vec2(dir.y, -dir.x);
base->param.direction = vec2(MATH_SIGN_NOZERO(dir.x), 0);
for (int32_t i = -4; i <= 4; ++i) {
vec2_t v;
vec2_mul(&v, &dir, 1);
vec2_t p;
vec2_mul(&p, &invdir, i*.02f);
locommon_position_t pos = base->super.super.pos;
vec2_addeq(&pos.fract, &p);
locommon_position_reduce(&pos);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_circle_build(b,
.owner = base->super.super.id,
.basepos = pos,
.size = vec2(.02f, .02f),
.color = vec4(1, 1, 1, 1),
.velocity = v,
.knockback = 1,
.effect = loeffect_damage(
base->param.recipient.status.attack*.6f),
.duration = 1000,
);
loentity_store_add(base->entities, &b->super.super);
}
}
static void lochara_theists_child_update_thrust_in_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = BEAT_MS_/4;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
static void lochara_theists_child_update_thrust_out_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = BEAT_MS_/4;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
}
static void lochara_theists_child_update_down_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
const bool fast =
meta->state == LOCHARA_STATE_DOWN ||
meta->state == LOCHARA_STATE_REVIVE;
const bool reverse =
meta->state == LOCHARA_STATE_REVIVE ||
meta->state == LOCHARA_STATE_RESUSCITATE;
const uint64_t dur = fast? reverse? BEAT_MS_*3: BEAT_MS_: BEAT_MS_*16;
const uint64_t key = fast? dur: dur/2;
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
if (reverse) t = dur - t;
if (t < key) {
const float tf = t*1.f/key;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_SIT;
base->cache.instance.motion.time = powf(tf, 6);
} else {
const float tf = (t-key)*1.f/(dur-key);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_SIT;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_DOWN;
base->cache.instance.motion.time = powf(tf, 4);
}
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = BEAT_MS_*2,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_ATTACK1,
),
lochara_state_walk(
.period = BEAT_MS_,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
lochara_state_dodge(
.duration = BEAT_MS_/2,
.speed = 1,
.acceleration = {{3, 3}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_WALK,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
),
lochara_state_jump(),
lochara_state_teleport(
.duration = BEAT_MS_*2,
.offset = {{WIDTH_*2, 0}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_ATTACK2,
),
{
.state = LOCHARA_STATE_THRUST_IN,
.name = "THRUST_IN",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_THRUST_OUT,
.name = "THRUST_OUT",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_thrust_out_state_,
},
{
.state = LOCHARA_STATE_SHOOT,
.name = "SHOOT",
.initialize = lochara_theists_child_initialize_shoot_state_,
.update = lochara_theists_child_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_DOWN,
.name = "DOWN",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_down_state_,
},
{
.state = LOCHARA_STATE_REVIVE,
.name = "REVIVE",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_down_state_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_down_state_,
},
{
.state = LOCHARA_STATE_RESUSCITATE,
.name = "RESUSCITATE",
.initialize = lochara_state_initialize_any_,
.update = lochara_theists_child_update_down_state_,
},
{0},
};
static void lochara_theists_child_update_wait_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
static const float range2 = WAKE_UP_RANGE_*WAKE_UP_RANGE_;
lochara_base_t* base = instance;
vec2_t disp;
locommon_position_sub(
&disp, &base->player->entity->super.super.pos, &base->super.super.pos);
if (fabsf(disp.y) < HEIGHT_/2 && vec2_pow_length(&disp) < range2) {
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
if (loplayer_stance_set_has(&base->player->stances, REWARD_STANCE_)) {
*next = LOCHARA_STRATEGY_WAKE_UP;
} else {
*next = LOCHARA_STRATEGY_WAKE_UP_EVENT;
}
return;
}
statman_transition_to(
state_table_, instance, &base->param.state, LOCHARA_STATE_DEAD);
}
static void lochara_theists_child_initialize_wake_up_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_strategy_initialize_any_(meta, instance, next);
if (meta->state != LOCHARA_STRATEGY_WAKE_UP_EVENT) return;
lochara_base_t* base = instance;
loentity_character_apply_effect(
&base->super, &loeffect_fanatic(MUSIC_DURATION_));
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_curse(MUSIC_DURATION_));
}
static void lochara_theists_child_update_wake_up_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
const bool ev = meta->state == LOCHARA_STRATEGY_WAKE_UP_EVENT;
const uint64_t dur = (ev? BEAT_MS_*64: BEAT_MS_*16);
lochara_base_t* base = instance;
const uint64_t t = base->ticker->time - base->param.last_strategy_changed;
if (t >= dur) {
*next = LOCHARA_STRATEGY_APPROACH;
return;
}
statman_transition_to(
state_table_, instance, &base->param.state, LOCHARA_STATE_RESUSCITATE);
}
static void lochara_theists_child_update_approach_strategy_(
const statman_meta_t* meta, void* instance, statman_state_t* next) {
assert(meta != NULL);
assert(instance != NULL);
assert(next != NULL);
lochara_base_t* base = instance;
if (!loeffect_recipient_is_alive(&base->param.recipient)) {
*next = LOCHARA_STRATEGY_DEAD;
return;
}
const uint64_t since = base->param.last_strategy_changed;
uint64_t until = since + BEAT_MS_;
if (base->player->event.executor == base->super.super.id) {
const uint64_t msince = base->player->event.ctx.music.since;
if (msince < since) {
const uint64_t beats = (since - msince)/BEAT_MS_ + 1;
until = msince + beats*BEAT_MS_;
}
}
/* ---- strategy transition ---- */
const locommon_position_t* player = &base->player->entity->super.super.pos;
vec2_t disp;
locommon_position_sub(&disp, player, &base->super.super.pos);
if (player->chunk.x != base->super.super.pos.chunk.x ||
player->chunk.y != base->super.super.pos.chunk.y ||
disp.y < -HEIGHT_) {
*next = LOCHARA_STRATEGY_WAIT;
return;
}
const float dist = MATH_ABS(disp.x);
if (base->ticker->time >= until) {
if (MATH_ABS(disp.y) > HEIGHT_) {
*next = LOCHARA_STRATEGY_SHOOT1;
} else if (dist < WIDTH_*4) {
*next = LOCHARA_STRATEGY_COMBO1;
} else if (dist < WIDTH_*8) {
*next = LOCHARA_STRATEGY_SHOOT1;
} else {
*next = LOCHARA_STRATEGY_COMBO2;
}
return;
}
/* ---- approaching ---- */
if (dist > WIDTH_*6) {
base->param.direction = vec2(MATH_SIGN_NOZERO(disp.x), 0);
const lochara_state_t state =
disp.x < 0? LOCHARA_STATE_WALK_LEFT: LOCHARA_STATE_WALK_RIGHT;
statman_transition_to(state_table_, base, &base->param.state, state);
}
}
static const lochara_combat_action_t combo1_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_/4*3,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .8f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4*3,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_DODGE_LEFT,
),
{0},
};
static const lochara_combat_action_t combo2_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_*2,
.state = LOCHARA_STATE_TELEPORT_FRONT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .8f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = BEAT_MS_/4,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .6f,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_/2,
.state = LOCHARA_STATE_DODGE_RIGHT,
),
{0},
};
static const lochara_combat_action_t shoot1_[] = {
lochara_combat_action_rest(
.duration = 1,
.state = LOCHARA_STATE_JUMP,
),
lochara_combat_action_rest(
.duration = BEAT_MS_-1,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_SHOOT,
),
lochara_combat_action_rest(
.duration = BEAT_MS_*2,
.state = LOCHARA_STATE_TELEPORT_BEHIND,
),
{0},
};
static const lochara_combat_action_t down_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_,
.state = LOCHARA_STATE_DOWN,
),
lochara_combat_action_rest(
.duration = BEAT_MS_*3,
.state = LOCHARA_STATE_REVIVE,
),
{0},
};
static const lochara_combat_action_t kill_[] = {
lochara_combat_action_rest(
.duration = BEAT_MS_*12,
.state = LOCHARA_STATE_DEAD,
),
{0},
};
static const lochara_combat_action_t dead_[] = {
lochara_combat_action_rest(
.duration = 30000,
.state = LOCHARA_STATE_DEAD,
),
{0},
};
static const statman_meta_t strategy_table_[] = {
{
.state = LOCHARA_STRATEGY_WAIT,
.name = "WAIT",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_theists_child_update_wait_strategy_,
},
{
.state = LOCHARA_STRATEGY_WAKE_UP,
.name = "WAKE_UP",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_theists_child_update_wake_up_strategy_,
},
{
.state = LOCHARA_STRATEGY_WAKE_UP_EVENT,
.name = "WAKE_UP_EVENT",
.initialize = lochara_theists_child_initialize_wake_up_strategy_,
.update = lochara_theists_child_update_wake_up_strategy_,
},
{
.state = LOCHARA_STRATEGY_APPROACH,
.name = "APPROACH",
.initialize = lochara_strategy_initialize_any_,
.update = lochara_theists_child_update_approach_strategy_,
},
lochara_strategy_combat(COMBO1,
.state_table = state_table_,
.actions = combo1_,
.parry_window = 100,
.parried_next = LOCHARA_STRATEGY_DOWN,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(COMBO2,
.state_table = state_table_,
.actions = combo2_,
.parry_window = 100,
.parried_next = LOCHARA_STRATEGY_DOWN,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(SHOOT1,
.state_table = state_table_,
.actions = shoot1_,
.parry_window = 200,
.parried_next = LOCHARA_STRATEGY_DOWN,
.gravity = true,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(DOWN,
.state_table = state_table_,
.actions = down_,
.gravity = true,
.next = LOCHARA_STRATEGY_APPROACH,
),
lochara_strategy_combat(KILL,
.state_table = state_table_,
.actions = kill_,
.gravity = true,
.next = LOCHARA_STRATEGY_WAIT,
),
lochara_strategy_combat(DEAD,
.state_table = state_table_,
.actions = dead_,
.gravity = true,
.next = LOCHARA_STRATEGY_WAIT,
),
{0},
};
static void lochara_theists_child_update_event_(lochara_base_t* base) {
assert(base != NULL);
static const loplayer_event_command_t wake_up[] = {
loplayer_event_command_play_music(LORESOURCE_MUSIC_ID_BOSS_THEISTS_CHILD),
loplayer_event_command_set_area(.49f, .45f),
loplayer_event_command_set_cinescope(1),
loplayer_event_command_wait(BEAT_MS_*16),
loplayer_event_command_set_line("boss_theists_child_line0"),
loplayer_event_command_wait(BEAT_MS_*16),
loplayer_event_command_set_line("boss_theists_child_line1"),
loplayer_event_command_wait(BEAT_MS_*16),
loplayer_event_command_set_line("boss_theists_child_line2"),
loplayer_event_command_wait(BEAT_MS_*15),
loplayer_event_command_set_line(NULL),
loplayer_event_command_set_cinescope(0),
{0},
};
static const loplayer_event_command_t kill[] = {
loplayer_event_command_set_area(0, 0),
loplayer_event_command_set_cinescope(1),
loplayer_event_command_wait(BEAT_MS_),
loplayer_event_command_stop_music(),
loplayer_event_command_set_line("boss_theists_child_kill_line0"),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_set_line("boss_theists_child_kill_line1"),
loplayer_event_command_wait(BEAT_MS_*4),
loplayer_event_command_finalize(),
{0},
};
static const loplayer_event_command_t dead[] = {
loplayer_event_command_set_area(0, 0),
loplayer_event_command_wait(BEAT_MS_),
loplayer_event_command_stop_music(),
loplayer_event_command_set_line("boss_theists_child_dead_line"),
loplayer_event_command_wait(BEAT_MS_*8),
loplayer_event_command_finalize(),
{0},
};
const uint64_t t = base->ticker->time;
loplayer_event_t* event = &base->player->event;
const loentity_id_t id = base->super.super.id;
locommon_position_t basepos = base->super.super.pos;
basepos.fract = vec2(.5f, .5f);
if (base->param.strategy == LOCHARA_STRATEGY_WAKE_UP_EVENT) {
loplayer_event_execute_commands(event, id, &basepos, wake_up);
return;
}
if (event->executor != id) return;
if (base->player->entity->param.state == LOCHARA_STATE_DEAD) {
statman_transition_to(
strategy_table_, base, &base->param.strategy, LOCHARA_STRATEGY_KILL);
loplayer_event_execute_commands(event, id, &basepos, kill);
return;
}
if (base->param.strategy == LOCHARA_STRATEGY_DEAD) {
loplayer_event_execute_commands(event, id, &basepos, dead);
if (!loplayer_stance_set_has(&base->player->stances, REWARD_STANCE_)) {
loplayer_stance_set_add(&base->player->stances, REWARD_STANCE_);
loplayer_popup_queue_new_stance(&base->player->popup, REWARD_STANCE_);
}
return;
}
if (base->player->event.basetime+MUSIC_DURATION_ <= t &&
loeffect_recipient_is_alive(&base->param.recipient)) {
loentity_character_apply_effect(
&base->player->entity->super, &loeffect_curse_trigger());
return;
}
loplayer_event_execute_commands(event, id, &basepos, wake_up);
}
bool lochara_theists_child_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
statman_update(strategy_table_, base, &base->param.strategy);
lochara_theists_child_update_event_(base);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_CAVIA,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
if (base->param.strategy != LOCHARA_STRATEGY_WAIT &&
base->param.strategy != LOCHARA_STRATEGY_WAKE_UP &&
base->param.strategy != LOCHARA_STRATEGY_WAKE_UP_EVENT &&
base->param.strategy != LOCHARA_STRATEGY_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
lochara_base_bind_on_ground(base, &vec2(WIDTH_, HEIGHT_+MARKER_));
return true;
}
void lochara_theists_child_build(
lochara_base_t* base, loentity_ground_t* gnd) {
assert(base != NULL);
assert(gnd != NULL);
base->super.super.pos = gnd->super.pos;
vec2_addeq(&base->super.super.pos.fract, &vec2(0, gnd->size.y));
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_THEISTS_CHILD,
.state = LOCHARA_STATE_DEAD,
.last_state_changed = base->ticker->time,
.strategy = LOCHARA_STRATEGY_WAIT,
.last_strategy_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_theists_child_update(
lochara_base_t* base
);
void
lochara_theists_child_build(
lochara_base_t* base,
loentity_ground_t* gnd
);

18
core/lochara/type.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
typedef enum {
/* BENUM BEGIN lochara_type */
LOCHARA_TYPE_PLAYER,
LOCHARA_TYPE_ENCEPHALON,
LOCHARA_TYPE_CAVIA,
LOCHARA_TYPE_WARDER,
LOCHARA_TYPE_BIG_WARDER,
LOCHARA_TYPE_THEISTS_CHILD,
/* BENUM END*/
} lochara_type_t;
/* generated benum utils */
#include "core/lochara/benum/type.h"

370
core/lochara/warder.c Normal file
View File

@@ -0,0 +1,370 @@
#include "./warder.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/statman/statman.h"
#include "core/lobullet/base.h"
#include "core/lobullet/linear.h"
#include "core/loeffect/effect.h"
#include "core/loeffect/recipient.h"
#include "core/loentity/ground.h"
#include "core/loresource/sound.h"
#include "core/loshader/character.h"
#include "./base.h"
#include "./combat.h"
#define WIDTH_ .02f
#define HEIGHT_ .055f
#define MARKER_ .022f
#define COLOR_ vec4(0, 0, 0, 1)
#define BULLET_DAMAGE_ .9f
#define BULLET_KNOCKBACK_ 6
#define BULLET_DURATION_ 2000
#define BULLET_SPEED_ .6f
#define BULLET_COLOR_ vec4(0, 0, 0, 1)
#define BULLET_SIZE_ vec2(.03f, .03f)
#define SHOOT_DURATION_ 2000
static const loeffect_recipient_status_t base_status_ = {
.attack = .2f,
.defence = .05f,
.speed = .05f,
.jump = 1.f,
};
static void lochara_warder_update_thrust_in_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = 100;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
static void lochara_warder_update_thrust_out_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static const uint64_t dur = 100;
lochara_base_t* base = instance;
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
const float tf = t*1.f / dur;
base->cache.instance.motion.time = powf(tf, 2);
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
}
static void lochara_warder_update_shoot_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_base_t* base = instance;
const uint64_t since = base->param.last_state_changed;
uint64_t t = base->ticker->time - since;
if (t > SHOOT_DURATION_) t = SHOOT_DURATION_;
const uint64_t pt = t == 0? 0: base->ticker->prev_time - since;
if (pt < SHOOT_DURATION_/2 && SHOOT_DURATION_/2 <= t) {
vec2_t v = base->param.direction;
vec2_muleq(&v, BULLET_SPEED_);
lobullet_base_t* b = lobullet_pool_create(base->bullet);
lobullet_linear_circle_build(b,
.owner = base->super.super.id,
.basepos = base->super.super.pos,
.size = BULLET_SIZE_,
.color = BULLET_COLOR_,
.velocity = v,
.knockback = BULLET_KNOCKBACK_,
.effect = loeffect_damage(
base->param.recipient.status.attack*BULLET_DAMAGE_),
.duration = BULLET_DURATION_,);
loentity_store_add(base->entities, &b->super.super);
loresource_sound_set_play(
&base->res->sound, LORESOURCE_SOUND_ID_ENEMY_SHOOT);
}
float tf = t*1.f/SHOOT_DURATION_*2;
if (tf < 1) {
tf *= 2;
if (tf < 1) {
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
} else {
tf -= 1;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
}
base->cache.instance.motion.time = powf(tf, 4);
} else {
tf -= 1;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_ATTACK2;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.time = tf;
}
}
static void lochara_warder_initialize_resuscitate_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
lochara_state_initialize_any_(meta, instance, state);
lochara_base_t* base = instance;
loeffect_recipient_apply_effect(
&base->param.recipient, &loeffect_resuscitate());
}
static void lochara_warder_update_dead_state_(
const statman_meta_t* meta, void* instance, statman_state_t* state) {
assert(meta != NULL);
assert(instance != NULL);
assert(state != NULL);
static uint64_t dur = 1000;
lochara_base_t* base = instance;
base->param.movement = vec2(0, 0);
uint64_t t = base->ticker->time - base->param.last_state_changed;
if (t > dur) t = dur;
if (base->param.state == LOCHARA_STATE_RESUSCITATE) t = dur - t;
base->cache.instance.motion.from = LOSHADER_CHARACTER_MOTION_ID_STAND1;
base->cache.instance.motion.to = LOSHADER_CHARACTER_MOTION_ID_DOWN;
base->cache.instance.motion.time = powf(t*1.f/dur, 6);
}
static const statman_meta_t state_table_[] = {
lochara_state_stand(
.period = 4000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
),
lochara_state_walk(
.period = 1000,
.acceleration = {{5, 5}},
.motion1 = LOSHADER_CHARACTER_MOTION_ID_STAND1,
.motion2 = LOSHADER_CHARACTER_MOTION_ID_WALK,
),
{
.state = LOCHARA_STATE_THRUST_IN,
.name = "THRUST_IN",
.initialize = lochara_state_initialize_any_,
.update = lochara_warder_update_thrust_in_state_,
},
{
.state = LOCHARA_STATE_THRUST_OUT,
.name = "THRUST_OUT",
.initialize = lochara_state_initialize_any_,
.update = lochara_warder_update_thrust_out_state_,
},
{
.state = LOCHARA_STATE_SHOOT,
.name = "SHOOT",
.initialize = lochara_state_initialize_any_,
.update = lochara_warder_update_shoot_state_,
},
{
.state = LOCHARA_STATE_DEAD,
.name = "DEAD",
.initialize = lochara_state_initialize_any_,
.update = lochara_warder_update_dead_state_,
},
{
.state = LOCHARA_STATE_RESUSCITATE,
.name = "RESUSCITATE",
.initialize = lochara_warder_initialize_resuscitate_state_,
.update = lochara_warder_update_dead_state_,
},
{0},
};
static const lochara_combat_action_t combo1_[] = {
lochara_combat_action_rest(
.duration = 400,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_attack(
.duration = 200,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = 200,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 300,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1,
),
lochara_combat_action_rest(
.duration = 200,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 300,
.state = LOCHARA_STATE_THRUST_IN,
.damage = 1.2f,
),
lochara_combat_action_rest(
.duration = 200,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_attack(
.duration = 300,
.state = LOCHARA_STATE_THRUST_IN,
.damage = .8f,
),
lochara_combat_action_rest(
.duration = 400,
.state = LOCHARA_STATE_THRUST_OUT,
),
lochara_combat_action_rest(
.duration = 600,
.state = LOCHARA_STATE_STAND,
),
{0},
};
static const lochara_combat_action_t shoot1_[] = {
lochara_combat_action_rest(
.duration = 100,
.state = LOCHARA_STATE_STAND,
),
lochara_combat_action_rest(
.duration = SHOOT_DURATION_,
.state = LOCHARA_STATE_SHOOT,
),
lochara_combat_action_rest(
.duration = 500,
.state = LOCHARA_STATE_STAND,
),
{0},
};
static const lochara_combat_action_t dead_[] = {
lochara_combat_action_rest(
.duration = 30000,
.state = LOCHARA_STATE_DEAD,
),
lochara_combat_action_rest(
.duration = 2000,
.state = LOCHARA_STATE_RESUSCITATE,
),
{0},
};
static const statman_meta_t strategy_table_[] = {
lochara_strategy_scouting(
.state_table = state_table_,
.period = 2000,
.stagger = .8f,
.range_back = .5f,
.range_close = WIDTH_*4,
.found_close = LOCHARA_STRATEGY_COMBO1,
.range_mid = .4f,
.found_mid = LOCHARA_STRATEGY_SHOOT1,
),
lochara_strategy_combat(COMBO1,
.state_table = state_table_,
.actions = combo1_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
lochara_strategy_combat(SHOOT1,
.state_table = state_table_,
.actions = shoot1_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
lochara_strategy_combat(DEAD,
.state_table = state_table_,
.actions = dead_,
.next = LOCHARA_STRATEGY_SCOUTING,
),
{0},
};
bool lochara_warder_update(lochara_base_t* base) {
assert(base != NULL);
loeffect_recipient_update(&base->param.recipient, &base_status_);
statman_update(strategy_table_, base, &base->param.strategy);
const float dir = MATH_SIGN_NOZERO(base->param.direction.x);
base->cache.instance = (loshader_character_drawer_instance_t) {
.character_id = LOSHADER_CHARACTER_ID_WARDER,
.pos = vec2(0, -MARKER_),
.size = vec2(dir*HEIGHT_, HEIGHT_),
.color = COLOR_,
.marker_offset = vec2(0, MARKER_),
};
statman_update(state_table_, base, &base->param.state);
if (base->param.state != LOCHARA_STATE_DEAD) {
base->cache.instance.marker = lochara_base_affect_bullets(base);
}
lochara_base_calculate_physics(
base, &vec2(WIDTH_, HEIGHT_), &vec2(0, MARKER_));
lochara_base_bind_on_ground(base, &vec2(WIDTH_, HEIGHT_+MARKER_));
return true;
}
void lochara_warder_build(
lochara_base_t* base, const loentity_ground_t* gnd, float pos) {
assert(base != NULL);
assert(gnd != NULL);
assert(MATH_FLOAT_VALID(pos));
base->super.super.pos = gnd->super.pos;
vec2_addeq(
&base->super.super.pos.fract,
&vec2(gnd->size.x*pos, gnd->size.y));
locommon_position_reduce(&base->super.super.pos);
base->param = (typeof(base->param)) {
.type = LOCHARA_TYPE_WARDER,
.state = LOCHARA_STATE_STAND,
.last_state_changed = base->ticker->time,
.strategy = LOCHARA_STRATEGY_SCOUTING,
.last_strategy_changed = base->ticker->time,
.ground = gnd->super.id,
};
loeffect_recipient_initialize(
&base->param.recipient, base->ticker, &base_status_);
}

19
core/lochara/warder.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
#include "core/loentity/ground.h"
#include "./base.h"
bool
lochara_warder_update(
lochara_base_t* base
);
void
lochara_warder_build(
lochara_base_t* base,
const loentity_ground_t* gnd,
float pos
);