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:
Andrei Alexeyev 2017-10-17 23:27:35 +03:00
parent e6dc4f4362
commit 7f72c1c09a
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
8 changed files with 247 additions and 68 deletions

View file

@ -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);

View file

@ -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:

View file

@ -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);

View file

@ -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;
}

View file

@ -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++) {

View file

@ -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) {

View file

@ -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) {

View file

@ -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)