// bobomb.c.inc static struct ObjectHitbox sBobombHitbox = { /* interactType: */ INTERACT_GRABBABLE, /* downOffset: */ 0, /* damageOrCoinValue: */ 0, /* health: */ 0, /* numLootCoins: */ 0, /* radius: */ 65, /* height: */ 113, /* hurtboxRadius: */ 0, /* hurtboxHeight: */ 0, }; void bhv_bobomb_init(void) { o->oGravity = 2.5; o->oFriction = 0.8; o->oBuoyancy = 1.3; o->oInteractionSubtype = INT_SUBTYPE_KICKABLE; } void func_802E5B7C(void) { if (((o->oBehParams >> 8) & 0x1) == 0) { ObjSpawnYellowCoins(o, 1); o->oBehParams = 0x100; set_object_respawn_info_bits(o, 1); } } void BobombExplodeLoop(void) { struct Object *explosion; if (o->oTimer < 5) obj_scale(1.0 + (f32) o->oTimer / 5.0); else { explosion = spawn_object(o, MODEL_EXPLOSION, bhvExplosion); explosion->oGraphYOffset += 100.0f; func_802E5B7C(); create_respawner(MODEL_BLACK_BOBOMB, bhvBobomb, 3000); o->activeFlags = 0; } } void CheckBobombInteractions(void) { set_object_hitbox(o, &sBobombHitbox); if ((o->oInteractStatus & INT_STATUS_INTERACTED) != 0) /* bit 15 */ { if ((o->oInteractStatus & INTERACT_GRABBABLE) != 0) /* bit 1 */ { o->oMoveAngleYaw = gMarioObject->header.gfx.angle[1]; o->oForwardVel = 25.0; o->oVelY = 30.0; o->oAction = BOBOMB_ACT_LAUNCHED; } if ((o->oInteractStatus & INTERACT_TEXT) != 0) /* bit 23 */ o->oAction = BOBOMB_ACT_EXPLODE; o->oInteractStatus = 0; } if (attack_collided_non_mario_object(o) == 1) o->oAction = BOBOMB_ACT_EXPLODE; } void BobombPatrolLoop(void) { UNUSED s8 filler[4]; UNUSED s16 sp22; s16 collisionFlags; sp22 = o->header.gfx.unk38.animFrame; o->oForwardVel = 5.0; collisionFlags = ObjectStep(); if ((ObjLeaveIfMarioIsNearHome(o, o->oHomeX, o->oHomeY, o->oHomeZ, 400) == 1) && (func_802E46C0(o->oMoveAngleYaw, o->oAngleToMario, 0x2000) == 1)) { o->oBobombFuseLit = 1; o->oAction = BOBOMB_ACT_CHASE_MARIO; } ObjCheckFloorDeath(collisionFlags, D_803600E0); } void BobombChaseMarioLoop(void) { UNUSED u8 filler[4]; s16 sp1a, collisionFlags; sp1a = ++o->header.gfx.unk38.animFrame; o->oForwardVel = 20.0; collisionFlags = ObjectStep(); if (sp1a == 5 || sp1a == 16) PlaySound2(SOUND_OBJ_BOBOMB_WALK); obj_turn_toward_object(o, gMarioObject, 16, 0x800); ObjCheckFloorDeath(collisionFlags, D_803600E0); } void BobombLaunchedLoop(void) { s16 collisionFlags = 0; collisionFlags = ObjectStep(); if ((collisionFlags & 0x1) == 1) o->oAction = BOBOMB_ACT_EXPLODE; /* bit 0 */ } void GenericBobombFreeLoop(void) { switch (o->oAction) { case BOBOMB_ACT_PATROL: BobombPatrolLoop(); break; case BOBOMB_ACT_LAUNCHED: BobombLaunchedLoop(); break; case BOBOMB_ACT_CHASE_MARIO: BobombChaseMarioLoop(); break; case BOBOMB_ACT_EXPLODE: BobombExplodeLoop(); break; case BOBOMB_ACT_LAVA_DEATH: if (ObjLavaDeath() == 1) create_respawner(MODEL_BLACK_BOBOMB, bhvBobomb, 3000); break; case BOBOMB_ACT_DEATH_PLANE_DEATH: o->activeFlags = 0; create_respawner(MODEL_BLACK_BOBOMB, bhvBobomb, 3000); break; } CheckBobombInteractions(); if (o->oBobombFuseTimer >= 151) o->oAction = 3; } void StationaryBobombFreeLoop(void) { switch (o->oAction) { case BOBOMB_ACT_LAUNCHED: BobombLaunchedLoop(); break; case BOBOMB_ACT_EXPLODE: BobombExplodeLoop(); break; case BOBOMB_ACT_LAVA_DEATH: if (ObjLavaDeath() == 1) create_respawner(MODEL_BLACK_BOBOMB, bhvBobomb, 3000); break; case BOBOMB_ACT_DEATH_PLANE_DEATH: o->activeFlags = 0; create_respawner(MODEL_BLACK_BOBOMB, bhvBobomb, 3000); break; } CheckBobombInteractions(); if (o->oBobombFuseTimer >= 151) o->oAction = 3; } void BobombFreeLoop(void) { if (o->oBehParams2ndByte == BOBOMB_BP_STYPE_GENERIC) GenericBobombFreeLoop(); else StationaryBobombFreeLoop(); } void BobombHeldLoop(void) { o->header.gfx.node.flags |= 0x10; /* bit 4 */ SetObjAnimation(1); obj_set_pos_relative(gMarioObject, 0, 60.0f, 100.0); o->oBobombFuseLit = 1; if (o->oBobombFuseTimer >= 151) { //! Although the Bob-omb's action is set to explode when the fuse timer expires, // BobombExplodeLoop() will not execute until the bob-omb's held state changes. // This allows the Bob-omb to be regrabbed indefinitely. gMarioObject->oInteractStatus |= INTERACT_DAMAGE; /* bit 3 */ o->oAction = BOBOMB_ACT_EXPLODE; } } void BobombDroppedLoop(void) { obj_get_dropped(); o->header.gfx.node.flags &= ~0x10; /* bit 4 = 0 */ SetObjAnimation(0); o->oHeldState = 0; o->oAction = BOBOMB_ACT_PATROL; } void BobombThrownLoop(void) { obj_enable_rendering_2(); o->header.gfx.node.flags &= ~0x10; /* bit 4 = 0 */ o->oHeldState = 0; o->oFlags &= ~0x8; /* bit 3 */ o->oForwardVel = 25.0; o->oVelY = 20.0; o->oAction = BOBOMB_ACT_LAUNCHED; } // sp18 = blinkTimer void ObjRandomBlink(s32 *blinkTimer) { if (*blinkTimer == 0) { if ((s16)(RandomFloat() * 100.0f) == 0) { o->oAnimState = 1; *blinkTimer = 1; } } else { (*blinkTimer)++; if (*blinkTimer >= 6) o->oAnimState = 0; if (*blinkTimer >= 11) o->oAnimState = 1; if (*blinkTimer >= 16) { o->oAnimState = 0; *blinkTimer = 0; } } } void bhv_bobomb_loop(void) { s8 dustPeriodMinus1; if (is_point_within_radius_of_mario(o->oPosX, o->oPosY, o->oPosZ, 4000) != 0) { switch (o->oHeldState) { case HELD_FREE: BobombFreeLoop(); break; case HELD_HELD: BobombHeldLoop(); break; case HELD_THROWN: BobombThrownLoop(); break; case HELD_DROPPED: BobombDroppedLoop(); break; } ObjRandomBlink(&o->oBobombBlinkTimer); if (o->oBobombFuseLit == 1) { if (o->oBobombFuseTimer >= 121) dustPeriodMinus1 = 1; else dustPeriodMinus1 = 7; if ((dustPeriodMinus1 & o->oBobombFuseTimer) == 0) /* oBobombFuseTimer % 2 or oBobombFuseTimer % 8 */ spawn_object(o, MODEL_SMOKE, bhvBobombFuseSmoke); PlaySound(SOUND_AIR_BOBOMB_LIT_FUSE); o->oBobombFuseTimer++; } } } void bhv_bobomb_fuse_smoke_init(void) { o->oPosX += (s32)(RandomFloat() * 80.0f) - 40; o->oPosY += (s32)(RandomFloat() * 80.0f) + 60; o->oPosZ += (s32)(RandomFloat() * 80.0f) - 40; obj_scale(1.2f); } void bhv_bobomb_buddy_init(void) { o->oGravity = 2.5; o->oFriction = 0.8; o->oBuoyancy = 1.3; o->oInteractionSubtype = INT_SUBTYPE_NPC; } void BobombBuddyIdleLoop(void) { UNUSED u8 filler[4]; s16 sp1a = o->header.gfx.unk38.animFrame; UNUSED s16 collisionFlags = 0; o->oBobombBuddyPosXCopy = o->oPosX; o->oBobombBuddyPosYCopy = o->oPosY; o->oBobombBuddyPosZCopy = o->oPosZ; collisionFlags = ObjectStep(); if ((sp1a == 5) || (sp1a == 16)) PlaySound2(SOUND_OBJ_BOBOMB_WALK); if (o->oDistanceToMario < 1000.0f) o->oMoveAngleYaw = approach_s16_symmetric(o->oMoveAngleYaw, o->oAngleToMario, 0x140); if (o->oInteractStatus == INT_STATUS_INTERACTED) o->oAction = BOBOMB_BUDDY_ACT_TURN_TO_TALK; } /** * Function for the Bob-omb Buddy cannon guy. * dialogFirstText is the first dialogID called when Bob-omb Buddy * starts to talk to Mario to prepare the cannon(s) for him. * Then the camera goes to the nearest cannon, to play the "prepare cannon" cutscene * dialogSecondText is called after Bob-omb Buddy has the cannon(s) ready and * then tells Mario that is "Ready for blastoff". */ void BobombBuddyCannonLoop(s16 dialogFirstText, s16 dialogSecondText) { struct Object *cannonClosed; s16 buddyText, cutscene; switch (o->oBobombBuddyCannonStatus) { case BOBOMB_BUDDY_CANNON_UNOPENED: buddyText = cutscene_object_with_dialog(CUTSCENE_DIALOG_1, o, dialogFirstText); if (buddyText != 0) { save_file_set_cannon_unlocked(); cannonClosed = obj_nearest_object_with_behavior(bhvCannonClosed); if (cannonClosed != 0) o->oBobombBuddyCannonStatus = BOBOMB_BUDDY_CANNON_OPENING; else o->oBobombBuddyCannonStatus = BOBOMB_BUDDY_CANNON_STOP_TALKING; } break; case BOBOMB_BUDDY_CANNON_OPENING: cannonClosed = obj_nearest_object_with_behavior(bhvCannonClosed); cutscene = cutscene_object(CUTSCENE_PREPARE_CANNON, cannonClosed); if (cutscene == -1) o->oBobombBuddyCannonStatus = BOBOMB_BUDDY_CANNON_OPENED; break; case BOBOMB_BUDDY_CANNON_OPENED: buddyText = cutscene_object_with_dialog(CUTSCENE_DIALOG_1, o, dialogSecondText); if (buddyText != 0) o->oBobombBuddyCannonStatus = BOBOMB_BUDDY_CANNON_STOP_TALKING; break; case BOBOMB_BUDDY_CANNON_STOP_TALKING: set_mario_npc_dialog(0); o->activeFlags &= ~0x20; /* bit 5 */ o->oBobombBuddyHasTalkedToMario = BOBOMB_BUDDY_HAS_TALKED; o->oInteractStatus = 0; o->oAction = BOBOMB_BUDDY_ACT_IDLE; o->oBobombBuddyCannonStatus = BOBOMB_BUDDY_CANNON_OPENED; break; } } void BobombBuddyTalkLoop(void) { if (set_mario_npc_dialog(1) == 2) { o->activeFlags |= 0x20; /* bit 5 */ switch (o->oBobombBuddyRole) { case BOBOMB_BUDDY_ROLE_ADVICE: if (cutscene_object_with_dialog(CUTSCENE_DIALOG_1, o, o->oBehParams2ndByte) != BOBOMB_BUDDY_BP_STYPE_GENERIC) { set_mario_npc_dialog(0); o->activeFlags &= ~0x20; /* bit 5 */ o->oBobombBuddyHasTalkedToMario = BOBOMB_BUDDY_HAS_TALKED; o->oInteractStatus = 0; o->oAction = BOBOMB_BUDDY_ACT_IDLE; } break; case BOBOMB_BUDDY_ROLE_CANNON: if (gCurrCourseNum == COURSE_BOB) BobombBuddyCannonLoop(4, 105); else BobombBuddyCannonLoop(47, 106); break; } } } void BobombBuddyTurnToTalkLoop(void) { s16 sp1e = o->header.gfx.unk38.animFrame; if ((sp1e == 5) || (sp1e == 16)) PlaySound2(SOUND_OBJ_BOBOMB_WALK); o->oMoveAngleYaw = approach_s16_symmetric(o->oMoveAngleYaw, o->oAngleToMario, 0x1000); if ((s16) o->oMoveAngleYaw == (s16) o->oAngleToMario) o->oAction = BOBOMB_BUDDY_ACT_TALK; PlaySound2(SOUND_ACTION_READ_SIGN); } void BobombBuddyActionLoop(void) { switch (o->oAction) { case BOBOMB_BUDDY_ACT_IDLE: BobombBuddyIdleLoop(); break; case BOBOMB_BUDDY_ACT_TURN_TO_TALK: BobombBuddyTurnToTalkLoop(); break; case BOBOMB_BUDDY_ACT_TALK: BobombBuddyTalkLoop(); break; } SetObjectVisibility(o, 3000); } void bhv_bobomb_buddy_loop(void) { BobombBuddyActionLoop(); ObjRandomBlink(&o->oBobombBuddyBlinkTimer); o->oInteractStatus = 0; }