// cutscene igor void ai_npc_igor(Object *o) { switch(o->state) { case 0: // init, standing/panting o->xinertia = 0; o->frame = 0; o->animtimer = 0; o->state = 1; case 1: ANIMATE(5, 0, 1); break; case 2: // walking o->state = 3; o->frame = 2; o->animtimer = 0; case 3: ANIMATE(3, 2, 5); XMOVE(0x200); break; case 4: // punch o->xinertia = 0; o->state = 5; o->timer = 0; case 5: o->frame = 6; if (++o->timer > 10) { o->timer = 0; o->state = 6; sound(SND_EXPL_SMALL); } break; case 6: o->frame = 7; if (++o->timer > 8) { o->state = 0; o->frame = 0; } break; case 7: o->state = 1; break; } o->yinertia += 0x40; LIMITY(0x5FF); }
static bool run_bute_defeated(Object *o, int hp) { if (o->hp <= (1000 - hp)) { if (o->type == OBJ_MESA) { o->ChangeType(OBJ_MESA_DYING); } else { o->x -= (4 << CSF); o->y -= (4 << CSF); o->ChangeType(OBJ_BUTE_DYING); sound(SND_ENEMY_SQUEAK); XMOVE(-0x100); } ai_bute_dying(o); return 1; } return 0; }
// white sparky thing that moves along floor throwing out bones, // spawned he hits the ground. // similar to the red smoke-spawning ones from Undead Core. void ai_ballos_bone_spawner(Object *o) { switch(o->state) { case 0: { sound(SND_MISSILE_HIT); o->state = 1; XMOVE(0x400); } case 1: { ANIMATE(1, 0, 2); o->timer++; if ((o->timer % 6) == 1) { int xi = (random(4, 16) << CSF) / 8; if (o->dir == LEFT) xi = -xi; CreateObject(o->x, o->y, OBJ_BALLOS_BONE, xi, -0x400); sound(SND_BLOCK_DESTROY); } if ((o->blockl && o->xinertia < 0) || \ (o->blockr && o->xinertia > 0)) { o->Delete(); } } break; } }
void ai_red_bat(Object *o) { ANIMATE(1, 0, 2); switch(o->state) { case 0: { o->state = 1; o->ymark = o->y; o->timer = random(0, 50); } case 1: { if (--o->timer < 0) { o->state = 2; o->yinertia = 0x400; } else break; } case 2: { o->yinertia += (o->y < o->ymark) ? 0x10 : -0x10; LIMITY(0x300); XMOVE(0x100); } break; } if (o->x < 0 || o->x > (map.xsize * TILE_W) << CSF) { effect(o->CenterX(), o->CenterY(), EFFECT_BOOMFLASH); o->Delete(); } }
// this is her phasy teleport out/teleport in effect // it's a 2-dir interlaced picture of her with each dir // containing only half the lines. We spawn two objects // in opposite dirs and then separate them. void ai_misery_phase(Object *o) { XMOVE(0x400); if (++o->timer >= 8) o->Delete(); }
void ai_ma_pignon(Object *o) { /*debug("state: %d", o->state); debug("timer: %d", o->timer); debug("timer2: %d", o->timer2); debug("timer3: %d", o->timer3); debug("xinertia: %d", o->xinertia); debug("yinertia: %d", o->yinertia); debug("frame: %d", o->frame); */ switch(o->state) { case 0: { o->SnapToGround(); o->state = 1; } case 1: { FACEPLAYER; o->frame = 0; randblink(o); } break; case MP_Fight_Start: // set by script { o->state = MP_BaseState; o->timer = 0; o->timer2 = 0; o->flags |= FLAG_SHOOTABLE; } case MP_BaseState: { FACEPLAYER; o->damage = 1; o->frame = 0; if (++o->timer > 4) { o->timer = 0; o->state = MP_Jump; if (++o->timer3 > 12) { o->timer3 = 0; o->state = MP_CloneAttack; } } } break; case MP_Jump: // pause a moment and jump { o->frame = 2; if (++o->timer > 4) { o->state = MP_In_Air; o->frame = 3; o->xinertia = random(-0x400, 0x400); o->yinertia = -0x800; sound(SND_ENEMY_JUMP); o->timer2++; } } break; case MP_In_Air: // jumping or falling after clone attack { o->yinertia += 0x80; // for when falling back onscreen after clone attack if (o->y > (8 * TILE_H) << CSF) o->flags &= ~FLAG_IGNORE_SOLID; else o->blockd = false; // bounce off walls if ((o->blockl && o->xinertia < 0) || \ (o->blockr && o->xinertia > 0)) { o->xinertia = -o->xinertia; } FACEPLAYER; // select frame if (o->yinertia < -0x200) { o->frame = 3; } else if (o->yinertia > 0x200) { o->frame = 4; } else { o->frame = 0; } if (o->blockd && o->yinertia > 0) { o->state = MP_Landed; o->timer = 0; o->frame = 2; o->xinertia = 0; } if (o->timer2 > 4) { if (player->y < (o->y + 0x800)) { o->state = MP_ChargeAttack; o->timer = 0; o->xinertia = 0; o->yinertia = 0; } } } break; case MP_Landed: { o->frame = 2; if (++o->timer > 4) { o->state = MP_BaseState; } } break; case MP_ChargeAttack: // charge attack { o->frame = 5; if (++o->timer > 10) { o->state = MP_ChargeAttack+1; o->frame = 6; XMOVE(0x5ff); sound(SND_FUNNY_EXPLODE); o->flags &= ~FLAG_SHOOTABLE; o->flags |= FLAG_INVULNERABLE; o->damage = 10; } } break; case MP_ChargeAttack+1: // in-air during charge attack { ANIMATE(0, 6, 7); if ((o->xinertia < 0 && o->blockl) || \ (o->xinertia > 0 && o->blockr)) { o->state = MP_Hit_Wall; } } break; case MP_Hit_Wall: // hit wall { o->state++; o->timer = 0; quake(16); } case MP_Hit_Wall+1: { o->damage = 4; ANIMATE(0, 6, 7); if ((++o->timer % 6) == 0) { int x = (random(4, 16) * TILE_W) << CSF; CreateObject(x, (16 << CSF), OBJ_MA_PIGNON_ROCK); } if (o->timer > 30) { o->timer2 = 0; o->state = MP_In_Air; o->flags |= FLAG_SHOOTABLE; o->flags &= ~FLAG_INVULNERABLE; o->damage = 3; } } break; case MP_CloneAttack: // begin clone-attack sequence { o->state++; o->frame = 9; FACEPLAYER; } case MP_CloneAttack+1: // walk at player before attack { ANIMATE(0, 9, 11); XMOVE(0x400); if (pdistlx(3 << CSF)) { o->state = MP_Fly_Up; o->timer = 0; o->frame = 2; o->xinertia = 0; } } break; case MP_Fly_Up: // jump and fly up for clone attack { o->frame = 2; if (++o->timer > 4) { o->state++; o->frame = 12; o->yinertia = -0x800; sound(SND_FUNNY_EXPLODE); o->flags |= FLAG_IGNORE_SOLID; o->flags &= ~FLAG_SHOOTABLE; o->flags |= FLAG_INVULNERABLE; o->damage = 10; } } break; case MP_Fly_Up+1: // flying up { ANIMATE(0, 12, 13); if (o->y < (16<<CSF)) o->state = MP_Spawn_Clones; } break; case MP_Spawn_Clones: // offscreen, spawning clones { o->yinertia = 0; o->state++; o->timer = 0; quake(10); } case MP_Spawn_Clones+1: { ANIMATE(0, 12, 13); if ((++o->timer % 6) == 0) { int x = (random(4, 16) * TILE_W) << CSF; CreateObject(x, (16 << CSF), OBJ_MA_PIGNON_CLONE); } if (o->timer > 30) { o->timer2 = 0; o->state = MP_In_Air; // fall back down to ground o->flags |= FLAG_SHOOTABLE; o->flags &= ~FLAG_INVULNERABLE; } } break; case MP_Defeated: // defeated -- set by script { KillObjectsOfType(OBJ_MA_PIGNON_CLONE); o->flags &= ~FLAG_SHOOTABLE; o->state++; o->timer = 0; o->frame = 8; o->damage = 0; } case MP_Defeated+1: { o->yinertia += 0x20; if (o->blockd) { o->xinertia *= 7; o->xinertia /= 8; } o->display_xoff = (++o->timer & 1); } break; } // ma pignon is invulnerable to missiles and Blade. if (o->state >= MP_Fight_Start && o->state < MP_Defeated) { // ....he's invulnerable anyway during these two states so don't mess with that. if (o->state != MP_ChargeAttack+1 && o->state != MP_Fly_Up+1) { bool found_weapons = false; if (o->type != OBJ_MA_PIGNON_CLONE) { Object *c; FOREACH_OBJECT(c) { if (c->type == OBJ_MISSILE_SHOT || \ c->type == OBJ_SUPERMISSILE_SHOT || \ c->type == OBJ_MISSILE_BOOM_SPAWNER || \ c->type == OBJ_BLADE12_SHOT || \ c->type == OBJ_BLADE3_SHOT || \ c->type == OBJ_BLADE_SLASH) { found_weapons = true; break; } } } if (found_weapons) { o->flags &= ~FLAG_SHOOTABLE; o->flags |= FLAG_INVULNERABLE; } else { o->flags |= FLAG_SHOOTABLE; o->flags &= ~FLAG_INVULNERABLE; } }
void ai_npc_sue(Object *o) { switch(o->state) { case 0: // stand and blink o->timer = 0; o->frame = 0; o->xinertia = 0; o->sue.carried_by = NULL; randblink(o, 1, 4); break; case 3: // walking case 4: // walking case 5: // face away ai_generic_npc(o); break; // got punched by Igor case 6: o->state = 7; o->frame = 7; o->timer = 0; sound(SND_ENEMY_SQUEAK); case 7: if (++o->timer > 10) o->state = 0; break; // got punched extra hard by Igor // flys through air backwards and crashes case 8: o->state = 9; o->frame = 7; o->timer = 0; sound(SND_ENEMY_SQUEAK); o->yinertia = -0x200; XMOVE(-0x400); case 9: if (++o->timer > 3 && o->blockd) { o->state = 10; o->dir ^= 1; } break; case 10: o->xinertia = 0; o->frame = 8; break; // punching the air (when she tells Igor "I'm not afraid of you!") case 11: o->state = 12; o->timer = 0; o->animframe = 0; o->animtimer = 0; case 12: { const static int punchframes[] = { 10, 0 }; o->animate_seq(8, punchframes, 2); } break; // picked up & carried away by Igor case 13: o->frame = 11; o->xinertia = 0; o->yinertia = 0; o->state = 14; // find Igor o->sue.carried_by = FindObjectByID2(501); if (!o->sue.carried_by) NX_ERR("-- Could not find entity carrying Sue (ID 501)\n"); case 14: // being carried--see aftermove routine o->frame = 9; break; // spawn red crystal and call it to us (Undead Core intro) case 15: { o->PushBehind(dr_create_red_crystal(o->x+(128<<CSF), o->y)); o->state = 16; o->xinertia = 0; o->frame = 0; } case 16: { crystal_xmark = o->x - (18<<CSF); crystal_ymark = o->y - (8<<CSF); } break; case 17: // look up (still followed by red crystal) { o->xinertia = 0; o->frame = 12; crystal_xmark = o->x; crystal_ymark = o->y - (8<<CSF); } break; // run away from DOCTOR_GHOST and hide behind player case 20: { o->state = 21; o->frame = 2; o->animtimer = 0; } case 21: { ANIMATE(2, 2, 5); XMOVE(0x400); if (o->x < player->x - (8<<CSF)) { o->dir = RIGHT; o->state = 0; } } break; // run, during "we've got to get out of here" post-undead core cutscene. case 30: { o->state = 31; o->frame = 2; o->animtimer = 0; } case 31: { ANIMATE(2, 2, 5); XMOVE(0x400); } break; case 40: // she jumps off the island { o->state = 41; o->frame = 9; o->yinertia = -0x400; } break; /*default: NX_ERR("-- Sue entered unhandled state %d (0x%02x)\n", o->state, o->state); exit(1);*/ } o->yinertia += 0x40; LIMITX(0x400); LIMITY(0x5ff); }
void ai_toroko(Object *o) { switch(o->state) { case 0: // stand and blink o->frame = 0; o->xinertia = 0; randblink(o, 1, 4); break; case 3: // run away!! o->state = 4; o->frame = 1; o->animtimer = 0; case 4: ANIMATE(2, 1, 4); if (o->blockl) { o->dir = RIGHT; o->xinertia = 0x200; } if (o->blockr) { o->dir = LEFT; o->xinertia = -0x200; } XMOVE(0x400); break; case 6: // hop and run away!! o->state = 7; o->frame = 1; o->animtimer = 0; o->yinertia = -0x400; o->toro.left_ground = false; case 7: ANIMATE(2, 1, 4); XMOVE(0x100); if (!o->toro.left_ground) { if (!o->blockd) o->toro.left_ground = true; } else { if (o->blockd) { o->toro.left_ground = false; o->state = 3; } } break; // small hop straight up/down from Balrog // shaking the ground, used in Shack case 8: o->frame = 1; o->timer = 0; o->state = 9; o->yinertia = -0x200; o->toro.left_ground = false; case 9: { if (!o->toro.left_ground) { if (!o->blockd) o->toro.left_ground = true; } else { if (o->blockd) { o->toro.left_ground = false; o->state = 0; } } } break; case 10: // eeks and falls down o->state = 11; o->frame = 5; o->yinertia = -(2 << CSF); sound(SND_ENEMY_SQUEAK); XMOVE(0x100); break; case 11: // falling down if (o->blockd) { o->state = 12; o->frame = 6; o->flags |= FLAG_SCRIPTONACTIVATE; o->xinertia = 0; } break; } o->yinertia += 0x40; LIMITX(0x400); LIMITY(0x5ff); }
// regular NPC curly void ai_curly(Object *o) { switch(o->state) { case 0: // state 0: stand and do nothing o->frame = 0; o->flags |= FLAG_SCRIPTONACTIVATE; // needed for after Almond battle case 1: // important that state 1 does not change look-away frame for Drain cutscene if (o->frame != 12) o->frame = 0; o->xinertia = 0; break; case 3: // state 3: walk forward case 10: // state 10: walk to player and stop { if (o->state == 10) FACEPLAYER; o->state++; o->animtimer = 0; o->frame = 0; } case 4: case 11: { if (o->state == 11 && pdistlx(20<<CSF)) { o->state = 0; break; } ANIMATE(5, 0, 3); if (!o->blockd) o->frame = 3; XMOVE(0x200); } break; // state 5: curly makes a "kaboom", then looks sad. case 5: o->state = 6; SmokeClouds(o, 8, 0, 0); case 6: o->frame = 16; break; case 20: // face away o->xinertia = 0; o->frame = 12; break; case 21: // look up o->xinertia = 0; o->frame = 4; break; case 30: // state 30: curly goes flying through the air and is knocked out { o->state = 31; o->frame = 14; o->timer2 = 0; o->yinertia = -0x400; XMOVE(-0x200); } case 31: { if (o->blockd && o->yinertia >= 0) o->state = 32; else break; } case 32: // state 32: curly is laying knocked out { o->frame = 15; o->xinertia = 0; } break; // walk backwards from collapsing wall during final cutscene case 70: { o->state = 71; o->timer = 0; o->frame = 1; o->animtimer = 0; } case 71: { XMOVE(-0x100); ANIMATE(8, 0, 3); } break; } o->yinertia += 0x40; LIMITY(0x5ff); }
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; }
// boss-fight igor void ai_boss_igor(Object *o) { enum { STATE_INIT = 0, STATE_STAND, STATE_BEGIN_ATTACK, STATE_WALK, STATE_JUMPING, STATE_LANDED, STATE_PUNCH, STATE_PUNCH_2, STATE_PUNCH_3, STATE_MOUTH_BLAST, STATE_MOUTH_BLAST_2 }; switch(o->state) { case STATE_INIT: { o->damage = 0; o->xinertia = 0; o->state = STATE_STAND; o->frame = 0; o->animtimer = 0; } case STATE_STAND: { ANIMATE(5, 0, 1); if (++o->timer > 50) { o->state = STATE_BEGIN_ATTACK; } } break; case STATE_BEGIN_ATTACK: { o->state = STATE_WALK; o->frame = 2; o->animtimer = 0; o->timer = 0; FACEPLAYER; o->igor.fireattack = false; // when health is less than halfway, then use // the mouth blast attack every third time. if (++o->timer2 >= 3 && \ o->hp <= (objprop[o->type].initial_hp / 2)) { o->timer2 = 0; o->igor.fireattack = true; o->dir ^= 1; // walk away from player } } // fall thru case STATE_WALK: { ANIMATE(3, 2, 5); XMOVE(0x200); if (o->igor.fireattack) { // begin mouth-blast attack if (++o->timer > 16) { o->state = STATE_MOUTH_BLAST; o->xinertia = 0; o->frame = 10; } } else { if (o->dir == LEFT) { if (o->x <= player->x + player->Width()) o->state = STATE_PUNCH; } else { if (o->x + o->Width() >= player->x) o->state = STATE_PUNCH; } // if we don't reach him after a while, do a jump if (++o->timer > 50) { o->frame = 10; o->yinertia = -0x400; o->state = STATE_JUMPING; o->timer = 0; o->xinertia *= 2; o->xinertia /= 3; o->damage = 2; } } } break; case STATE_PUNCH: { o->xinertia = 0; o->state = STATE_PUNCH_2; o->frame = 6; o->timer = 0; } case STATE_PUNCH_2: { if (++o->timer > 12) { sound(SND_EXPL_SMALL); // sprite appears identical, but has a wider bounding box. o->sprite = SPR_IGOR_PUNCHING; o->damage = 5; o->state = STATE_PUNCH_3; o->frame = 7; o->timer = 0; } } break; case STATE_PUNCH_3: { if (++o->timer > 10) { o->state = STATE_INIT; o->frame = 0; o->damage = 0; // return to normal-size bounding box o->sprite = SPR_IGOR; } } break; case STATE_JUMPING: { if (o->blockd) { sound(SND_QUAKE); SmokeSide(o, 4, DOWN); o->state = STATE_LANDED; o->frame = 11; o->timer = 0; } } break; case STATE_LANDED: { o->xinertia = 0; if (++o->timer > 10) { o->state = STATE_INIT; o->frame = 0; o->damage = 0; } } break; case STATE_MOUTH_BLAST: { FACEPLAYER; o->timer = 0; o->state++; } case STATE_MOUTH_BLAST_2: { o->timer++; // flash mouth o->frame = 8; if (o->timer > 50 && (o->timer & 2)) o->frame = 9; // fire shots if (o->timer > 100) { if ((o->timer % 6) == 1) { sound(SND_BLOCK_DESTROY); Object *shot = SpawnObjectAtActionPoint(o, OBJ_IGOR_SHOT); int angle = (o->dir == LEFT) ? 136 : 248; angle += random(-16, 16); ThrowObjectAtAngle(shot, angle, 0x580); } if (o->timer > 132) // fires 6 shots { o->state = STATE_INIT; o->timer = 0; } } } break; } o->yinertia += 0x40; LIMITY(0x5ff); }
void ai_bute_sword(Object *o) { if (run_bute_defeated(o, BUTE_HP)) return; switch(o->state) { case 0: { o->flags |= (FLAG_SHOOTABLE | FLAG_INVULNERABLE); o->nxflags |= NXFLAG_FOLLOW_SLOPE; o->damage = 0; o->state = 1; } case 1: // lying in wait { FACEPLAYER; if (pdistlx(128<<CSF) && \ pdistly2(128<<CSF, 16<<CSF)) { o->state = 10; } } break; // wait a moment, then start running at player case 10: { o->flags |= FLAG_INVULNERABLE; o->damage = 0; o->frame = 0; o->state = 11; o->timer = 0; } case 11: { if (++o->timer > 30) { o->state = 20; o->timer = 0; } } break; // run at player and jump case 20: { o->flags &= ~FLAG_INVULNERABLE; o->state = 21; FACEPLAYER; } case 21: { ANIMATE(3, 0, 1); XMOVE(0x400); if (pdistlx(40<<CSF)) { o->xinertia /= 2; o->yinertia = -0x300; o->state = 30; o->frame = 2; // sword back, jumping sound(SND_ENEMY_JUMP); } else if (++o->timer > 50) { // timeout, p got away o->state = 10; o->xinertia = 0; } } break; // jumping up case 30: { if (o->yinertia > -0x80) { o->frame = 3; // sword swipe fwd o->damage = 9; o->state = 31; o->timer = 0; } } break; // swiping sword, in air case 31: { if (++o->timer > 2) { o->timer = 0; o->frame = 4; // sword down, in front } if (o->blockd && o->yinertia > 0) { o->xinertia = 0; o->damage = 3; o->state = 32; o->timer = 0; } } break; case 32: { if (++o->timer > 30) { o->state = 10; o->timer = 0; } } break; } o->yinertia += 0x20; LIMITY(0x5ff); }
// handles his "looping" flight/rush attacks static void run_flight(Object *o) { switch(o->state) { // flying left or right case BP_FLY_LR: { o->state++; o->animtimer = 0; o->frame = 6; // flying horizontally o->yinertia = 0; o->damage = DMG_RUSH; FACEPLAYER; XMOVE(RUSH_SPEED); } case BP_FLY_LR+1: { ANIMATE(1, 6, 7); // smacked into wall? if ((o->blockl && o->dir == LEFT) || \ (o->blockr && o->dir == RIGHT)) { o->xinertia = 0; o->state = BP_HIT_WALL; o->damage = DMG_NORMAL; o->timer = 0; megaquake(10); } // reached player? // this has to be AFTER smacked-into-wall check for proper behavior // if player stands in spikes at far left/right of arena. if (pdistlx(RUSH_DIST)) o->state = BP_PREPARE_FLY_UD; } break; // smacked into wall while flying L/R case BP_HIT_WALL: { o->frame = 6; if (++o->timer > 30) { if (o->timer2 <= 3) o->state = BP_PREPARE_FLY_LR; else o->state = BP_RETURN_TO_GROUND; } } break; // flying up case BP_FLY_UP: { o->state++; o->timer = 0; o->animtimer = 0; o->frame = 8; // vertical flight o->dir = LEFT; // up-facing frame o->yinertia = -RUSH_SPEED; o->xinertia = 0; o->damage = DMG_RUSH; } case BP_FLY_UP+1: { ANIMATE(1, 8, 9); // hit ceiling? (to make this happen, break his loop and jump ABOVE him // while he is in the air, at the part where he would normally be // coming back down at you). if (o->blocku) { o->state = BP_HIT_CEILING; o->damage = DMG_NORMAL; o->timer = 0; SmokeXY(o->CenterX(), o->Top(), 8); megaquake(10); spawn_bones(o, UP); } // reached player? (this check here isn't exactly the same as pdistly; // it's important that it checks the player's top and not his center). if ((abs(player->y - o->y) < RUSH_DIST) && o->timer2 < 4) o->state = BP_PREPARE_FLY_LR; } break; case BP_HIT_CEILING: // hit ceiling { o->frame = 8; if (++o->timer > 30) { if (o->timer2 <= 3) o->state = BP_PREPARE_FLY_LR; else o->state = BP_RETURN_TO_GROUND; } } break; // flying down case BP_FLY_DOWN: { o->state++; o->timer = 0; o->animtimer = 0; o->frame = 8; // vertical flight o->dir = RIGHT; // down-facing frame o->yinertia = RUSH_SPEED; o->xinertia = 0; o->damage = DMG_RUSH; } case BP_FLY_DOWN+1: { ANIMATE(1, 8, 9); if (o->blockd) { o->state = BP_HIT_FLOOR; o->damage = DMG_NORMAL; o->timer = 0; SmokeXY(o->CenterX(), o->Bottom(), 8); megaquake(10); spawn_bones(o, DOWN); FACEPLAYER; } if (pdistly(RUSH_DIST) && o->timer2 < 4) o->state = BP_PREPARE_FLY_LR; } break; case BP_HIT_FLOOR: // hit floor { o->frame = 3; if (++o->timer > 30) { o->state = BP_FIGHTING_STANCE; o->timer = 120; } } break; // come back to ground while facing head on case BP_RETURN_TO_GROUND: { o->frame = 4; // face screen frame o->dir = LEFT; // non-flashing version o->state++; } case BP_RETURN_TO_GROUND+1: { ANIMATE(1, 4, 5); o->yinertia += 0x40; LIMITY(0x5ff); if (o->blockd && o->yinertia >= 0) { o->state++; o->timer = 0; o->frame = 3; // landed FACEPLAYER; } } break; case BP_RETURN_TO_GROUND+2: { o->xinertia *= 3; o->xinertia /= 4; if (++o->timer > 10) { o->state = BP_FIGHTING_STANCE; o->timer = 140; } } break; } }
// there is an apparent bug on the sprite sheet for this monster: // right-facing frame 1 is a duplicate of frame 0, // so the mouth-twitch animation does not work when // the frog is facing right. void ai_frog(Object *o) { switch(o->state) { case 0: { o->timer = 0; o->xinertia = 0; o->yinertia = 0; // non-normal dirs are used to indicate that this frog was // spawned by balfrog: we are falling out of ceiling during fight if (o->dir != LEFT && o->dir != RIGHT) { o->dir = random(0, 1) ? LEFT : RIGHT; o->flags |= FLAG_IGNORE_SOLID; o->state = 3; o->frame = 2; } else { o->flags &= ~FLAG_IGNORE_SOLID; o->state = 1; } } case 1: // standing { o->timer++; if (!random(0, 50)) { // mouth-flitter animation o->state = 2; o->frame = 0; o->timer = 0; o->animtimer = 0; } } break; case 2: // mouth flitter { ANIMATE(2, 0, 1); if (++o->timer > 18) o->state = 1; } break; case 3: // falling out of ceiling during balfrog fight { if (++o->timer > 40) { o->flags &= ~FLAG_IGNORE_SOLID; if (o->blockd) { o->state = 0; o->frame = 0; o->timer = 0; } } } break; case 10: // jumping case 11: { if (o->blockl && o->xinertia < 0) { o->dir = RIGHT; o->xinertia = -o->xinertia; } if (o->blockr && o->xinertia > 0) { o->dir = LEFT; o->xinertia = -o->xinertia; } if (o->blockd) { o->state = 0; o->frame = 0; o->timer = 0; } } break; } // random jumping, and jump when shot if (o->state < 3 && o->timer > 10) { bool dojump = false; if (o->shaketime) { dojump = true; } else if (pdistlx(0x14000) && pdistly(0x8000)) { if (!random(0, 50)) { dojump = true; } } if (dojump) { FACEPLAYER; o->state = 10; o->frame = 2; o->yinertia = -0x5ff; // no jumping sound in cutscenes at ending if (!player->inputs_locked && !player->disabled) sound(SND_ENEMY_JUMP); XMOVE(0x200); } } o->yinertia += 0x80; LIMITY(0x5ff); }
void ai_critter(Object *o) { switch(o->state) { case 0: { if (o->type == OBJ_POWER_CRITTER) { // altered physics for Power Critter o->critter.jumpheight = 0x2800; o->critter.jumpgrav = 0x1C; o->critter.falldmg = 12; } else { o->critter.jumpheight = 0; o->critter.jumpgrav = 0x40; o->critter.falldmg = 3; if (o->type == OBJ_CRITTER_HOPPING_BLUE || // first cave o->type == OBJ_CRITTER_HOPPING_GREEN || // egg 1 o->type == OBJ_CRITTER_HOPPING_AQUA || // egg 2 o->type == OBJ_CRITTER_HOPPING_RED) // last cave { o->critter.canfly = false; // critters in egg1 only 2 dmg if (o->type == OBJ_CRITTER_HOPPING_GREEN) o->critter.falldmg = 2; // critters in First Cave don't jump as high if (o->type != OBJ_CRITTER_HOPPING_BLUE) { o->critter.jumpgrav = 0x2C; } } else { // critters are purple in Maze o->sprite = SPR_CRITTER_FLYING_CYAN; o->critter.canfly = true; } } o->state = 1; } //fall thru case 1: { o->frame = 0; if (o->timer >= 8) { int attack_dist = (o->critter.canfly) ? (96 << CSF) : (64 << CSF); // close enough to attack? if (pdistlx(attack_dist) && pdistly2(96<<CSF, 48<<CSF)) { o->state = 2; o->frame = 0; o->timer = 0; } else if (pdistlx(attack_dist + (32<<CSF)) && pdistly2(128<<CSF, 48<<CSF)) { // no, but close enough to look up at "attention" and watch player FACEPLAYER; o->frame = 1; o->timer = 8; // reset timer to stop watching } else { // once a little time has passed stop watching him if he turns his back if ((o->x > player->x && player->dir==LEFT) || \ (o->x < player->x && player->dir==RIGHT)) { if (++o->timer >= 150) { o->frame = 0; o->timer = 8; } } else o->timer = 8; } } else { o->timer++; } if (o->shaketime) { // attack if shot o->state = 2; o->frame = 0; o->timer = 0; } } break; case 2: // start jump { if (++o->timer > 8) { o->state = 3; o->frame = 2; o->yinertia = -1228; sound(SND_ENEMY_JUMP); FACEPLAYER; XMOVE(0x100); } } break; case 3: // jumping { // enter flying phase as we start to come down or // if we hit the ceiling. if (o->yinertia > 0x100 || o->blocku) { // during flight we will sine-wave oscilliate around this position o->ymark = (o->y - o->critter.jumpheight); o->state = 4; o->frame = 3; o->timer = 0; } else { if (o->blockd && o->yinertia >= 0) { // jumped onto a platform before we got to fly--land immediately goto landed; } break; } } // fall-thru case 4: // flying { FACEPLAYER; // time to come down yet? // (come down immediately if we are not one of the flying critters) if (!o->critter.canfly || \ o->blockl || o->blockr || o->blocku || \ ++o->timer > 100) { o->damage = o->critter.falldmg; // increased damage if falls on player o->state = 5; o->frame = 2; o->yinertia /= 2; } else { // run the propeller ANIMATE(0, 3, 5); if ((o->timer & 3)==1) sound(SND_CRITTER_FLY); if (o->blockd) o->yinertia = -0x200; } } break; case 5: // coming down from flight { if (o->blockd) { // landed landed: ; o->damage = 2; // reset to normal damage o->state = 1; o->frame = 0; o->timer = 0; o->xinertia = 0; sound(SND_THUD); } } break; } if (o->state == 4) // flying { // fly towards player o->xinertia += (o->x > player->x) ? -0x20 : 0x20; // sine-wave oscillate o->yinertia += (o->y > o->ymark) ? -0x10 : 0x10; LIMITX(0x200); LIMITY(0x200); } else if (o->state == 3 && o->yinertia < 0) // jumping up { o->yinertia += o->critter.jumpgrav; } else { o->yinertia += 0x40; } LIMITY(0x5ff); }