Coroutinize YoumuB (#215)

This commit is contained in:
Andrei Alexeyev 2020-04-21 20:11:09 +03:00 committed by GitHub
parent eceecaf0c3
commit 2c00cec422
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 408 additions and 316 deletions

View file

@ -50,37 +50,34 @@ double youmu_common_property(Player *plr, PlrProperty prop) {
UNREACHABLE;
}
void youmu_common_shot(Player *plr) {
play_loop("generic_shot");
if(!(global.frames % 6)) {
Color *c = RGB(1, 1, 1);
PROJECTILE(
.proto = pp_youmu,
.pos = plr->pos + 10 - I*20,
.color = c,
.rule = linear,
.args = { -20.0*I },
.type = PROJ_PLAYER,
.damage = 120,
.shader = "sprite_default",
);
PROJECTILE(
.proto = pp_youmu,
.pos = plr->pos - 10 - I*20,
.color = c,
.rule = linear,
.args = { -20.0*I },
.type = PROJ_PLAYER,
.damage = 120,
.shader = "sprite_default",
);
}
Projectile *youmu_common_shot(cmplx pos, MoveParams move, real dmg, ShaderProgram *shader) {
return PROJECTILE(
.damage = dmg,
.layer = LAYER_PLAYER_SHOT | 0x20,
.move = move,
.pos = pos,
.proto = pp_youmu,
.shader_ptr = shader,
.type = PROJ_PLAYER,
);
}
static Framebuffer *bomb_buffer;
void youmu_common_init_bomb_background(YoumuBombBGData *bg_data) {
FBAttachmentConfig cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.attachment = FRAMEBUFFER_ATTACH_COLOR0;
cfg.tex_params.type = TEX_TYPE_RGB;
cfg.tex_params.filter.min = TEX_FILTER_LINEAR;
cfg.tex_params.filter.mag = TEX_FILTER_LINEAR;
cfg.tex_params.wrap.s = TEX_WRAP_MIRROR;
cfg.tex_params.wrap.t = TEX_WRAP_MIRROR;
bg_data->buffer = stage_add_background_framebuffer("Youmu bomb FB", 0.25, 1, 1, &cfg);
bg_data->shader = r_shader_get("youmu_bomb_bg");
bg_data->uniforms.petals = r_shader_uniform(bg_data->shader, "petals");
bg_data->uniforms.time = r_shader_uniform(bg_data->shader, "time");
bg_data->texture = r_texture_get("youmu_bombbg1");
}
static void capture_frame(Framebuffer *dest, Framebuffer *src) {
r_state_push();
@ -92,43 +89,37 @@ static void capture_frame(Framebuffer *dest, Framebuffer *src) {
r_state_pop();
}
void youmu_common_bombbg(Player *plr) {
if(!player_is_bomb_active(plr)) {
return;
}
DEFINE_EXTERN_TASK(youmu_common_bomb_background) {
Player *plr = TASK_BIND(ARGS.plr);
YoumuBombBGData *bg_data = ARGS.bg_data;
CoEvent *draw_event = &stage_get_draw_events()->background_drawn;
float t = player_get_bomb_progress(&global.plr);
float fade = 1;
do {
WAIT_EVENT_OR_DIE(draw_event);
if(t < 1./12)
fade = t*12;
float t = player_get_bomb_progress(&global.plr);
float fade = 1.0f;
if(t > 1./2)
fade = 1-(t-1./2)*2;
if(t < 1.0f / 12.0f) {
fade = t * 12.0f;
}
if(fade < 0)
fade = 0;
if(t > 1.0f / 2.0f) {
fade = 1-(t-1./2)*2;
}
r_state_push();
r_color4(fade, fade, fade, fade);
r_shader("youmu_bomb_bg");
r_uniform_sampler("petals", "youmu_bombbg1");
r_uniform_float("time", t);
if(fade < 0.0f) {
fade = 0.0f;
}
draw_framebuffer_tex(bomb_buffer, VIEWPORT_W, VIEWPORT_H);
r_state_pop();
r_state_push();
r_color4(fade, fade, fade, fade);
r_shader_ptr(bg_data->shader);
r_uniform_sampler(bg_data->uniforms.petals, bg_data->texture);
r_uniform_float(bg_data->uniforms.time, t);
draw_framebuffer_tex(bg_data->buffer, VIEWPORT_W, VIEWPORT_H);
r_state_pop();
capture_frame(bomb_buffer, r_framebuffer_current());
}
void youmu_common_bomb_buffer_init(void) {
FBAttachmentConfig cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.attachment = FRAMEBUFFER_ATTACH_COLOR0;
cfg.tex_params.type = TEX_TYPE_RGB;
cfg.tex_params.filter.min = TEX_FILTER_LINEAR;
cfg.tex_params.filter.mag = TEX_FILTER_LINEAR;
cfg.tex_params.wrap.s = TEX_WRAP_MIRROR;
cfg.tex_params.wrap.t = TEX_WRAP_MIRROR;
bomb_buffer = stage_add_background_framebuffer("Youmu bomb FB", 0.25, 1, 1, &cfg);
capture_frame(bg_data->buffer, r_framebuffer_current());
} while(player_is_bomb_active(plr));
}

View file

@ -18,9 +18,19 @@
extern PlayerCharacter character_youmu;
typedef struct YoumuBombBGData {
Framebuffer *buffer;
ShaderProgram *shader;
struct {
Uniform *petals, *time;
} uniforms;
Texture *texture;
} YoumuBombBGData;
double youmu_common_property(Player *plr, PlrProperty prop);
void youmu_common_shot(Player *plr);
void youmu_common_bombbg(Player *plr);
void youmu_common_bomb_buffer_init(void);
Projectile *youmu_common_shot(cmplx pos, MoveParams move, real dmg, ShaderProgram *shader);
void youmu_common_init_bomb_background(YoumuBombBGData *bg_data);
DECLARE_EXTERN_TASK(youmu_common_bomb_background, { BoxedPlayer plr; YoumuBombBGData *bg_data; });
#endif // IGUARD_plrmodes_youmu_h

View file

@ -45,6 +45,7 @@ struct YoumuAController {
Player *plr;
YoumuAMyon myon;
YoumuBombBGData bomb_bg;
};
static Color *myon_color(Color *c, float f, float opacity, float alpha) {
@ -536,17 +537,6 @@ TASK(youmu_mirror_bomb_postprocess, { YoumuAController *ctrl; }) {
} while(player_is_bomb_active(plr));
}
TASK(youmu_mirror_bomb_background, { YoumuAController *ctrl; }) {
YoumuAController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
CoEvent *draw_event = &stage_get_draw_events()->background_drawn;
do {
WAIT_EVENT_OR_DIE(draw_event);
youmu_common_bombbg(plr);
} while(player_is_bomb_active(plr));
}
TASK(youmu_mirror_bomb_handler, { YoumuAController *ctrl; }) {
YoumuAController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
@ -555,23 +545,11 @@ TASK(youmu_mirror_bomb_handler, { YoumuAController *ctrl; }) {
WAIT_EVENT_OR_DIE(&plr->events.bomb_used);
play_sound("bomb_youmu_b");
INVOKE_SUBTASK(youmu_mirror_bomb_controller, ctrl);
INVOKE_SUBTASK(youmu_mirror_bomb_background, ctrl);
INVOKE_SUBTASK(youmu_common_bomb_background, ENT_BOX(plr), &ctrl->bomb_bg);
INVOKE_SUBTASK(youmu_mirror_bomb_postprocess, ctrl);
}
}
static inline Projectile *youmu_mirror_self_shot(cmplx pos, MoveParams move, real dmg, ShaderProgram *shader) {
return PROJECTILE(
.damage = dmg,
.layer = LAYER_PLAYER_SHOT | 0x20,
.move = move,
.pos = pos,
.proto = pp_youmu,
.shader_ptr = shader,
.type = PROJ_PLAYER,
);
}
TASK(youmu_mirror_shot_forward, { YoumuAController *ctrl; }) {
YoumuAController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
@ -589,12 +567,12 @@ TASK(youmu_mirror_shot_forward, { YoumuAController *ctrl; }) {
for(int side = -1; side < 2; side += 2) {
cmplx origin = plr->pos + 10*side + 5*I;
youmu_mirror_self_shot(origin, move_linear(v), SHOT_SELF_DAMAGE, shader);
youmu_common_shot(origin, move_linear(v), SHOT_SELF_DAMAGE, shader);
for(int p = 0; p < power_rank; ++p) {
cmplx v2 = -(20 - p) * I * cdir(side * (1 + p) * spread);
youmu_mirror_self_shot(
youmu_common_shot(
origin,
move_asymptotic_halflife(
0.2 * v2 * cdir(M_PI * 0.25 * side),
@ -618,6 +596,8 @@ TASK(youmu_mirror_controller, { BoxedPlayer plr; }) {
ctrl->sprites.petal = get_sprite("part/petal");
ctrl->sprites.blast_huge_halo = get_sprite("part/blast_huge_halo");
youmu_common_init_bomb_background(&ctrl->bomb_bg);
INVOKE_SUBTASK(youmu_mirror_shot_forward, ctrl);
INVOKE_SUBTASK(youmu_mirror_myon, ctrl);
INVOKE_SUBTASK(youmu_mirror_bomb_handler, ctrl);
@ -626,7 +606,6 @@ TASK(youmu_mirror_controller, { BoxedPlayer plr; }) {
}
static void youmu_mirror_init(Player *plr) {
youmu_common_bomb_buffer_init();
INVOKE_TASK(youmu_mirror_controller, ENT_BOX(plr));
}

View file

@ -12,19 +12,50 @@
#include "plrmodes.h"
#include "youmu.h"
#include "util/glm.h"
#include "stagedraw.h"
static cmplx youmu_homing_target(cmplx org, cmplx fallback) {
return plrutil_homing_target(org, fallback);
}
#define SHOT_BASIC_DAMAGE 60
#define SHOT_BASIC_DELAY 6
static void youmu_homing_trail(Projectile *p, cmplx v, int to) {
#define SHOT_HOMING_DAMAGE 120
#define SHOT_HOMING_DELAY 6
#define SHOT_SPREAD_DAMAGE 40
#define SHOT_SPREAD_DELAY 12
#define SHOT_ORBS_SPIRIT_DAMAGE 90
#define SHOT_ORBS_SPIRIT_SPAWN_DELAY 11
#define SHOT_ORBS_DELAY_BASE 45
#define SHOT_ORBS_DELAY_PER_POWER -5
#define SHOT_ORBS_LIFETIME_BASE 100
#define SHOT_ORBS_LIFETIME_PER_POWER 10
#define BOMB_SLICE_DAMAGE 100
#define BOMB_SLICE_PERIOD 2
typedef struct YoumuBController {
struct {
Sprite *blast_huge_halo;
Sprite *blast_huge_rays;
Sprite *petal;
Sprite *smoothdot;
Sprite *stardust;
Sprite *youmu_slice;
} sprites;
ShaderProgram *shot_shader;
Player *plr;
YoumuBombBGData bomb_bg;
} YoumuBController;
static void youmu_homing_trail(YoumuBController *ctrl, Projectile *p, cmplx v, int to) {
uint32_t tmp = p->ent.spawn_id;
float u = M_PI * 2.0f * (float)(splitmix32(&tmp) / (double)UINT32_MAX);
RNG_ARRAY(R, 5);
PARTICLE(
.sprite = "stardust",
.sprite_ptr = ctrl->sprites.stardust,
.pos = p->pos + vrng_range(R[0], 3, 12) * vrng_dir(R[1]),
.color = RGBA(0.0, 0.3 * vrng_real(R[2]), 0.3, 0.0),
// .draw_rule = pdraw_timeout_fade(1, 0),
@ -38,7 +69,7 @@ static void youmu_homing_trail(Projectile *p, cmplx v, int to) {
);
PARTICLE(
.sprite = "smoothdot",
.sprite_ptr = ctrl->sprites.smoothdot,
.pos = p->pos,
.color = color_mul(RGBA(0.2, 0.24, 0.3, 0.2), &p->color),
.move = move_asymptotic_simple(-0.5*v*cdir(0.2*sin(u+3*creal(p->pos)/VIEWPORT_W*M_TAU) + 0.2*cos(u+3*cimag(p->pos)/VIEWPORT_H*M_TAU)), 2),
@ -50,16 +81,20 @@ static void youmu_homing_trail(Projectile *p, cmplx v, int to) {
}
static void youmu_particle_slice_draw(Projectile *p, int t, ProjDrawRuleArgs args) {
// TODO: pick animation frame based on slice direction?
AniPlayer *player_ani = args[0].as_ptr;
Sprite *player_frame = aniplayer_get_frame(player_ani);
float lifetime = p->timeout;
float tt = t/lifetime;
float f = 0;
float tt = t / lifetime;
float f = 0.0f;
if(tt > 0.1) {
f = min(1,(tt-0.1)/0.2);
f = fminf(1.0f, (tt - 0.1f) / 0.2f);
}
if(tt > 0.5) {
f = 1+(tt-0.5)/0.5;
if(tt > 0.5f) {
f = 1.0f + (tt - 0.5f) / 0.5f;
}
SpriteParamsBuffer spbuf;
@ -67,154 +102,96 @@ static void youmu_particle_slice_draw(Projectile *p, int t, ProjDrawRuleArgs arg
sp.scale.x *= f;
r_draw_sprite(&sp);
float slicelen = 500;
cmplx slicepos = p->pos-(tt>0.1)*slicelen*I*cdir(p->angle)*(5*pow(tt-0.1,1.1)-0.5);
float slicelen = 500.0f;
cmplx slicepos = p->pos - (tt > 0.1f) * slicelen * I*cdir(p->angle) * (5 * powf(tt - 0.1f, 1.1f) - 0.5f);
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = aniplayer_get_frame(&global.plr.ani),
.pos = { creal(slicepos), cimag(slicepos) },
.sprite_ptr = player_frame,
.pos.as_cmplx = slicepos,
.shader_params = &spbuf.shader_params,
});
}
static int youmu_slice_petal(Projectile *p, int t) {
int r = accelerated(p, t);
TASK(youmu_haunting_shot_basic, { YoumuBController *ctrl; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
if(t >= 0) {
p->color = *color_mul_scalar(RGBA(0.2, 0.2, 1, 0), min(1, t / 40.0));
}
for(;;) {
WAIT_EVENT_OR_DIE(&plr->events.shoot);
play_loop("generic_shot");
return r;
}
cmplx v = -20 * I;
static int youmu_particle_slice_logic(Projectile *p, int t) {
if(t < 0) {
return ACTION_ACK;
}
for(int side = -1; side < 2; side += 2) {
cmplx origin = plr->pos + 10*side + 5*I;
youmu_common_shot(origin, move_linear(v), SHOT_BASIC_DAMAGE, ctrl->shot_shader);
}
double lifetime = p->timeout;
double tt = t/lifetime;
double a = 0;
if(tt > 0.) {
a = min(1,(tt-0.)/0.2);
}
if(tt > 0.5) {
a = max(0,1-(tt-0.5)/0.5);
}
p->color = *RGBA(a, a, a, 0);
if(t%5 == 0) {
cmplx phase = cdir(p->angle);
PARTICLE(
.sprite = "petal",
.pos = p->pos-400*phase,
.rule = youmu_slice_petal,
.draw_rule = pdraw_petal_random(),
.args = {
phase,
phase*cdir(0.1),
},
.layer = LAYER_PARTICLE_HIGH | 0x2,
.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
);
}
Ellipse e = {
.origin = p->pos,
.axes = CMPLX(512, 64),
.angle = p->angle + M_PI * 0.5,
};
// FIXME: this may still be too slow for lasers in some cases
stage_clear_hazards_in_ellipse(e, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
ent_area_damage_ellipse(e, &(DamageInfo) { 52, DMG_PLAYER_BOMB }, NULL, NULL);
return ACTION_NONE;
}
static void YoumuSlash(Enemy *e, int t, bool render) {
}
static int youmu_slash(Enemy *e, int t) {
if(t > creal(e->args[0]))
return ACTION_DESTROY;
if(t < 0)
return 1;
if(global.frames - global.plr.recovery > 0) {
return ACTION_DESTROY;
}
TIMER(&t);
FROM_TO(0,10000,3) {
cmplx pos = cexp(I*_i)*(100+10*_i*_i*0.01);
PARTICLE(
.sprite = "youmu_slice",
.color = RGBA(1, 1, 1, 0),
.pos = e->pos+pos,
.draw_rule = youmu_particle_slice_draw,
.rule = youmu_particle_slice_logic,
.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
.timeout = 100,
.angle = carg(pos) + M_PI/2,
.layer = LAYER_PARTICLE_HIGH | 0x1,
);
}
return 1;
}
static int youmu_asymptotic(Projectile *p, int t) {
if(t < 0) {
return ACTION_ACK;
}
p->angle = carg(p->args[0]);
p->args[1] *= 0.8;
p->pos += p->args[0] * (p->args[1] + 1);
youmu_homing_trail(p, cexp(I*p->angle), 5);
return 1;
}
static void youmu_haunting_power_shot(Player *plr, int p) {
int d = -2;
double spread = 0.5 * (1 + 0.25 * sin(global.frames/10.0));
double speed = 8;
if(2 * plr->power / 100 < p || (global.frames + d * p) % 12) {
return;
}
float np = (float)p / (2 * plr->power / 100);
for(int sign = -1; sign < 2; sign += 2) {
cmplx dir = cexp(I*carg(sign*p*spread-speed*I));
PROJECTILE(
.proto = pp_hghost,
.pos = plr->pos,
.rule = youmu_asymptotic,
.color = color_mul_scalar(RGB(0.7 + 0.3 * (1-np), 0.8 + 0.2 * sqrt(1-np), 1.0), 0.5),
.args = { speed * dir * (1 - 0.25 * (1 - np)), 3 * (1 - pow(1 - np, 2)), 60, },
.type = PROJ_PLAYER,
.damage = 20,
.shader = "sprite_default",
);
WAIT(SHOT_BASIC_DELAY);
}
}
TASK(youmu_homing_shot, { BoxedPlayer plr; }) {
TASK(youmu_burst_shot, { YoumuBController *ctrl; int num_shots; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
ShaderProgram *shader = ctrl->shot_shader;
int nshots = ARGS.num_shots;
real base_speed = 8;
for(int shot = 1; shot <= nshots; ++shot) {
real np = shot / (real)nshots;
Color *clr = color_mul_scalar(RGB(0.7 + 0.3 * (1-np), 0.8 + 0.2 * sqrt(1-np), 1.0), 0.5);
real spread = 0.5 * (1 + 0.25 * sin(global.frames/10.0));
real speed = base_speed * (1 - 0.25 * (1 - np));
real boost = 3 * (1 - pow(1 - np, 2));
for(int side = -1; side < 2; side += 2) {
cmplx vel = speed * cnormalize(side * shot * spread - base_speed * I);
PROJECTILE(
.color = clr,
.damage = SHOT_SPREAD_DAMAGE,
.move = move_asymptotic_simple(vel, boost),
.pos = plr->pos,
.proto = pp_hghost,
.shader_ptr = shader,
.type = PROJ_PLAYER,
);
}
YIELD;
}
}
TASK(youmu_haunting_shot_spread, { YoumuBController *ctrl; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
for(;;) {
WAIT_EVENT_OR_DIE(&plr->events.shoot);
if(plr->inputflags & INFLAG_FOCUS) {
continue;
}
INVOKE_TASK(youmu_burst_shot, ctrl, 2 * plr->power / 100);
WAIT(SHOT_SPREAD_DELAY);
}
}
TASK(youmu_homing_shot, { YoumuBController *ctrl; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_hghost,
.pos = ENT_UNBOX(ARGS.plr)->pos,
.pos = plr->pos,
.color = RGB(0.75, 0.9, 1),
.type = PROJ_PLAYER,
.damage = 120,
.shader = "sprite_default",
.damage = SHOT_HOMING_DAMAGE,
.shader_ptr = ctrl->shot_shader,
));
real speed = 10;
@ -224,17 +201,33 @@ TASK(youmu_homing_shot, { BoxedPlayer plr; }) {
cmplx target = VIEWPORT_W * 0.5;
for(int i = 0; i < 60; ++i) {
target = youmu_homing_target(p->pos, target);
target = plrutil_homing_target(p->pos, target);
cmplx aimdir = cnormalize(target - p->pos - p->move.velocity*10);
p->move.velocity += aim_strength * aimdir;
p->move.velocity *= speed / cabs(p->move.velocity);
aim_strength += aim_strength_accel;
youmu_homing_trail(p, 0.5 * p->move.velocity, 12);
youmu_homing_trail(ctrl, p, 0.5 * p->move.velocity, 12);
YIELD;
}
}
TASK(youmu_haunting_shot_homing, { YoumuBController *ctrl; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
for(;;) {
WAIT_EVENT_OR_DIE(&plr->events.shoot);
if(plr->inputflags & INFLAG_FOCUS) {
continue;
}
INVOKE_TASK(youmu_homing_shot, ctrl);
WAIT(SHOT_HOMING_DELAY);
}
}
TASK(youmu_orb_homing_spirit_expire, { BoxedProjectile p; }) {
Projectile *p = ENT_UNBOX(ARGS.p);
@ -256,7 +249,8 @@ static int youmu_orb_homing_spirit_timeout(Projectile *orb) {
return orb->timeout - projectile_time(orb);
}
TASK(youmu_orb_homing_spirit, { cmplx pos; cmplx velocity; cmplx target; real charge; real damage; real spin; BoxedProjectile orb; }) {
TASK(youmu_orb_homing_spirit, { YoumuBController *ctrl; cmplx pos; cmplx velocity; cmplx target; real charge; real damage; real spin; BoxedProjectile orb; }) {
YoumuBController *ctrl = ARGS.ctrl;
int timeout = youmu_orb_homing_spirit_timeout(ENT_UNBOX(ARGS.orb));
if(timeout <= 0) {
@ -270,7 +264,7 @@ TASK(youmu_orb_homing_spirit, { cmplx pos; cmplx velocity; cmplx target; real ch
.type = PROJ_PLAYER,
.timeout = timeout,
.damage = ARGS.damage,
.shader = "sprite_particle",
.shader_ptr = ctrl->shot_shader,
));
INVOKE_TASK_AFTER(&p->events.killed, youmu_orb_homing_spirit_expire, ENT_BOX(p));
@ -304,7 +298,7 @@ TASK(youmu_orb_homing_spirit, { cmplx pos; cmplx velocity; cmplx target; real ch
if(orb) {
target = orb->pos;
} else {
target = youmu_homing_target(p->pos, creal(global.plr.pos) - 128*I);
target = plrutil_homing_target(p->pos, creal(global.plr.pos) - 128*I);
}
cmplx aimdir = cnormalize(target - p->pos - p->move.velocity);
@ -327,13 +321,52 @@ TASK(youmu_orb_homing_spirit, { cmplx pos; cmplx velocity; cmplx target; real ch
p->move.velocity = s * cnormalize(p->move.velocity + aim_strength * s * aimdir);
approach_asymptotic_p(&speed, speed_target, 0.05, 1e-5);
youmu_homing_trail(p, 0.5 * p->move.velocity, 12);
youmu_homing_trail(ctrl, p, 0.5 * p->move.velocity, 12);
YIELD;
}
}
TASK(youmu_orb_update, { BoxedPlayer plr; BoxedProjectile orb; }) {
Player *plr = ENT_UNBOX(ARGS.plr);
static void youmu_orb_explode(YoumuBController *ctrl, Projectile *orb) {
PARTICLE(
.sprite_ptr = ctrl->sprites.blast_huge_rays,
.pos = orb->pos,
.timeout = 20,
.color = RGBA(0.1, 0.5, 0.1, 0.0),
.draw_rule = pdraw_timeout_scalefade_exp(0.01*(1+I), 1, 1, 0, 2),
.flags = PFLAG_REQUIREDPARTICLE,
.angle = rng_angle(),
.layer = LAYER_PARTICLE_LOW,
);
PARTICLE(
.sprite_ptr = ctrl->sprites.blast_huge_halo,
.pos = orb->pos,
.timeout = 30,
.color = RGBA(0.1, 0.1, 0.5, 0.0),
.draw_rule = pdraw_timeout_scalefade_exp(1, 0.01*(1+I), 1, 0, 2),
.flags = PFLAG_REQUIREDPARTICLE,
.angle = rng_angle(),
.layer = LAYER_PARTICLE_LOW,
);
PARTICLE(
.sprite_ptr = ctrl->sprites.blast_huge_halo,
.pos = orb->pos,
.timeout = 40,
.color = RGBA(0.5, 0.1, 0.1, 0.0),
.draw_rule = pdraw_timeout_scalefade_exp(0.8, -0.3*(1+I), 1, 0, 2),
.flags = PFLAG_REQUIREDPARTICLE,
.angle = rng_angle(),
.layer = LAYER_PARTICLE_LOW,
);
// TODO sound effect;
kill_projectile(orb);
}
TASK(youmu_orb_update, { YoumuBController *ctrl; BoxedProjectile orb; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
Projectile *orb = TASK_BIND(ARGS.orb);
for(;;) {
@ -343,43 +376,8 @@ TASK(youmu_orb_update, { BoxedPlayer plr; BoxedProjectile orb; }) {
orb->scale = 0.5 + 0.5 * tf;
orb->angle += 0.2;
// TODO events for player input?
if(!(plr->inputflags & INFLAG_FOCUS)) {
PARTICLE(
.sprite = "blast_huge_rays",
.pos = orb->pos,
.timeout = 20,
.color = RGBA(0.1, 0.5, 0.1, 0.0),
.draw_rule = pdraw_timeout_scalefade_exp(0.01*(1+I), 1, 1, 0, 2),
.flags = PFLAG_REQUIREDPARTICLE,
.angle = rng_angle(),
.layer = LAYER_PARTICLE_LOW,
);
PARTICLE(
.sprite = "blast_huge_halo",
.pos = orb->pos,
.timeout = 30,
.color = RGBA(0.1, 0.1, 0.5, 0.0),
.draw_rule = pdraw_timeout_scalefade_exp(1, 0.01*(1+I), 1, 0, 2),
.flags = PFLAG_REQUIREDPARTICLE,
.angle = rng_angle(),
.layer = LAYER_PARTICLE_LOW,
);
PARTICLE(
.sprite = "blast_huge_halo",
.pos = orb->pos,
.timeout = 40,
.color = RGBA(0.5, 0.1, 0.1, 0.0),
.draw_rule = pdraw_timeout_scalefade_exp(0.8, -0.3*(1+I), 1, 0, 2),
.flags = PFLAG_REQUIREDPARTICLE,
.angle = rng_angle(),
.layer = LAYER_PARTICLE_LOW,
);
// TODO sound effect;
kill_projectile(orb);
youmu_orb_explode(ctrl, orb);
break;
}
@ -388,7 +386,7 @@ TASK(youmu_orb_update, { BoxedPlayer plr; BoxedProjectile orb; }) {
}
TASK(youmu_orb_death, { BoxedProjectile orb; BoxedTask control_task; }) {
cotask_cancel(cotask_unbox(ARGS.control_task));
CANCEL_TASK(ARGS.control_task);
Projectile *orb = ENT_UNBOX(ARGS.orb);
PARTICLE(
@ -410,59 +408,180 @@ TASK(youmu_orb_death, { BoxedProjectile orb; BoxedTask control_task; }) {
);
}
TASK(youmu_orb_shot, { BoxedPlayer plr; }) {
Player *plr = ENT_UNBOX(ARGS.plr);
int pwr = plr->power / 100;
TASK(youmu_orb_shot, { YoumuBController *ctrl; int lifetime; real spirit_damage; int spirit_spawn_delay; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
Projectile *orb = TASK_BIND(PROJECTILE(
.proto = pp_youhoming,
.pos = plr->pos,
.color = RGB(1, 1, 1),
.type = PROJ_PLAYER,
.damage = 1000,
.timeout = 100 + 10 * pwr,
.timeout = ARGS.lifetime,
.move = move_asymptotic(-30.0*I, -0.7*I, 0.8),
.flags = PFLAG_MANUALANGLE,
.flags = PFLAG_MANUALANGLE | PFLAG_NOCOLLISION,
));
INVOKE_TASK(youmu_orb_update, ARGS.plr, ENT_BOX(orb));
INVOKE_TASK(youmu_orb_update, ctrl, ENT_BOX(orb));
INVOKE_TASK_AFTER(&orb->events.killed, youmu_orb_death, ENT_BOX(orb), THIS_TASK);
real pdmg = 120 - 18 * 4 * (1 - pow(1 - pwr / 4.0, 1.5));
real pdmg = ARGS.spirit_damage;
cmplx v = 5 * I;
int spawn_delay = ARGS.spirit_spawn_delay;
for(;;) {
WAIT(11);
INVOKE_TASK(youmu_orb_homing_spirit, orb->pos, v, 0, 0, pdmg, 0.1, ENT_BOX(orb));
INVOKE_TASK(youmu_orb_homing_spirit, orb->pos, v, 0, 0, pdmg, -0.1, ENT_BOX(orb));
WAIT(spawn_delay);
INVOKE_TASK(youmu_orb_homing_spirit, ctrl, orb->pos, v, 0, 0, pdmg, 0.1, ENT_BOX(orb));
INVOKE_TASK(youmu_orb_homing_spirit, ctrl, orb->pos, v, 0, 0, pdmg, -0.1, ENT_BOX(orb));
}
}
static void youmu_haunting_shot(Player *plr) {
youmu_common_shot(plr);
TASK(youmu_haunting_shot_orbs, { YoumuBController *ctrl; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
if(player_should_shoot(plr)) {
if(plr->inputflags & INFLAG_FOCUS) {
int pwr = plr->power / 100;
for(;;) {
WAIT_EVENT_OR_DIE(&plr->events.shoot);
if(!(global.frames % (45 - 4 * pwr))) {
INVOKE_TASK(youmu_orb_shot, ENT_BOX(plr));
}
} else {
if(!(global.frames % 6)) {
INVOKE_TASK(youmu_homing_shot, ENT_BOX(plr));
}
for(int p = 1; p <= 2*PLR_MAX_POWER/100; ++p) {
youmu_haunting_power_shot(plr, p);
}
if(!(plr->inputflags & INFLAG_FOCUS)) {
continue;
}
int power_rank = plr->power / 100;
INVOKE_TASK(youmu_orb_shot,
.ctrl = ctrl,
.lifetime = SHOT_ORBS_LIFETIME_BASE + power_rank * SHOT_ORBS_LIFETIME_PER_POWER,
.spirit_damage = SHOT_ORBS_SPIRIT_DAMAGE, // 120 - 18 * 4 * (1 - pow(1 - (plr->power / 100) / 4.0, 1.5));
.spirit_spawn_delay = SHOT_ORBS_SPIRIT_SPAWN_DELAY
);
WAIT(SHOT_ORBS_DELAY_BASE + power_rank * SHOT_ORBS_DELAY_PER_POWER);
}
}
static void youmu_haunting_bomb(Player *plr) {
play_sound("bomb_youmu_b");
create_enemy_p(&plr->slaves, global.plr.pos, ENEMY_BOMB, YoumuSlash, youmu_slash, 280,0,0,0);
TASK(youmu_haunting_bomb_slice_petal, { YoumuBController *ctrl; cmplx pos; cmplx vel; }) {
YoumuBController *ctrl = ARGS.ctrl;
Projectile *p = TASK_BIND(PARTICLE(
.sprite_ptr = ctrl->sprites.petal,
.pos = ARGS.pos,
.draw_rule = pdraw_petal_random(),
.move = move_accelerated(ARGS.vel, ARGS.vel * cdir(0.1)),
.layer = LAYER_PARTICLE_HIGH | 0x2,
.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
));
real transition_time = 40;
for(real t = 0; t <= transition_time; ++t) {
p->color = *color_mul_scalar(RGBA(0.2, 0.2, 1, 0), min(1, t / transition_time));
YIELD;
}
}
TASK(youmu_haunting_bomb_slice, { YoumuBController *ctrl; cmplx pos; real angle; }) {
YoumuBController *ctrl = ARGS.ctrl;
Projectile *p = TASK_BIND(PARTICLE(
.sprite_ptr = ctrl->sprites.youmu_slice,
.color = RGBA(1, 1, 1, 0),
.pos = ARGS.pos,
.draw_rule = {
.func = youmu_particle_slice_draw,
.args[0].as_ptr = &ctrl->plr->ani,
},
.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
.timeout = 100,
.angle = ARGS.angle,
.layer = LAYER_PARTICLE_HIGH | 0x1,
));
cmplx petal_dir = cdir(ARGS.angle);
for(int t = 0;; ++t) {
real lifetime = p->timeout;
real tt = t/lifetime;
real a = 0;
if(tt > 0.5) {
a = max(0, 1 - (tt - 0.5) / 0.5);
} else {
a = min(1, tt / 0.2);
}
p->color = *RGBA(a, a, a, 0);
if(t % 5 == 0) {
INVOKE_TASK(youmu_haunting_bomb_slice_petal, ctrl, p->pos - 400 * petal_dir, petal_dir);
}
if(t % BOMB_SLICE_PERIOD == 0) {
Ellipse e = {
.origin = p->pos,
.axes = CMPLX(512, 64),
.angle = p->angle + M_PI * 0.5,
};
// FIXME: this may still be too slow for lasers in some cases
stage_clear_hazards_in_ellipse(e, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
ent_area_damage_ellipse(e, &(DamageInfo) { BOMB_SLICE_DAMAGE, DMG_PLAYER_BOMB }, NULL, NULL);
}
YIELD;
}
}
TASK(youmu_haunting_bomb_controller, { YoumuBController *ctrl; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
cmplx origin = plr->pos;
int i = 0;
do {
cmplx ofs = cdir(i) * (100 + 10 * i * i * 0.01);
real angle = carg(ofs) + M_PI/2;
INVOKE_TASK(youmu_haunting_bomb_slice, ctrl, origin + ofs, angle);
WAIT(3);
++i;
} while(player_is_bomb_active(plr));
}
TASK(youmu_haunting_bomb_handler, { YoumuBController *ctrl; }) {
YoumuBController *ctrl = ARGS.ctrl;
Player *plr = ctrl->plr;
for(;;) {
WAIT_EVENT_OR_DIE(&plr->events.bomb_used);
play_sound("bomb_youmu_b");
INVOKE_SUBTASK(youmu_haunting_bomb_controller, ctrl);
INVOKE_SUBTASK(youmu_common_bomb_background, ENT_BOX(plr), &ctrl->bomb_bg);
}
}
TASK(youmu_haunting_controller, { BoxedPlayer plr; }) {
YoumuBController *ctrl = TASK_MALLOC(sizeof(*ctrl));
ctrl->plr = TASK_BIND(ARGS.plr);
ctrl->sprites.blast_huge_halo = get_sprite("part/blast_huge_halo");
ctrl->sprites.blast_huge_rays = get_sprite("part/blast_huge_rays");
ctrl->sprites.petal = get_sprite("part/petal");
ctrl->sprites.smoothdot = get_sprite("part/smoothdot");
ctrl->sprites.stardust = get_sprite("part/stardust");
ctrl->sprites.youmu_slice = get_sprite("part/youmu_slice");
ctrl->shot_shader = r_shader_get("sprite_particle");
youmu_common_init_bomb_background(&ctrl->bomb_bg);
INVOKE_SUBTASK(youmu_haunting_shot_basic, ctrl);
INVOKE_SUBTASK(youmu_haunting_shot_homing, ctrl);
INVOKE_SUBTASK(youmu_haunting_shot_spread, ctrl);
INVOKE_SUBTASK(youmu_haunting_shot_orbs, ctrl);
INVOKE_SUBTASK(youmu_haunting_bomb_handler, ctrl);
STALL;
}
static void youmu_haunting_init(Player *plr) {
INVOKE_TASK(youmu_haunting_controller, ENT_BOX(plr));
}
static void youmu_haunting_preload(void) {
@ -482,10 +601,6 @@ static void youmu_haunting_preload(void) {
NULL);
}
static void youmu_haunting_init(Player *plr) {
youmu_common_bomb_buffer_init();
}
PlayerMode plrmode_youmu_b = {
.name = "Haunting Revelation",
.description = "Ghosts are real, but theyre nothing to be afraid of. Unless, of course, you happen to stand in the way of a determined sword-wielding lady…",
@ -495,10 +610,7 @@ PlayerMode plrmode_youmu_b = {
.shot_mode = PLR_SHOT_YOUMU_HAUNTING,
.procs = {
.property = youmu_common_property,
.bomb = youmu_haunting_bomb,
.bombbg = youmu_common_bombbg,
.init = youmu_haunting_init,
.shot = youmu_haunting_shot,
.preload = youmu_haunting_preload,
},
};