taisei/src/plrmodes/marisa_b.c
2024-05-17 14:11:48 +02:00

564 lines
14 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "marisa.h"
#include "audio/audio.h"
#include "common_tasks.h"
#include "dialog/marisa.h"
#include "global.h"
#include "plrmodes.h"
#include "renderer/api.h"
#include "stagedraw.h"
#include "util/glm.h"
#define SHOT_FORWARD_DAMAGE 60
#define SHOT_FORWARD_DELAY 6
#define SHOT_SLAVE_DAMAGE 50
#define SHOT_SLAVE_DELAY 5
#define BOMB_NUM_ORBITERS 5
#define HAKKERO_RETRACT_TIME 6
typedef struct MarisaBController {
struct {
Sprite *fairy_circle;
Sprite *lightningball;
Sprite *maristar_orbit;
Sprite *stardust;
} sprites;
Player *plr;
cmplx slave_ref_pos; // follows player
struct {
float beams_alpha;
} bomb;
COEVENTS_ARRAY(
slaves_expired
) events;
} MarisaBController;
DEFINE_ENTITY_TYPE(MarisaBSlave, {
Sprite *sprite;
ShaderProgram *shader;
cmplx pos;
uint alive;
});
DEFINE_ENTITY_TYPE(MarisaBOrbiter, {
MarisaBController *ctrl;
Sprite *sprite;
cmplx pos;
cmplx offset;
Color particle_color;
Color circle_color;
});
DEFINE_ENTITY_TYPE(MarisaBBeams, {
BoxedMarisaBOrbiter orbiters[BOMB_NUM_ORBITERS];
int time;
float alpha;
});
static void marisa_star_draw_slave(EntityInterface *ent) {
MarisaBSlave *slave = ENT_CAST(ent, MarisaBSlave);
ShaderCustomParams shader_params = { 0 };
// shader_params.color = *RGBA(0.2, 0.4, 0.5, slave->flare_alpha * 0.75);
float t = global.frames;
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = slave->sprite,
.shader_ptr = slave->shader,
.pos.as_cmplx = slave->pos,
.rotation.angle = t * 0.05f,
// .color = color_lerp(RGB(0.2, 0.4, 0.5), RGB(1.0, 1.0, 1.0), 0.25 * powf(psinf(t / 6.0f), 2.0f) * slave->flare_alpha),
.color = RGB(0.2, 0.4, 0.5),
.shader_params = &shader_params,
});
}
static Color *marisa_star_slave_projectile_color(Color *c, real focus, real brightener) {
static const Color unfocused = { 0.3, 0.8, 1.0, 0.2 };
static const Color focused = { 1.0, 0.8, 0.3, 0.2 };
*c = unfocused;
color_lerp(c, &focused, focus);
return color_add(c, RGBA(brightener, brightener, brightener, brightener));
}
TASK(marisa_star_slave_projectile, {
MarisaBController *ctrl;
BoxedMarisaBSlave slave;
cmplx pos;
cmplx vel;
real damage;
ShaderProgram *shader;
}) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
MarisaBSlave *slave = NOT_NULL(ENT_UNBOX(ARGS.slave));
if(!slave->alive || !player_should_shoot(plr)) {
return;
}
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_maristar,
.pos = ARGS.pos,
.move = move_linear(ARGS.vel),
.type = PROJ_PLAYER,
.damage = ARGS.damage,
.shader_ptr = ARGS.shader,
.max_viewport_dist = 128,
));
real freq = 0.1;
cmplx vel = ARGS.vel;
real focus = !!(plr->inputflags & INFLAG_FOCUS);
cmplx prev_pos = p->pos;
cmplx next_pos = p->pos;
for(int t = 0;; ++t) {
slave = ENT_UNBOX(ARGS.slave);
if(slave == NULL || !slave->alive || !player_should_shoot(plr)) {
break;
}
approach_asymptotic_p(&focus, !!(plr->inputflags & INFLAG_FOCUS), 0.2, 1e-3);
real focusfac = 1;
if(focus < 1) {
focusfac = t * 0.015 - 1 / (1 - focus);
focusfac = tanh(sqrt(fabs(focusfac)));
}
cmplx center = clerp(plr->pos, ctrl->slave_ref_pos, tanh(t / 10.0));
real brightener = -1 / (1 + sqrt(0.03 * fabs(re(p->pos - center))));
marisa_star_slave_projectile_color(&p->color, focus, brightener);
real verticalfac = - 5 * t * (1 + 0.01 * t) + 10 * t / (0.01 * t + 1);
prev_pos = p->pos;
next_pos = center + focusfac * cbrt(0.1 * t) * re(vel) * 70 * sin(freq * t + im(vel)) + verticalfac*I;
p->move.velocity = next_pos - prev_pos;
if(t%(2+(int)round(2*rng_real())) == 0) { // please never write stuff like this ever again
PARTICLE(
.sprite_ptr = ctrl->sprites.stardust,
.pos = next_pos,
.color = RGBA(0.5 * focus, 0, 0.5 * (1 - focus), 0),
.timeout = 5,
.angle = rng_angle(),
.angle_delta = 0.1 * rng_sreal(),
.draw_rule = pdraw_timeout_scalefade(0, 1.4, 1, 0),
.flags = PFLAG_NOREFLECT | PFLAG_MANUALANGLE,
);
}
YIELD;
}
p->move.retention = 1.005;
}
TASK(marisa_star_slave_shot, {
MarisaBController *ctrl;
BoxedMarisaBSlave slave;
real phase;
}) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
MarisaBSlave *slave = TASK_BIND(ARGS.slave);
ShaderProgram *shot_shader = res_shader("sprite_particle");
real nphase = ARGS.phase / M_TAU;
real damage = SHOT_SLAVE_DAMAGE;
int t = 0;
for(;;) {
t += WAIT_EVENT_OR_DIE(&plr->events.shoot).frames;
for(int i = 0; i < 2; ++i) {
cmplx v = (1 - 2 * i);
v *= 1 - 0.9 * nphase;
v -= I * 0.04 * t * (4 - 3 * nphase);
INVOKE_TASK(marisa_star_slave_projectile,
.ctrl = ctrl,
.slave = ENT_BOX(slave),
.pos = slave->pos,
.vel = v,
.damage = damage,
.shader = shot_shader
);
t += WAIT(2);
}
static_assert(SHOT_SLAVE_DELAY >= 4, "SHOT_SLAVE_DELAY is too low");
t += WAIT(SHOT_SLAVE_DELAY - 4);
}
}
TASK(marisa_star_slave, {
MarisaBController *ctrl;
real phase;
}) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
MarisaBSlave *slave = TASK_HOST_ENT(MarisaBSlave);
slave->alive = 1;
slave->pos = plr->pos;
slave->shader = res_shader("sprite_hakkero");
slave->sprite = res_sprite("hakkero");
slave->ent.draw_func = marisa_star_draw_slave;
slave->ent.draw_layer = LAYER_PLAYER_SLAVE;
INVOKE_SUBTASK_WHEN(&ctrl->events.slaves_expired, common_set_bitflags,
.pflags = &slave->alive,
.mask = 0,
.set = 0
);
BoxedTask shot_task = cotask_box(INVOKE_SUBTASK(marisa_star_slave_shot,
.ctrl = ctrl,
.slave = ENT_BOX(slave),
.phase = ARGS.phase
));
real angle_step = 0.05;
real angle = angle_step * global.frames + ARGS.phase;
for(int t = 0; slave->alive; t += WAIT(1)) {
cmplx target_pos = ctrl->slave_ref_pos + 80 * sin(angle) + 45*I;
slave->pos = clerp(plr->pos, target_pos, glm_ease_quad_out(min(1, (real)t/HAKKERO_RETRACT_TIME)));
slave->ent.draw_layer = cos(angle) < 0 ? LAYER_BACKGROUND : LAYER_PLAYER_SLAVE;
angle += angle_step;
}
CANCEL_TASK(shot_task);
plrutil_slave_retract(ENT_BOX(plr), &slave->pos, HAKKERO_RETRACT_TIME);
}
static void marisa_star_respawn_slaves(MarisaBController *ctrl, int numslaves) {
Player *plr = ctrl->plr;
coevent_signal(&ctrl->events.slaves_expired);
ctrl->slave_ref_pos = plr->pos;
for(int i = 0; i < numslaves; i++) {
INVOKE_TASK(marisa_star_slave,
.ctrl = ctrl,
.phase = M_TAU * i / numslaves
);
}
}
TASK(marisa_star_power_handler, { MarisaBController *ctrl; }) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
int old_power = player_get_effective_power(plr) / 100;
marisa_star_respawn_slaves(ctrl, old_power);
for(;;) {
WAIT_EVENT_OR_DIE(&plr->events.effective_power_changed);
int new_power = player_get_effective_power(plr) / 100;
if(old_power != new_power) {
marisa_star_respawn_slaves(ctrl, new_power);
old_power = new_power;
}
}
}
static void marisa_star_draw_orbiter(EntityInterface *ent) {
MarisaBOrbiter *orbiter = ENT_CAST(ent, MarisaBOrbiter);
MarisaBController *ctrl = orbiter->ctrl;
SpriteParams sp = { 0 };
sp.pos.as_cmplx = orbiter->pos;
sp.color = color_mul_scalar(COLOR_COPY(&orbiter->circle_color), ctrl->bomb.beams_alpha);
sp.rotation.angle = global.frames * 10 * DEG2RAD;
sp.sprite_ptr = ctrl->sprites.fairy_circle;
r_draw_sprite(&sp);
sp.sprite_ptr = ctrl->sprites.lightningball;
sp.scale.both = 0.6;
r_draw_sprite(&sp);
}
static void marisa_star_draw_beams(EntityInterface *ent) {
MarisaBBeams *beams_ent = ENT_CAST(ent, MarisaBBeams);
MarisaBeamInfo beams[BOMB_NUM_ORBITERS], *pbeam = beams;
float a = beams_ent->alpha;
for(int i = 0; i < ARRAY_SIZE(beams_ent->orbiters); i++) {
MarisaBOrbiter *orbiter = ENT_UNBOX(beams_ent->orbiters[i]);
if(orbiter == NULL) {
continue;
}
pbeam->origin = orbiter->pos;
pbeam->size = 250 * a + VIEWPORT_H * 1.5 * I;
pbeam->angle = carg(orbiter->offset) + M_PI/2;
pbeam->t = beams_ent->time;
++pbeam;
}
marisa_common_masterspark_draw(pbeam - beams, beams, a);
}
TASK(marisa_star_orbiter_stars, { MarisaBController *ctrl; BoxedMarisaBOrbiter orbiter; Color *color; }) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
MarisaBOrbiter *orbiter = TASK_BIND(ARGS.orbiter);
for(int t = 0; t < 300; t += WAIT(10 - t / 30)) {
cmplx vel = -5 * cnormalize(orbiter->pos - plr->pos);
PARTICLE(
.sprite_ptr = ctrl->sprites.maristar_orbit,
.pos = orbiter->pos,
.color = ARGS.color,
.draw_rule = pdraw_timeout_scalefade(0, 6, 1, 0),
.timeout = 150,
.flags = PFLAG_NOREFLECT | PFLAG_MANUALANGLE,
.angle = t,
.angle_delta = 0.1,
.move = move_accelerated(vel, cnormalize(vel) * 0.15),
);
}
}
TASK(marisa_star_orbiter, { MarisaBController *ctrl; cmplx dir; real hue; BoxedMarisaBOrbiter *out_ref; }) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
MarisaBOrbiter *orbiter = TASK_HOST_ENT(MarisaBOrbiter);
orbiter->ctrl = ctrl;
orbiter->particle_color = *HSLA(ARGS.hue, 1, 0.6, 1);
orbiter->circle_color = *HSLA(ARGS.hue, 0.9, 0.5, 0);
orbiter->ent.draw_layer = LAYER_PLAYER_FOCUS - 1;
orbiter->ent.draw_func = marisa_star_draw_orbiter;
*ARGS.out_ref = ENT_BOX(orbiter);
Color pcolor;
BoxedTask stars_task = cotask_box(INVOKE_SUBTASK_DELAYED(1, marisa_star_orbiter_stars,
.ctrl = ctrl,
.orbiter = ENT_BOX(orbiter),
.color = &pcolor
));
for(int t = 0;; t += WAIT(1)) {
pcolor = orbiter->particle_color;
real r = 100 * pow(tanh(t / 20.0), 2);
orbiter->offset = ARGS.dir * r * cdir(sqrt(1000 + (t * t) * (1 + 0.03 * t)) * 0.04);
orbiter->pos = plr->pos + orbiter->offset;
real tb = player_get_bomb_progress(plr);
real fadetime = 3.0 / 4.0;
if(tb >= fadetime) {
pcolor.a = 1 - (tb - fadetime) / (1 - fadetime);
CANCEL_TASK(stars_task);
stars_task = (BoxedTask) { 0 };
}
color_mul_alpha(&pcolor);
pcolor.a = 0;
PARTICLE(
.sprite_ptr = ctrl->sprites.maristar_orbit,
.pos = orbiter->pos,
.color = color_mul_scalar(COLOR_COPY(&pcolor), 0.5),
.timeout = 10,
.angle = t * 0.1,
.draw_rule = pdraw_timeout_scalefade(0, 1 + 4 * tb, 1, 0),
.flags = PFLAG_NOREFLECT | PFLAG_MANUALANGLE,
);
}
}
TASK(marisa_star_bomb_background, { MarisaBController *ctrl; }) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
ShaderProgram *bg_shader = res_shader("maristar_bombbg");
Uniform *u_t = r_shader_uniform(bg_shader, "t");
Uniform *u_decay = r_shader_uniform(bg_shader, "decay");
Uniform *u_plrpos = r_shader_uniform(bg_shader, "plrpos");
Texture *bg_tex = res_texture("marisa_bombbg");
for(;;) {
WAIT_EVENT_OR_DIE(&stage_get_draw_events()->background_drawn);
r_state_push();
r_shader_ptr(bg_shader);
r_uniform_float(u_t, player_get_bomb_progress(plr));
r_uniform_float(u_decay, 1);
r_uniform_vec2_complex(u_plrpos, cwmul(plr->pos, CMPLX(1.0/VIEWPORT_W, 1.0/VIEWPORT_H)));
fill_viewport_p(0, 0, 1, 1, 0, bg_tex);
r_state_pop();
}
}
TASK(marisa_star_bomb, { MarisaBController *ctrl; }) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
INVOKE_SUBTASK(marisa_star_bomb_background, ctrl);
YIELD;
MarisaBBeams *beams = TASK_HOST_ENT(MarisaBBeams);
beams->ent.draw_layer = LAYER_PLAYER_FOCUS - 1;
beams->ent.draw_func = marisa_star_draw_beams;
for(int i = 0; i < BOMB_NUM_ORBITERS; ++i) {
INVOKE_SUBTASK(marisa_star_orbiter,
.ctrl = ctrl,
.dir = cdir(i * M_TAU / BOMB_NUM_ORBITERS),
.hue = i / (real)BOMB_NUM_ORBITERS,
.out_ref = beams->orbiters + i
);
}
do {
stage_shake_view(8);
player_placeholder_bomb_logic(plr);
float tb = player_get_bomb_progress(plr);
float a = 1;
if(tb < 1.0f / 6.0f) {
a = tb * 6.0f;
a = sqrtf(a);
}
if(tb > 3.0f / 4.0f) {
a = 1.0f - tb * 4.0f + 3.0f;
a *= a;
}
ctrl->bomb.beams_alpha = a;
beams->alpha = a;
++beams->time;
YIELD;
} while(player_is_bomb_active(plr));
}
TASK(marisa_star_bomb_handler, { MarisaBController *ctrl; }) {
MarisaBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
BoxedTask bomb_task = { 0 };
for(;;) {
WAIT_EVENT_OR_DIE(&plr->events.bomb_used);
CANCEL_TASK(bomb_task);
play_sfx("bomb_marisa_b");
bomb_task = cotask_box(INVOKE_SUBTASK(marisa_star_bomb, ctrl));
}
}
TASK(marisa_star_controller, { BoxedPlayer plr; }) {
MarisaBController *ctrl = TASK_MALLOC(sizeof(*ctrl));
ctrl->plr = TASK_BIND(ARGS.plr);
TASK_HOST_EVENTS(ctrl->events);
ctrl->sprites.fairy_circle = res_sprite("fairy_circle");
ctrl->sprites.lightningball = res_sprite("part/lightningball");
ctrl->sprites.maristar_orbit = res_sprite("part/maristar_orbit");
ctrl->sprites.stardust = res_sprite("part/stardust");
INVOKE_SUBTASK(marisa_star_power_handler, ctrl);
INVOKE_SUBTASK(marisa_star_bomb_handler, ctrl);
INVOKE_SUBTASK(marisa_common_shot_forward, ARGS.plr, SHOT_FORWARD_DAMAGE, SHOT_FORWARD_DELAY);
ctrl->slave_ref_pos = ctrl->plr->pos;
for(;;) {
cmplx pdelta = ctrl->plr->pos - ctrl->slave_ref_pos;
ctrl->slave_ref_pos += cclampabs(pdelta, 2 + 2 * !(ctrl->plr->inputflags & INFLAG_FOCUS));
YIELD;
}
}
static void marisa_star_init(Player *plr) {
INVOKE_TASK(marisa_star_controller, { ENT_BOX(plr) });
}
static double marisa_star_property(Player *plr, PlrProperty prop) {
switch(prop) {
case PLR_PROP_SPEED: {
double s = marisa_common_property(plr, prop);
if(player_is_bomb_active(plr)) {
s /= 4.0;
}
return s;
}
default:
return marisa_common_property(plr, prop);
}
}
static void marisa_star_preload(ResourceGroup *rg) {
const int flags = RESF_DEFAULT;
res_group_preload(rg, RES_SPRITE, flags,
"hakkero",
"masterspark_ring",
"part/maristar_orbit",
"part/stardust",
"proj/marisa",
"proj/maristar",
NULL);
res_group_preload(rg, RES_TEXTURE, flags,
"marisa_bombbg",
NULL);
res_group_preload(rg, RES_SHADER_PROGRAM, flags,
"masterspark",
"maristar_bombbg",
"sprite_hakkero",
NULL);
res_group_preload(rg, RES_SFX, flags | RESF_OPTIONAL,
"bomb_marisa_b",
NULL);
}
PlayerMode plrmode_marisa_b = {
.name = "Stellar Vortex",
.description = "As many bullets as there are stars in the sky. Some of them are bound to hit. That's called “homing”, right?",
.spellcard_name = "Magic Sign “Stellar Vortex”",
.character = &character_marisa,
.dialog = &dialog_tasks_marisa,
.shot_mode = PLR_SHOT_MARISA_STAR,
.procs = {
.init = marisa_star_init,
.preload = marisa_star_preload,
.property = marisa_star_property,
},
};