// This object is responsible for collapsing the walls in the final best-ending sequence. // All the original object does is collapse one tile further every 101 frames. // However, since it's triggered at the beginning of the cinematic and then is let to run // through almost the entire thing it needs to be sync'd really-really perfect with a // number of other systems; the textboxes, etc. // // I spent several hours trying to get my events to run in perfect frame-by-frame // exactness with the original engine, and found several things that were slightly off. // However, I've decided that even if I got it absolutely perfect, it's too liable to // get broken by some minor innocent change in the future, and requires too much of // the engine to be tuned just so. // // So, I've added some event-based triggers to the object, that are NOT technically supposed // to be there. These will make extra sure that nothing embarrassing happens during this great // finale, such as the walls being one tile too far at one point, or even worse, having // them collapse onto Balrog before he makes it to the exit. Because there are no triggers // in the script and I can't change the script, I had to do a bit of sneaky spying on program // state to implement them. void ai_wall_collapser(Object *o) { int y; switch(o->state) { case 0: { o->invisible = true; o->timer = 0; o->state = 1; } break; case 10: // trigger { if (++o->timer > 100) { o->timer2++; o->timer = 0; int xa = (o->x >> CSF) / TILE_W; int ya = (o->y >> CSF) / TILE_H; for(y=0;y<20;y++) { // pushing the smoke behind all objects prevents it from covering // up the NPC's on the collapse just before takeoff. map_ChangeTileWithSmoke(xa, ya+y, 109, 4, false, lowestobject); } sound(SND_BLOCK_DESTROY); quake(20); if (o->dir == LEFT) o->x -= (TILE_W << CSF); else o->x += (TILE_W << CSF); // reached the solid tile in the center of the throne. // it isn't supposed to cover this tile until after Curly // says we're gonna get crushed. if (o->timer2 == 6) o->state = 20; // balrog is about to take off/rescue you. if (o->timer2 == 9) o->state = 30; } } break; // "gonna get crushed" event case 20: { // wait for text to come up if (textbox.IsVisible()) o->state = 21; } break; case 21: { // wait for text to dismiss, then tile immediately collapses if (!textbox.IsVisible()) { o->state = 10; o->timer = 1000; } } break; // balrog is about to take off. the video I took shows that // the walls are supposed to collapse into your space on the // exact same frame that he breaks the first ceiling tile. case 30: { o->linkedobject = Objects::FindByType(OBJ_BALROG_DROP_IN); if (o->linkedobject) o->state = 31; } break; case 31: { //debug("%x", o->linkedobject->y); if (o->linkedobject && o->linkedobject->y <= 0x45800) { o->state = 10; o->timer = 1000; } } break; }
void ai_balrog(Object *o) { bool fall = true; // he is greenish when he first appears in Gum Room if (DoesCurrentStageUseSpriteset(NPCSET_FROG)) o->sprite = SPR_BALROG_GREEN; switch(o->state) { case 0: { o->flags &= ~FLAG_IGNORE_SOLID; o->xinertia = 0; o->balrog.smoking = false; o->frame = 0; randblink(o, 4, 8); } break; case 10: // he jumps and flys away o->xinertia = 0; o->frame = 2; o->timer = 0; o->state++; case 11: { if (++o->timer <= 20) break; o->state++; o->yinertia = -0x800; o->flags |= FLAG_IGNORE_SOLID; } case 12: { fall = false; o->frame = 3; o->yinertia -= 0x10; if (o->y < 0) { o->Delete(); sound(SND_QUAKE); game.quaketime = 30; } } break; // he looks shocked and shakes, then flys away // used when he is "hit by something" case 20: { o->state = 21; o->frame = 5; o->xinertia = 0; o->timer = o->timer2 = 0; SmokeClouds(o, 4, 8, 8); sound(SND_BIG_CRASH); o->balrog.smoking = 1; } case 21: { o->timer2++; o->x += ((o->timer2 >> 1) & 1) ? (1<<CSF) : -(1<<CSF); if (++o->timer > 100) o->state = 10; o->yinertia += 0x20; LIMITY(0x5ff); } break; case 30: // he smiles for a moment o->frame = 6; o->timer = 0; o->state = 31; case 31: if (++o->timer > 100) o->state = o->frame = 0; break; // flashing white (spell casted on him) // this only works in Gum Room before balfrog fight, as the normal // non-greenish spritesheet doesn't include the required frame. case 40: o->state = 41; o->animtimer = 0; o->animframe = 0; case 41: { static const int flash_seq[] = { 5, 7 }; o->animate_seq(1, flash_seq, 2); } break; case 42: o->timer = 0; o->state = 43; case 43: // flashing visibility // (transforming into Balfrog stage boss; // our flashing is interlaced with his) o->timer++; o->invisible = (o->timer & 2) ? false : true; break; case 50: // he faces away o->frame = 8; o->xinertia = 0; break; case 60: // he walks o->state = 61; balrog_walk_init(o); case 61: { balrog_walk_animation(o); XMOVE(0x200); } break; // he is teleported away (looking distressed) // this is when he is sent to Labyrinth at end of Sand Zone case 70: o->xinertia = 0; o->timer = 0; o->frame = 7; o->state++; case 71: if (DoTeleportOut(o, 2)) o->Delete(); break; case 80: // hands up and shakes o->frame = 5; o->state = 81; case 81: { if (++o->timer & 2) o->x += (1 << CSF); else o->x -= (1 << CSF); } break; // fly up and lift Curly & PNPC // (post-Ballos ending scene) case 100: { o->state = 101; o->timer = 0; o->frame = 2; // prepare for jump } case 101: { if (++o->timer > 20) { o->state = 102; o->timer = 0; o->frame = 3; // fly up DeleteObjectsOfType(OBJ_NPC_PLAYER); DeleteObjectsOfType(OBJ_CURLY); CreateObject(0, 0, OBJ_BALROG_PASSENGER, 0, 0, LEFT)->linkedobject = o; CreateObject(0, 0, OBJ_BALROG_PASSENGER, 0, 0, RIGHT)->linkedobject = o; o->yinertia = -0x800; o->flags |= FLAG_IGNORE_SOLID; // so can fly through ceiling fall = false; } } break; case 102: // flying up during escape seq { fall = false; // bust through ceiling int y = ((o->y + (4<<CSF)) >> CSF) / TILE_H; if (y < 35 && y >= 0) { int x = (o->CenterX() >> CSF) / TILE_W; if (map.tiles[x][y] != 0) { // smoke needs to go at the bottom of z-order or you can't // see any of the characters through all the smoke. map_ChangeTileWithSmoke(x, y, 0, 4, false, lowestobject); map_ChangeTileWithSmoke(x-1, y, 0, 4, false, lowestobject); map_ChangeTileWithSmoke(x+1, y, 0, 4, false, lowestobject); megaquake(10, 0); sound(SND_MISSILE_HIT); } } if (o->Bottom() < -(20<<CSF)) { quake(30, 0); o->Delete(); } } break; case 500: // used during Balfrog death scene { fall = false; } break; }