2018-08-11 21:13:48 +02:00
|
|
|
/*
|
2019-08-03 19:43:48 +02:00
|
|
|
* This software is licensed under the terms of the MIT License.
|
2018-08-11 21:13:48 +02:00
|
|
|
* See COPYING for further information.
|
|
|
|
* ---
|
2024-05-16 23:30:41 +02:00
|
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
2018-08-11 21:13:48 +02:00
|
|
|
*/
|
|
|
|
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "reimu.h"
|
|
|
|
|
|
|
|
#include "audio/audio.h"
|
|
|
|
#include "common_tasks.h"
|
|
|
|
#include "dialog/reimu.h"
|
2018-08-11 21:13:48 +02:00
|
|
|
#include "global.h"
|
|
|
|
#include "plrmodes.h"
|
|
|
|
#include "stagedraw.h"
|
2024-05-17 04:41:28 +02:00
|
|
|
#include "util/graphics.h"
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2023-06-01 02:59:26 +02:00
|
|
|
#define SHOT_FORWARD_DMG 35
|
2020-03-25 07:52:18 +01:00
|
|
|
#define SHOT_FORWARD_DELAY 6
|
|
|
|
|
2023-06-01 02:59:26 +02:00
|
|
|
#define SHOT_SLAVE_DMG 44
|
2020-03-25 07:52:18 +01:00
|
|
|
#define SHOT_SLAVE_PRE_DELAY 3
|
|
|
|
#define SHOT_SLAVE_POST_DELAY 3
|
|
|
|
|
|
|
|
#define BOMB_PROJECTILE_DAMAGE 75
|
|
|
|
#define BOMB_PROJECTILE_FIRE_DELAY 4
|
|
|
|
#define BOMB_PROJECTILE_PERIODIC_AREA_DAMAGE 50
|
|
|
|
#define BOMB_PROJECTILE_PERIODIC_AREA_RADIUS (GAP_LENGTH * 0.5)
|
|
|
|
#define BOMB_PROJECTILE_PERIODIC_DELAY 3
|
|
|
|
#define BOMB_PROJECTILE_IMPACT_AREA_DAMAGE 50
|
|
|
|
#define BOMB_PROJECTILE_IMPACT_AREA_RADIUS GAP_LENGTH
|
|
|
|
|
|
|
|
#define ORB_RETRACT_TIME 4
|
|
|
|
|
2018-08-11 21:13:48 +02:00
|
|
|
#define GAP_LENGTH 128
|
|
|
|
#define GAP_WIDTH 16
|
|
|
|
#define GAP_OFFSET 8
|
|
|
|
#define GAP_LIMIT 10
|
|
|
|
|
|
|
|
// #define GAP_LENGTH 160
|
|
|
|
// #define GAP_WIDTH 160
|
|
|
|
// #define GAP_OFFSET 82
|
|
|
|
|
|
|
|
#define NUM_GAPS 4
|
|
|
|
|
2020-04-17 09:18:53 +02:00
|
|
|
typedef struct ReimuBGap ReimuBGap;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-04-17 09:18:53 +02:00
|
|
|
struct ReimuBGap {
|
|
|
|
ReimuBGap *link; // bullets entering this gap will exit from the linked gap
|
2020-03-25 07:52:18 +01:00
|
|
|
cmplx pos; // position of the gap's center in viewport space
|
|
|
|
cmplx orientation; // normalized, points to the side the gap is 'attached' to
|
|
|
|
cmplx parallel_axis; // normalized, parallel the gap, perpendicular to orientation
|
|
|
|
};
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-04-17 09:18:53 +02:00
|
|
|
DEFINE_ENTITY_TYPE(ReimuBSlave, {
|
2020-04-06 03:29:11 +02:00
|
|
|
Sprite *sprite;
|
|
|
|
ShaderProgram *shader;
|
|
|
|
cmplx pos;
|
|
|
|
uint alive;
|
2020-04-17 09:18:53 +02:00
|
|
|
});
|
2020-04-06 03:29:11 +02:00
|
|
|
|
2020-04-17 09:18:53 +02:00
|
|
|
DEFINE_ENTITY_TYPE(ReimuBController, {
|
2020-03-25 07:52:18 +01:00
|
|
|
Player *plr;
|
|
|
|
ShaderProgram *yinyang_shader;
|
|
|
|
Sprite *yinyang_sprite;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
union {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBGap array[NUM_GAPS];
|
2020-03-25 07:52:18 +01:00
|
|
|
struct {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBGap left, top, right, bottom;
|
2020-03-25 07:52:18 +01:00
|
|
|
};
|
|
|
|
} gaps;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
COEVENTS_ARRAY(
|
|
|
|
slaves_expired
|
|
|
|
) events;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
real bomb_alpha;
|
2020-04-17 09:18:53 +02:00
|
|
|
});
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
static void reimu_dream_gap_bomb_projectile_draw(Projectile *p, int t, ProjDrawRuleArgs args) {
|
|
|
|
SpriteParamsBuffer spbuf;
|
|
|
|
SpriteParams sp = projectile_sprite_params(p, &spbuf);
|
|
|
|
sp.scale.as_cmplx = 0.75 * clamp(t / 5.0, 0.1, 1.0) * (1 + I);
|
|
|
|
r_draw_sprite(&sp);
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
TASK(reimu_dream_gap_bomb_projectile_impact, { BoxedProjectile p; Sprite *impact_sprite; }) {
|
|
|
|
Projectile *p = TASK_BIND(ARGS.p);
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2021-02-18 18:03:47 +01:00
|
|
|
if(p->collision && p->collision->type == PCOL_VOID) {
|
|
|
|
// Auto-removed out of bounds; don't explode.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
real range = BOMB_PROJECTILE_IMPACT_AREA_RADIUS;
|
|
|
|
real damage = BOMB_PROJECTILE_IMPACT_AREA_DAMAGE;
|
2019-01-10 01:40:45 +01:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
PARTICLE(
|
|
|
|
.angle = rng_angle(),
|
|
|
|
.color = &p->color,
|
|
|
|
.draw_rule = pdraw_timeout_scalefade(0, 3 * range / ARGS.impact_sprite->w, 1, 0),
|
|
|
|
.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE | PFLAG_MANUALANGLE,
|
|
|
|
.layer = LAYER_BOSS + 2,
|
|
|
|
.pos = p->pos,
|
|
|
|
.sprite_ptr = ARGS.impact_sprite,
|
|
|
|
.timeout = 20,
|
|
|
|
);
|
2019-01-10 01:40:45 +01:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
stage_clear_hazards_at(p->pos, range, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
|
|
|
|
ent_area_damage(p->pos, range, &(DamageInfo) { damage, DMG_PLAYER_BOMB }, NULL, NULL);
|
|
|
|
}
|
2019-01-10 01:40:45 +01:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
TASK(reimu_dream_gap_bomb_projectile, {
|
|
|
|
cmplx pos;
|
|
|
|
cmplx vel;
|
|
|
|
const Color *color;
|
|
|
|
Sprite *sprite;
|
|
|
|
Sprite *impact_sprite;
|
|
|
|
}) {
|
2020-04-17 09:18:53 +02:00
|
|
|
Projectile *p = TASK_BIND(PROJECTILE(
|
2020-03-25 07:52:18 +01:00
|
|
|
.angle = rng_angle(),
|
|
|
|
.color = ARGS.color,
|
|
|
|
.damage = 75,
|
|
|
|
.damage_type = DMG_PLAYER_BOMB,
|
|
|
|
.draw_rule = reimu_dream_gap_bomb_projectile_draw,
|
|
|
|
.flags = PFLAG_MANUALANGLE,
|
|
|
|
.move = move_linear(ARGS.vel),
|
|
|
|
.pos = ARGS.pos,
|
|
|
|
.size = 32 * (1 + I),
|
|
|
|
.sprite_ptr = ARGS.sprite,
|
|
|
|
.type = PROJ_PLAYER,
|
2021-02-18 18:03:47 +01:00
|
|
|
.max_viewport_dist = BOMB_PROJECTILE_PERIODIC_AREA_RADIUS
|
2020-03-25 07:52:18 +01:00
|
|
|
));
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
INVOKE_TASK_WHEN(&p->events.killed, reimu_dream_gap_bomb_projectile_impact,
|
|
|
|
ENT_BOX(p),
|
|
|
|
ARGS.impact_sprite
|
|
|
|
);
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
for(;;) {
|
|
|
|
WAIT(BOMB_PROJECTILE_PERIODIC_DELAY);
|
|
|
|
real range = BOMB_PROJECTILE_PERIODIC_AREA_RADIUS;
|
|
|
|
real damage = BOMB_PROJECTILE_PERIODIC_AREA_DAMAGE;
|
2018-08-11 21:13:48 +02:00
|
|
|
stage_clear_hazards_at(p->pos, range, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
|
2019-01-10 00:56:33 +01:00
|
|
|
ent_area_damage(p->pos, range, &(DamageInfo) { damage, DMG_PLAYER_BOMB }, NULL, NULL);
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-12 22:31:06 +02:00
|
|
|
TASK(reimu_dream_bomb_noise) {
|
2021-06-10 11:57:45 +02:00
|
|
|
for(;;WAIT(16)) {
|
2020-06-22 16:41:03 +02:00
|
|
|
play_sfx("boon");
|
2021-06-10 11:57:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TASK(reimu_dream_bomb_background, { ReimuBController *ctrl; }) {
|
|
|
|
ReimuBController *ctrl = ARGS.ctrl;
|
|
|
|
CoEvent *draw_event = &stage_get_draw_events()->background_drawn;
|
|
|
|
|
|
|
|
for(;;) {
|
|
|
|
WAIT_EVENT_OR_DIE(draw_event);
|
|
|
|
reimu_common_bomb_bg(ctrl->plr, ctrl->bomb_alpha);
|
|
|
|
}
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2021-06-10 11:57:45 +02:00
|
|
|
TASK(reimu_dream_bomb, { ReimuBController *ctrl; }) {
|
2020-03-25 07:52:18 +01:00
|
|
|
ReimuBController *ctrl = ARGS.ctrl;
|
|
|
|
Player *plr = ctrl->plr;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2021-06-10 11:57:45 +02:00
|
|
|
BoxedTask noise_task = cotask_box(INVOKE_SUBTASK(reimu_dream_bomb_noise));
|
|
|
|
INVOKE_SUBTASK(reimu_dream_bomb_background, ctrl);
|
|
|
|
|
2020-06-09 03:33:22 +02:00
|
|
|
Sprite *spr_proj = res_sprite("proj/glowball");
|
|
|
|
Sprite *spr_impact = res_sprite("part/blast");
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2021-06-10 11:57:45 +02:00
|
|
|
YIELD;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2021-06-10 11:57:45 +02:00
|
|
|
int t = 0;
|
2020-03-25 07:52:18 +01:00
|
|
|
do {
|
|
|
|
Color *pcolor = HSLA(t/30.0, 0.5, 0.5, 0.5);
|
|
|
|
|
|
|
|
for(int i = 0; i < NUM_GAPS; ++i) {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBGap *gap = ctrl->gaps.array + i;
|
2020-03-25 07:52:18 +01:00
|
|
|
INVOKE_TASK(reimu_dream_gap_bomb_projectile,
|
|
|
|
.pos = gap->pos + gap->parallel_axis * GAP_LENGTH * 0.25 * rng_sreal(),
|
|
|
|
.vel = -20 * gap->orientation,
|
|
|
|
.color = pcolor,
|
|
|
|
.sprite = spr_proj,
|
|
|
|
.impact_sprite = spr_impact
|
|
|
|
);
|
|
|
|
}
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-05-16 22:42:22 +02:00
|
|
|
stage_shake_view(4);
|
2020-03-25 07:52:18 +01:00
|
|
|
t += WAIT(BOMB_PROJECTILE_FIRE_DELAY);
|
|
|
|
} while(player_is_bomb_active(plr));
|
2019-01-10 01:40:45 +01:00
|
|
|
|
2021-06-10 11:57:45 +02:00
|
|
|
CANCEL_TASK(noise_task);
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2021-06-10 11:57:45 +02:00
|
|
|
while(ctrl->bomb_alpha > 0) {
|
|
|
|
// keep bg renderer alive
|
|
|
|
YIELD;
|
|
|
|
}
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
TASK(reimu_dream_bomb_handler, { ReimuBController *ctrl; }) {
|
|
|
|
ReimuBController *ctrl = ARGS.ctrl;
|
|
|
|
Player *plr = ctrl->plr;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2021-06-10 11:57:45 +02:00
|
|
|
BoxedTask bomb_task = { 0 };
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
for(;;) {
|
|
|
|
WAIT_EVENT_OR_DIE(&plr->events.bomb_used);
|
2021-06-10 11:57:45 +02:00
|
|
|
CANCEL_TASK(bomb_task);
|
2020-06-22 16:41:03 +02:00
|
|
|
play_sfx("bomb_marisa_a");
|
2021-06-10 11:57:45 +02:00
|
|
|
bomb_task = cotask_box(INVOKE_SUBTASK(reimu_dream_bomb, ctrl));
|
2020-03-25 07:52:18 +01:00
|
|
|
}
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
static void reimu_dream_draw_gap_lights(ReimuBController *ctrl, int time, real strength) {
|
2018-08-11 21:13:48 +02:00
|
|
|
if(strength <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
r_shader("reimu_gap_light");
|
2018-09-14 09:37:20 +02:00
|
|
|
r_uniform_sampler("tex", "gaplight");
|
2020-03-25 07:52:18 +01:00
|
|
|
r_uniform_float("time", time / 60.0f);
|
2018-08-11 21:13:48 +02:00
|
|
|
r_uniform_float("strength", strength);
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
real len = GAP_LENGTH * 3 * sqrt(log1p(strength + 1) / 0.693f);
|
|
|
|
|
|
|
|
for(int i = 0; i < NUM_GAPS; ++i) {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBGap *gap = ctrl->gaps.array + i;
|
2020-03-25 07:52:18 +01:00
|
|
|
cmplx center = gap->pos - gap->orientation * (len * 0.5 - GAP_WIDTH * 0.6);
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_push();
|
2023-09-20 04:14:15 +02:00
|
|
|
r_mat_mv_translate(re(center), im(center), 0);
|
2020-03-25 07:52:18 +01:00
|
|
|
r_mat_mv_rotate(carg(gap->orientation) + M_PI, 0, 0, 1);
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_scale(len, GAP_LENGTH, 1);
|
2018-08-11 21:13:48 +02:00
|
|
|
r_draw_quad();
|
2019-10-12 14:02:15 +02:00
|
|
|
r_mat_mv_pop();
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
static void reimu_dream_draw_gaps(EntityInterface *gap_renderer_ent) {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBController *ctrl = ENT_CAST(gap_renderer_ent, ReimuBController);
|
2018-08-11 21:13:48 +02:00
|
|
|
|
|
|
|
float gaps[NUM_GAPS][2];
|
|
|
|
float angles[NUM_GAPS];
|
|
|
|
int links[NUM_GAPS];
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
for(int i = 0; i < NUM_GAPS; ++i) {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBGap *gap = ctrl->gaps.array + i;
|
2023-09-20 04:14:15 +02:00
|
|
|
gaps[i][0] = re(gap->pos);
|
|
|
|
gaps[i][1] = im(gap->pos);
|
2020-03-25 07:52:18 +01:00
|
|
|
angles[i] = -carg(gap->orientation);
|
|
|
|
links[i] = gap->link - ctrl->gaps.array;
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
FBPair *framebuffers = stage_get_fbpair(FBPAIR_FG);
|
2019-08-23 17:29:40 +02:00
|
|
|
bool render_to_fg = r_framebuffer_current() == framebuffers->back;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2019-08-23 17:29:40 +02:00
|
|
|
if(render_to_fg) {
|
|
|
|
fbpair_swap(framebuffers);
|
|
|
|
Framebuffer *target_fb = framebuffers->back;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2019-08-23 17:29:40 +02:00
|
|
|
// This change must propagate
|
|
|
|
r_state_pop();
|
|
|
|
r_framebuffer(target_fb);
|
|
|
|
r_state_push();
|
|
|
|
|
|
|
|
r_shader("reimu_gap");
|
|
|
|
r_uniform_int("draw_background", true);
|
|
|
|
r_blend(BLEND_NONE);
|
2023-04-09 04:00:00 +02:00
|
|
|
r_clear(BUFFER_COLOR, RGBA(0, 0, 0, 0), 1);
|
2019-08-23 17:29:40 +02:00
|
|
|
} else {
|
|
|
|
r_shader("reimu_gap");
|
|
|
|
r_uniform_int("draw_background", false);
|
|
|
|
r_blend(BLEND_PREMUL_ALPHA);
|
|
|
|
}
|
2018-08-11 21:13:48 +02:00
|
|
|
|
|
|
|
r_uniform_vec2("viewport", VIEWPORT_W, VIEWPORT_H);
|
2020-03-25 07:52:18 +01:00
|
|
|
r_uniform_float("time", global.frames / 60.0f);
|
|
|
|
r_uniform_vec2("gap_size", GAP_WIDTH/2.0f, GAP_LENGTH/2.0f);
|
2024-08-22 13:31:53 +02:00
|
|
|
r_uniform_vec2_array("gaps", 0, NUM_GAPS, gaps);
|
|
|
|
r_uniform_float_array("gap_angles", 0, NUM_GAPS, angles);
|
|
|
|
r_uniform_int_array("gap_links", 0, NUM_GAPS, links);
|
2018-08-11 21:13:48 +02:00
|
|
|
draw_framebuffer_tex(framebuffers->front, VIEWPORT_W, VIEWPORT_H);
|
|
|
|
|
2019-08-23 17:29:40 +02:00
|
|
|
r_blend(BLEND_PREMUL_ALPHA);
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
SpriteParams yinyang = {
|
|
|
|
.sprite_ptr = ctrl->yinyang_sprite,
|
|
|
|
.shader_ptr = ctrl->yinyang_shader,
|
|
|
|
.rotation.angle = global.frames * -6 * DEG2RAD,
|
|
|
|
.color = RGB(0.95, 0.75, 1.0),
|
|
|
|
.scale.both = 0.5,
|
|
|
|
};
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
for(int i = 0; i < NUM_GAPS; ++i) {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBGap *gap = ctrl->gaps.array + i;
|
2020-03-25 07:52:18 +01:00
|
|
|
yinyang.pos.as_cmplx = gap->pos + gap->parallel_axis * -0.5 * GAP_LENGTH;
|
|
|
|
r_draw_sprite(&yinyang);
|
|
|
|
yinyang.pos.as_cmplx = gap->pos + gap->parallel_axis * +0.5 * GAP_LENGTH;
|
|
|
|
r_draw_sprite(&yinyang);
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
reimu_dream_draw_gap_lights(ctrl, global.frames, ctrl->bomb_alpha * ctrl->bomb_alpha);
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2019-11-22 04:37:11 +01:00
|
|
|
static void reimu_dream_spawn_warp_effect(cmplx pos, bool exit) {
|
2018-08-11 21:13:48 +02:00
|
|
|
PARTICLE(
|
|
|
|
.sprite = "myon",
|
|
|
|
.pos = pos,
|
|
|
|
.color = RGBA(0.5, 0.5, 0.5, 0.5),
|
|
|
|
.timeout = 20,
|
2019-12-07 02:09:31 +01:00
|
|
|
.angle = rng_angle(),
|
2020-01-06 06:22:44 +01:00
|
|
|
.draw_rule = pdraw_timeout_scalefade(0.2, 1, 1, 0),
|
2018-08-11 21:13:48 +02:00
|
|
|
.layer = LAYER_PLAYER_FOCUS,
|
2023-02-18 14:09:14 +01:00
|
|
|
.flags = PFLAG_MANUALANGLE,
|
2018-08-11 21:13:48 +02:00
|
|
|
);
|
|
|
|
|
2019-12-11 10:25:57 +01:00
|
|
|
Color *clr = color_mul_scalar(RGBA(0.75, rng_range(0, 0.4), 0.4, 0), 0.8-0.4*exit);
|
2018-08-11 21:13:48 +02:00
|
|
|
PARTICLE(
|
|
|
|
.sprite = exit ? "stain" : "stardust",
|
|
|
|
.pos = pos,
|
2019-12-11 10:25:57 +01:00
|
|
|
.color = clr,
|
2018-08-11 21:13:48 +02:00
|
|
|
.timeout = 20,
|
2019-12-07 02:09:31 +01:00
|
|
|
.angle = rng_angle(),
|
2020-01-06 06:22:44 +01:00
|
|
|
.draw_rule = pdraw_timeout_scalefade(0.1, 0.6, 1, 0),
|
2018-08-11 21:13:48 +02:00
|
|
|
.layer = LAYER_PLAYER_FOCUS,
|
2023-02-18 14:09:14 +01:00
|
|
|
.flags = PFLAG_MANUALANGLE,
|
2018-08-11 21:13:48 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
static void reimu_dream_bullet_warp(ReimuBController *ctrl, Projectile *p, int *warp_count) {
|
2020-01-10 05:55:43 +01:00
|
|
|
if(*warp_count < 1) {
|
2018-08-11 21:13:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-09-23 21:56:34 +02:00
|
|
|
real p_long_side = max(re(p->size), im(p->size));
|
2020-03-25 07:52:18 +01:00
|
|
|
cmplx half = 0.25 * (1 + I);
|
2018-08-11 21:13:48 +02:00
|
|
|
Rect p_bbox = { p->pos - p_long_side * half, p->pos + p_long_side * half };
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
for(int i = 0; i < NUM_GAPS; ++i) {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBGap *gap = ctrl->gaps.array + i;
|
2021-06-02 08:15:26 +02:00
|
|
|
real a = carg(-gap->orientation/p->move.velocity);
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
if(fabs(a) < M_TAU/3) {
|
2018-08-11 21:13:48 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-03-12 17:20:25 +01:00
|
|
|
Rect gap_bbox, overlap = { };
|
2020-03-25 07:52:18 +01:00
|
|
|
cmplx gap_size = (GAP_LENGTH + I * GAP_WIDTH) * gap->parallel_axis;
|
2019-11-22 04:37:11 +01:00
|
|
|
cmplx p0 = gap->pos - gap_size * 0.5;
|
|
|
|
cmplx p1 = gap->pos + gap_size * 0.5;
|
2023-09-23 21:56:34 +02:00
|
|
|
gap_bbox.top_left = min(re(p0), re(p1)) + I * min(im(p0), im(p1));
|
|
|
|
gap_bbox.bottom_right = max(re(p0), re(p1)) + I * max(im(p0), im(p1));
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2019-01-06 01:02:44 +01:00
|
|
|
if(rect_rect_intersection(p_bbox, gap_bbox, true, false, &overlap)) {
|
2019-11-22 04:37:11 +01:00
|
|
|
cmplx o = (overlap.top_left + overlap.bottom_right) / 2;
|
2020-03-25 07:52:18 +01:00
|
|
|
real fract;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2023-09-20 04:14:15 +02:00
|
|
|
if(re(gap_size) > im(gap_size)) {
|
|
|
|
fract = 1 - re(o - gap_bbox.top_left) / re(gap_size);
|
2018-08-11 21:13:48 +02:00
|
|
|
} else {
|
2023-09-20 04:14:15 +02:00
|
|
|
fract = 1 - im(o - gap_bbox.top_left) / im(gap_size);
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBGap *ngap = gap->link;
|
2020-03-25 07:52:18 +01:00
|
|
|
o = ngap->pos + ngap->parallel_axis * GAP_LENGTH * (1 - fract - 0.5);
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
reimu_dream_spawn_warp_effect(gap->pos + gap->parallel_axis * GAP_LENGTH * (fract - 0.5), false);
|
2018-08-11 21:13:48 +02:00
|
|
|
reimu_dream_spawn_warp_effect(o, true);
|
|
|
|
|
2021-06-02 08:15:26 +02:00
|
|
|
cmplx new_vel = cabs(p->move.velocity) * -ngap->orientation;
|
2020-01-10 05:55:43 +01:00
|
|
|
|
2021-06-02 08:15:26 +02:00
|
|
|
p->move.acceleration *= cnormalize(new_vel/p->move.velocity);
|
|
|
|
p->move.velocity = new_vel;
|
2020-03-25 07:52:18 +01:00
|
|
|
p->pos = o - p->move.velocity;
|
2018-08-11 21:13:48 +02:00
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
--*warp_count;
|
2020-01-10 05:55:43 +01:00
|
|
|
}
|
|
|
|
}
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2020-04-06 03:29:11 +02:00
|
|
|
static void reimu_dream_draw_slave(EntityInterface *ent) {
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBSlave *slave = ENT_CAST(ent, ReimuBSlave);
|
2020-04-06 03:29:11 +02:00
|
|
|
r_draw_sprite(&(SpriteParams) {
|
|
|
|
.sprite_ptr = slave->sprite,
|
|
|
|
.shader_ptr = slave->shader,
|
|
|
|
.pos.as_cmplx = slave->pos,
|
|
|
|
.rotation.angle = global.frames * -6 * DEG2RAD,
|
|
|
|
.color = RGB(0.95, 0.75, 1.0),
|
|
|
|
.scale.both = 0.5,
|
|
|
|
});
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
TASK(reimu_dream_needle, {
|
|
|
|
ReimuBController *ctrl;
|
|
|
|
cmplx pos;
|
|
|
|
cmplx vel;
|
|
|
|
ShaderProgram *shader;
|
|
|
|
}) {
|
|
|
|
ReimuBController *ctrl = ARGS.ctrl;
|
2020-04-17 09:18:53 +02:00
|
|
|
Projectile *p = TASK_BIND(PROJECTILE(
|
2020-01-06 06:46:23 +01:00
|
|
|
.proto = pp_needle2,
|
|
|
|
.pos = ARGS.pos,
|
|
|
|
.color = RGBA_MUL_ALPHA(1, 1, 1, 0.35),
|
|
|
|
.move = move_linear(ARGS.vel),
|
|
|
|
.type = PROJ_PLAYER,
|
2020-03-25 07:52:18 +01:00
|
|
|
.damage = SHOT_SLAVE_DMG,
|
|
|
|
.shader_ptr = ARGS.shader,
|
2020-01-06 06:46:23 +01:00
|
|
|
));
|
|
|
|
|
2023-06-01 02:13:48 +02:00
|
|
|
Color *trail_color = color_mul_scalar(RGBA(0.75, 0.5, 1, 0), 0.15);
|
2020-01-10 05:55:43 +01:00
|
|
|
int warp_cnt = 1;
|
2020-03-25 07:52:18 +01:00
|
|
|
|
|
|
|
for(;;) {
|
|
|
|
reimu_dream_bullet_warp(ctrl, p, &warp_cnt);
|
2020-01-06 06:46:23 +01:00
|
|
|
|
|
|
|
PARTICLE(
|
|
|
|
.sprite_ptr = p->sprite,
|
|
|
|
.color = trail_color,
|
|
|
|
.timeout = 12,
|
|
|
|
.pos = p->pos,
|
2020-01-10 05:55:43 +01:00
|
|
|
.move = move_linear(p->move.velocity * 0.8),
|
2020-01-06 06:46:23 +01:00
|
|
|
.draw_rule = pdraw_timeout_scalefade(0, 3, 1, 0),
|
|
|
|
.layer = LAYER_PARTICLE_LOW,
|
|
|
|
.flags = PFLAG_NOREFLECT,
|
|
|
|
);
|
|
|
|
|
|
|
|
YIELD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-25 07:52:18 +01:00
|
|
|
TASK(reimu_dream_slave_shot, {
|
|
|
|
ReimuBController *ctrl;
|
2020-04-17 09:18:53 +02:00
|
|
|
BoxedReimuBSlave slave;
|
2020-03-25 07:52:18 +01:00
|
|
|
cmplx vel;
|
|
|
|
}) {
|
|
|
|
ReimuBController *ctrl = ARGS.ctrl;
|
|
|
|
Player *plr = ctrl->plr;
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBSlave *slave = TASK_BIND(ARGS.slave);
|
2020-03-25 07:52:18 +01:00
|
|
|
cmplx vel = ARGS.vel;
|
2020-06-09 03:33:22 +02:00
|
|
|
ShaderProgram *shader = res_shader("sprite_particle");
|
2020-03-25 07:52:18 +01:00
|
|
|
|
|
|
|
for(;;) {
|
|
|
|
WAIT_EVENT_OR_DIE(&plr->events.shoot);
|
|
|
|
WAIT(SHOT_SLAVE_PRE_DELAY);
|
|
|
|
INVOKE_TASK(reimu_dream_needle,
|
|
|
|
.ctrl = ctrl,
|
2020-04-06 03:29:11 +02:00
|
|
|
.pos = slave->pos,
|
2020-03-25 07:52:18 +01:00
|
|
|
.vel = (plr->inputflags & INFLAG_FOCUS) ? cswap(vel) : vel,
|
|
|
|
.shader = shader
|
|
|
|
);
|
|
|
|
WAIT(SHOT_SLAVE_POST_DELAY);
|
2018-08-11 21:13:48 +02:00
|
|
|
}
|
2020-03-25 07:52:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
TASK(reimu_dream_slave, {
|
|
|
|
ReimuBController *ctrl;
|
|
|
|
cmplx offset;
|
|
|
|
real angle_offset;
|
|
|
|
cmplx shot_dir;
|
|
|
|
}) {
|
|
|
|
ReimuBController *ctrl = ARGS.ctrl;
|
|
|
|
Player *plr = ctrl->plr;
|
2020-04-17 09:18:53 +02:00
|
|
|
ReimuBSlave *slave = TASK_HOST_ENT(ReimuBSlave);
|
2020-04-06 03:29:11 +02:00
|
|
|
slave->ent.draw_layer = LAYER_PLAYER_SLAVE;
|
|
|
|
slave->ent.draw_func = reimu_dream_draw_slave;
|
2020-06-09 03:33:22 +02:00
|
|
|
slave->sprite = res_sprite("yinyang"),
|
|
|
|
slave->shader = res_shader("sprite_yinyang"),
|
2020-04-06 03:29:11 +02:00
|
|
|
slave->pos = plr->pos;
|
|
|
|
slave->alive = 1;
|
|
|
|
|
|
|
|
INVOKE_SUBTASK_WHEN(&ctrl->events.slaves_expired, common_set_bitflags,
|
|
|