smooth(er) bomb cancellations
speed up the bomb animation when the boss spell is approaching, but only if it can't be finished properly in time
This commit is contained in:
parent
e6dc4f4362
commit
7f72c1c09a
8 changed files with 247 additions and 68 deletions
|
@ -538,11 +538,6 @@ void process_boss(Boss **pboss) {
|
|||
bool extra = boss->current->type == AT_ExtraSpell;
|
||||
bool over = boss->current->finished && global.frames >= boss->current->endtime;
|
||||
|
||||
if(time == 0 && ATTACK_IS_SPELL(boss->current->type)) {
|
||||
// attack just started - cancel the player's bomb so that it doesn't fail the spell immediately
|
||||
global.plr.recovery = min(global.plr.recovery, global.frames);
|
||||
}
|
||||
|
||||
if(!boss->current->endtime) {
|
||||
int remaining = boss->current->timeout - time;
|
||||
|
||||
|
@ -737,6 +732,10 @@ void boss_start_attack(Boss *b, Attack *a) {
|
|||
tsrand_fill(4);
|
||||
create_particle2c("stain", VIEWPORT_W/2 + VIEWPORT_W/4*anfrand(0)+I*VIEWPORT_H/2+I*anfrand(1)*30, rgb(0.2,0.3,0.4), GrowFadeAdd, timeout_linear, 50, sign(anfrand(2))*10*(1+afrand(3)));
|
||||
}
|
||||
|
||||
// schedule a bomb cancellation for when the spell actually starts
|
||||
// we don't want an ongoing bomb to immediately ruin the spell bonus
|
||||
player_cancel_bomb(&global.plr, a->starttime - global.frames);
|
||||
}
|
||||
|
||||
stage_clear_hazards(true);
|
||||
|
|
148
src/player.c
148
src/player.c
|
@ -190,6 +190,17 @@ void player_logic(Player* plr) {
|
|||
player_realdeath(plr);
|
||||
|
||||
if(global.frames - plr->recovery < 0) {
|
||||
if(plr->bombcanceltime) {
|
||||
int bctime = plr->bombcanceltime + plr->bombcanceldelay;
|
||||
|
||||
if(bctime <= global.frames) {
|
||||
plr->recovery = global.frames;
|
||||
plr->bombcanceltime = 0;
|
||||
plr->bombcanceldelay = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Enemy *en;
|
||||
for(en = global.enemies; en; en = en->next)
|
||||
if(en->hp > ENEMY_IMMUNE)
|
||||
|
@ -226,6 +237,8 @@ bool player_bomb(Player *plr) {
|
|||
}
|
||||
|
||||
plr->recovery = global.frames + BOMB_RECOVERY;
|
||||
plr->bombcanceltime = 0;
|
||||
plr->bombcanceldelay = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -233,6 +246,134 @@ bool player_bomb(Player *plr) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void player_cancel_bomb(Player *plr, int delay) {
|
||||
if(global.frames - plr->recovery >= 0) {
|
||||
// not bombing
|
||||
return;
|
||||
}
|
||||
|
||||
if(plr->bombcanceltime) {
|
||||
int canceltime_queued = plr->bombcanceltime + plr->bombcanceldelay;
|
||||
int canceltime_requested = global.frames + delay;
|
||||
|
||||
if(canceltime_queued > canceltime_requested) {
|
||||
plr->bombcanceldelay -= (canceltime_queued - canceltime_requested);
|
||||
}
|
||||
} else {
|
||||
plr->bombcanceltime = global.frames;
|
||||
plr->bombcanceldelay = delay;
|
||||
}
|
||||
}
|
||||
|
||||
int player_get_bomb_progress(Player *plr, double *out_speed) {
|
||||
if(global.frames - plr->recovery >= 0) {
|
||||
if(out_speed != NULL) {
|
||||
*out_speed = 1.0;
|
||||
}
|
||||
|
||||
return BOMB_RECOVERY;
|
||||
}
|
||||
|
||||
int start_time = plr->recovery - BOMB_RECOVERY;
|
||||
int end_time = plr->recovery;
|
||||
|
||||
if(!plr->bombcanceltime || plr->bombcanceltime + plr->bombcanceldelay >= end_time) {
|
||||
if(out_speed != NULL) {
|
||||
*out_speed = 1.0;
|
||||
}
|
||||
|
||||
return BOMB_RECOVERY - (end_time - global.frames);
|
||||
}
|
||||
|
||||
int cancel_time = plr->bombcanceltime + plr->bombcanceldelay;
|
||||
int passed_time = plr->bombcanceltime - start_time;
|
||||
|
||||
int shortened_total_time = (BOMB_RECOVERY - passed_time) - (end_time - cancel_time);
|
||||
int shortened_passed_time = (global.frames - plr->bombcanceltime);
|
||||
|
||||
double passed_fraction = passed_time / (double)BOMB_RECOVERY;
|
||||
double shortened_fraction = shortened_passed_time / (double)shortened_total_time;
|
||||
shortened_fraction *= (1 - passed_fraction);
|
||||
|
||||
if(out_speed != NULL) {
|
||||
*out_speed = (BOMB_RECOVERY - passed_time) / (double)shortened_total_time;
|
||||
}
|
||||
|
||||
return rint(BOMB_RECOVERY * (passed_fraction + shortened_fraction));
|
||||
}
|
||||
|
||||
int player_run_bomb_logic(Player *plr, void *ent, complex *argptr, int (*callback)(void *ent, int t, double speed)) {
|
||||
static bool inside;
|
||||
|
||||
if(inside) {
|
||||
log_fatal("recursive call not allowed");
|
||||
}
|
||||
|
||||
inside = true;
|
||||
|
||||
int prev_t = creal(*argptr);
|
||||
double speed;
|
||||
int bomb_t = player_get_bomb_progress(plr, &speed);
|
||||
|
||||
// we're going to (partially) simulate a few frames from the 'past' here...
|
||||
|
||||
int rewind = bomb_t - prev_t;
|
||||
int saveframes __attribute__((unused)) = global.frames;
|
||||
Projectile *saveparts = global.particles;
|
||||
|
||||
if(rewind < 0) {
|
||||
// edge case: entity from a previous bomb remaining,
|
||||
// but we're either not bombing or started a new bomb
|
||||
// no need to simulate any old frames in this case
|
||||
bomb_t = prev_t;
|
||||
rewind = 0;
|
||||
}
|
||||
|
||||
global.frames -= rewind;
|
||||
global.particles = NULL;
|
||||
|
||||
int t, ret;
|
||||
for(t = prev_t; t <= bomb_t; ++t) {
|
||||
ret = callback(ent, t, speed);
|
||||
|
||||
if(t != bomb_t) {
|
||||
// we must not process the final frame here, stage_logic will do it
|
||||
process_projectiles(&global.particles, false);
|
||||
|
||||
// call the draw functions too, because some of them modify args or spawn stuff...
|
||||
// this is stupid and should not happen, but for now it does.
|
||||
draw_projectiles(global.particles);
|
||||
|
||||
global.frames++;
|
||||
}
|
||||
|
||||
if(ret == ACTION_DESTROY) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// merge our stolen particles back into the main list
|
||||
// won't preserve the order, but whatever...
|
||||
|
||||
while(global.particles && saveparts) {
|
||||
Projectile *part = global.particles;
|
||||
global.particles = global.particles->next;
|
||||
|
||||
part->prev = NULL;
|
||||
part->next = saveparts;
|
||||
saveparts->prev = part;
|
||||
saveparts = part;
|
||||
}
|
||||
|
||||
assert(global.frames == saveframes);
|
||||
|
||||
global.particles = saveparts;
|
||||
*argptr = t;
|
||||
|
||||
inside = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void player_realdeath(Player *plr) {
|
||||
plr->deathtime = -DEATH_DELAY-1;
|
||||
plr->respawntime = global.frames;
|
||||
|
@ -351,6 +492,13 @@ bool player_event(Player *plr, uint8_t type, uint16_t value) {
|
|||
switch(value) {
|
||||
case KEY_BOMB:
|
||||
useful = player_bomb(plr);
|
||||
|
||||
if(!useful && plr->iddqd) {
|
||||
// smooth bomb cancellation test
|
||||
player_cancel_bomb(plr, 60);
|
||||
useful = true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case KEY_IDDQD:
|
||||
|
|
|
@ -71,6 +71,8 @@ typedef struct {
|
|||
int recovery;
|
||||
int deathtime;
|
||||
int respawntime;
|
||||
int bombcanceltime;
|
||||
int bombcanceldelay;
|
||||
|
||||
struct PlayerMode *mode;
|
||||
AniPlayer ani;
|
||||
|
@ -138,4 +140,8 @@ void player_add_lives(Player *plr, int lives);
|
|||
void player_add_bombs(Player *plr, int bombs);
|
||||
void player_add_points(Player *plr, unsigned int points);
|
||||
|
||||
void player_cancel_bomb(Player *plr, int delay);
|
||||
int player_get_bomb_progress(Player *plr, double *out_speed);
|
||||
int player_run_bomb_logic(Player *plr, void *ent, complex *argptr, int (*callback)(void *ent, int t, double speed));
|
||||
|
||||
void player_preload(void);
|
||||
|
|
|
@ -65,6 +65,11 @@ static int marisa_laser_slave(Enemy *e, int t) {
|
|||
static void masterspark_ring_draw(complex base, int t, float fade) {
|
||||
glPushMatrix();
|
||||
|
||||
if(t < 1) {
|
||||
// prevent division by zero
|
||||
t = 1;
|
||||
}
|
||||
|
||||
glTranslatef(creal(base), cimag(base)-t*t*0.4, 0);
|
||||
|
||||
float f = sqrt(t/500.0)*1200;
|
||||
|
@ -79,6 +84,8 @@ static void masterspark_ring_draw(complex base, int t, float fade) {
|
|||
}
|
||||
|
||||
static void masterspark_draw(Enemy *e, int t) {
|
||||
t = player_get_bomb_progress(&global.plr, NULL) * (e->args[0] / BOMB_RECOVERY);
|
||||
|
||||
glPushMatrix();
|
||||
|
||||
float angle = 9 - t/e->args[0]*6.0, fade = 1;
|
||||
|
@ -102,8 +109,7 @@ static void masterspark_draw(Enemy *e, int t) {
|
|||
glPopMatrix();
|
||||
|
||||
// glColor4f(0.9,1,1,fade*0.8);
|
||||
int i;
|
||||
for(i = 0; i < 8; i++)
|
||||
for(int i = 0; i < 8; i++)
|
||||
masterspark_ring_draw(global.plr.pos - 50.0*I, t%20 + 10*i, fade);
|
||||
|
||||
glColor4f(1,1,1,1);
|
||||
|
@ -115,7 +121,9 @@ static int masterspark(Enemy *e, int t) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if(t > creal(e->args[0]) || global.frames - global.plr.recovery > 0) {
|
||||
t = player_get_bomb_progress(&global.plr, NULL) * (e->args[0] / BOMB_RECOVERY);
|
||||
|
||||
if(t >= creal(e->args[0]) || global.frames - global.plr.recovery > 0) {
|
||||
global.shake_view = 0;
|
||||
return ACTION_DESTROY;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ static void marisa_star_trail_draw(Projectile *p, int t) {
|
|||
|
||||
static void marisa_star_bomb_draw(Projectile *p, int t) {
|
||||
marisa_star(p, t);
|
||||
create_particle1c("maristar_orbit", p->pos, 0, GrowFadeAdd, timeout, 40)->type = PlrProj;
|
||||
}
|
||||
|
||||
static int marisa_star_projectile(Projectile *p, int t) {
|
||||
|
@ -83,24 +82,32 @@ static int marisa_star_slave(Enemy *e, int t) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int marisa_star_orbit(Projectile *p, int t) { // a[0]: x' a[1]: x''
|
||||
if(t == 0)
|
||||
p->pos0 = global.plr.pos;
|
||||
if(t < 0)
|
||||
return 1;
|
||||
|
||||
if(t > 300 || global.frames - global.plr.recovery > 0)
|
||||
return ACTION_DESTROY;
|
||||
|
||||
static int marisa_star_orbit_logic(void *v, int t, double speed) {
|
||||
Projectile *p = v;
|
||||
float r = cabs(p->pos0 - p->pos);
|
||||
|
||||
p->args[1] = (0.5e5-t*t)*cexp(I*carg(p->pos0 - p->pos))/(r*r);
|
||||
p->args[0] += p->args[1]*0.2;
|
||||
p->pos += p->args[0];
|
||||
|
||||
create_particle1c("maristar_orbit", p->pos, 0, GrowFadeAdd, timeout, 40 / speed)->type = PlrProj;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int marisa_star_orbit(Projectile *p, int t) { // a[0]: x' a[1]: x''
|
||||
if(t == 0) {
|
||||
p->pos0 = global.plr.pos;
|
||||
}
|
||||
|
||||
if(t < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(global.frames - global.plr.recovery > 0) {
|
||||
return ACTION_DESTROY;
|
||||
}
|
||||
|
||||
return player_run_bomb_logic(&global.plr, p, &p->args[3], marisa_star_orbit_logic);
|
||||
}
|
||||
|
||||
static void marisa_star_bomb(Player *plr) {
|
||||
play_sound("bomb_marisa_b");
|
||||
for(int i = 0; i < 20; i++) {
|
||||
|
|
|
@ -119,6 +119,28 @@ static int youmu_mirror_myon(Enemy *e, int t) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int youmu_split_logic(void *v, int t, double speed) {
|
||||
Enemy *e = v;
|
||||
TIMER(&t);
|
||||
|
||||
FROM_TO(30,200,1) {
|
||||
tsrand_fill(2);
|
||||
create_particle2c("smoke", VIEWPORT_W/2 + VIEWPORT_H/2*I, rgba(0.4,0.4,0.4,afrand(0)*0.2+0.4), PartDraw, youmu_common_particle_spin, 300 / speed, speed*6*cexp(I*afrand(1)*2*M_PI));
|
||||
}
|
||||
|
||||
FROM_TO(100,170,10) {
|
||||
tsrand_fill(3);
|
||||
create_particle1c("youmu_slice", VIEWPORT_W/2.0 + VIEWPORT_H/2.0*I - 200-200.0*I + 400*afrand(0)+400.0*I*afrand(1), 0, youmu_common_particle_slice_draw, timeout, (100-_i) / speed)->angle = 360.0*afrand(2);
|
||||
}
|
||||
|
||||
FROM_TO(0, 220, 1) {
|
||||
float talt = atan((t-e->args[0]/2)/30.0)*10+atan(-e->args[0]/2);
|
||||
global.plr.pos = VIEWPORT_W/2.0 + (VIEWPORT_H-80)*I + VIEWPORT_W/3.0*sin(talt);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int youmu_split(Enemy *e, int t) {
|
||||
if(t < 0)
|
||||
return 1;
|
||||
|
@ -130,25 +152,7 @@ static int youmu_split(Enemy *e, int t) {
|
|||
return ACTION_DESTROY;
|
||||
}
|
||||
|
||||
TIMER(&t);
|
||||
|
||||
FROM_TO(30,200,1) {
|
||||
tsrand_fill(2);
|
||||
create_particle2c("smoke", VIEWPORT_W/2 + VIEWPORT_H/2*I, rgba(0.4,0.4,0.4,afrand(0)*0.2+0.4), PartDraw, youmu_common_particle_spin, 300, 6*cexp(I*afrand(1)*2*M_PI));
|
||||
}
|
||||
|
||||
FROM_TO(100,170,10) {
|
||||
tsrand_fill(3);
|
||||
create_particle1c("youmu_slice", VIEWPORT_W/2.0 + VIEWPORT_H/2.0*I - 200-200.0*I + 400*afrand(0)+400.0*I*afrand(1), 0, youmu_common_particle_slice_draw, timeout, 100-_i)->angle = 360.0*afrand(2);
|
||||
}
|
||||
|
||||
|
||||
FROM_TO(0, 220, 1) {
|
||||
float talt = atan((t-e->args[0]/2)/30.0)*10+atan(-e->args[0]/2);
|
||||
global.plr.pos = VIEWPORT_W/2.0 + (VIEWPORT_H-80)*I + VIEWPORT_W/3.0*sin(talt);
|
||||
}
|
||||
|
||||
return 1;
|
||||
return player_run_bomb_logic(&global.plr, e, &e->args[3], youmu_split_logic);
|
||||
}
|
||||
|
||||
static void youmu_mirror_bomb(Player *plr) {
|
||||
|
|
|
@ -123,9 +123,43 @@ static int youmu_trap(Projectile *p, int t) {
|
|||
}
|
||||
|
||||
static void YoumuSlash(Enemy *e, int t) {
|
||||
t = player_get_bomb_progress(&global.plr, NULL);
|
||||
fade_out(10.0/t+sin(t/10.0)*0.1);
|
||||
}
|
||||
|
||||
static int youmu_slash_logic(void *v, int t, double speed) {
|
||||
Enemy *e = v;
|
||||
TIMER(&t);
|
||||
|
||||
AT(0)
|
||||
global.plr.pos = VIEWPORT_W/5.0 + (VIEWPORT_H - 100)*I;
|
||||
|
||||
FROM_TO(8,20,1)
|
||||
global.plr.pos = VIEWPORT_W + (VIEWPORT_H - 100)*I - exp(-_i/8.0+log(4*VIEWPORT_W/5.0));
|
||||
|
||||
FROM_TO(30, 60, 10) {
|
||||
tsrand_fill(3);
|
||||
create_particle1c("youmu_slice", VIEWPORT_W/2.0 - 150 + 100*_i + VIEWPORT_H/2.0*I - 10-10.0*I + 20*afrand(0)+20.0*I*afrand(1), 0, youmu_common_particle_slice_draw, timeout, 200 / speed)->angle = -10.0+20.0*afrand(2);
|
||||
}
|
||||
|
||||
FROM_TO(40,200,1)
|
||||
if(frand() > 0.7) {
|
||||
tsrand_fill(6);
|
||||
create_particle2c("blast", VIEWPORT_W*afrand(0) + (VIEWPORT_H+50)*I, rgb(afrand(1),afrand(2),afrand(3)), Shrink, timeout_linear, 80 / speed, speed * (3*(1-2.0*afrand(4))-14.0*I+afrand(5)*2.0*I));
|
||||
}
|
||||
|
||||
int tpar = 30;
|
||||
if(t < 30)
|
||||
tpar = t;
|
||||
|
||||
if(t < creal(e->args[0])-60 && frand() > 0.2) {
|
||||
tsrand_fill(3);
|
||||
create_particle2c("smoke", VIEWPORT_W*afrand(0) + (VIEWPORT_H+100)*I, rgba(0.4,0.4,0.4,afrand(1)*0.2 - 0.2 + 0.6*(tpar/30.0)), PartDraw, youmu_common_particle_spin, 300 / speed, speed * (-7.0*I+afrand(2)*1.0*I));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int youmu_slash(Enemy *e, int t) {
|
||||
if(t > creal(e->args[0]))
|
||||
return ACTION_DESTROY;
|
||||
|
@ -136,34 +170,7 @@ static int youmu_slash(Enemy *e, int t) {
|
|||
return ACTION_DESTROY;
|
||||
}
|
||||
|
||||
TIMER(&t);
|
||||
|
||||
AT(0)
|
||||
global.plr.pos = VIEWPORT_W/5.0 + (VIEWPORT_H - 100)*I;
|
||||
|
||||
FROM_TO(8,20,1)
|
||||
global.plr.pos = VIEWPORT_W + (VIEWPORT_H - 100)*I - exp(-_i/8.0+log(4*VIEWPORT_W/5.0));
|
||||
|
||||
FROM_TO(30, 60, 10) {
|
||||
tsrand_fill(3);
|
||||
create_particle1c("youmu_slice", VIEWPORT_W/2.0 - 150 + 100*_i + VIEWPORT_H/2.0*I - 10-10.0*I + 20*afrand(0)+20.0*I*afrand(1), 0, youmu_common_particle_slice_draw, timeout, 200)->angle = -10.0+20.0*afrand(2);
|
||||
}
|
||||
|
||||
FROM_TO(40,200,1)
|
||||
if(frand() > 0.7) {
|
||||
tsrand_fill(6);
|
||||
create_particle2c("blast", VIEWPORT_W*afrand(0) + (VIEWPORT_H+50)*I, rgb(afrand(1),afrand(2),afrand(3)), Shrink, timeout_linear, 80, 3*(1-2.0*afrand(4))-14.0*I+afrand(5)*2.0*I);
|
||||
}
|
||||
|
||||
int tpar = 30;
|
||||
if(t < 30)
|
||||
tpar = t;
|
||||
|
||||
if(t < creal(e->args[0])-60 && frand() > 0.2) {
|
||||
tsrand_fill(3);
|
||||
create_particle2c("smoke", VIEWPORT_W*afrand(0) + (VIEWPORT_H+100)*I, rgba(0.4,0.4,0.4,afrand(1)*0.2 - 0.2 + 0.6*(tpar/30.0)), PartDraw, youmu_common_particle_spin, 300, -7.0*I+afrand(2)*1.0*I);
|
||||
}
|
||||
return 1;
|
||||
return player_run_bomb_logic(&global.plr, e, &e->args[3], youmu_slash_logic);
|
||||
}
|
||||
|
||||
static void youmu_haunting_power_shot(Player *plr, int p) {
|
||||
|
|
|
@ -313,7 +313,7 @@ void stage_draw_scene(StageInfo *stage) {
|
|||
|
||||
// fade the background during bomb
|
||||
if(global.frames - global.plr.recovery < 0) {
|
||||
float t = BOMB_RECOVERY - global.plr.recovery + global.frames;
|
||||
float t = player_get_bomb_progress(&global.plr, NULL);
|
||||
float fade = 1;
|
||||
|
||||
if(t < BOMB_RECOVERY/6)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue