sm64coopdx/src/game/behaviors/boo.inc.c
MysterD d2a2a80d56 Synchronized Big Boo's Haunt + major changes
Synchronized currentRoom per-player
Synchronized haunted bookshelf, and the bookshelf manager
Synchronized haunted chairs
Synchronized mad piano
Synchronized BBH's tilting trap, and made the physics multiple-player-aware
Synchronized scuttlebugs
Synchronized every variety of Boo
Synchronized elevators
Synchronized flamethrowers
Synchronized the various types of enemy books
Synchronized the book switches
Synchronized jumping box
Made coffins multiple-player-aware

Fixed everything that used gMarioState as an array instead of gMarioStates
Prevented some NPC-dialog softlocks
Prevented the remote player from messing up the local's camera settings
Possibly fixed the relatively rare chain chomp softlock
Possibly fixed the relatively rare chain hoot softlock
Fixed the first-person-camera softlock
Forced camera code to use the correct mario struct
2020-08-26 23:29:40 -07:00

1032 lines
28 KiB
C

// boo.c.inc
static struct ObjectHitbox sBooGivingStarHitbox = {
/* interactType: */ 0,
/* downOffset: */ 0,
/* damageOrCoinValue: */ 3,
/* health: */ 3,
/* numLootCoins: */ 0,
/* radius: */ 140,
/* height: */ 80,
/* hurtboxRadius: */ 40,
/* hurtboxHeight: */ 60,
};
// Relative positions
static s16 sCourtyardBooTripletPositions[][3] = {
{0, 50, 0},
{210, 110, 210},
{-210, 70, -210}
};
struct SyncObject* boo_network_init_object(void) {
struct SyncObject* so = network_init_object(o, 4000.0f);
network_init_object_field(o, &o->oBooBaseScale);
network_init_object_field(o, &o->oBooNegatedAggressiveness);
network_init_object_field(o, &o->oBooOscillationTimer);
network_init_object_field(o, &o->oBooTargetOpacity);
network_init_object_field(o, &o->oBooTurningSpeed);
network_init_object_field(o, &o->oFaceAngleRoll);
network_init_object_field(o, &o->oFaceAngleYaw);
network_init_object_field(o, &o->oFlags);
network_init_object_field(o, &o->oForwardVel);
network_init_object_field(o, &o->oHealth);
network_init_object_field(o, &o->oInteractStatus);
network_init_object_field(o, &o->oInteractType);
network_init_object_field(o, &o->oOpacity);
network_init_object_field(o, &o->oRoom);
return so;
}
static void boo_stop(void) {
o->oForwardVel = 0.0f;
o->oVelY = 0.0f;
o->oGravity = 0.0f;
}
void bhv_boo_init(void) {
o->oBooInitialMoveYaw = o->oMoveAngleYaw;
}
static s32 boo_should_be_stopped(void) {
if (cur_obj_has_behavior(bhvMerryGoRoundBigBoo) || cur_obj_has_behavior(bhvMerryGoRoundBoo)) {
for (int i = 0; i < MAX_PLAYERS; i++) {
if (gMarioStates[i].currentRoom == BBH_DYNAMIC_SURFACE_ROOM || gMarioStates[i].currentRoom == BBH_NEAR_MERRY_GO_ROUND_ROOM) { return FALSE; }
}
return TRUE;
/*if (gMarioOnMerryGoRound == FALSE) {
return TRUE;
} else {
return FALSE;
}*/
} else {
if (o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM) {
return TRUE;
}
if (o->oRoom == 10) {
if (gTimeStopState & TIME_STOP_MARIO_OPENED_DOOR) {
return TRUE;
}
}
}
return FALSE;
}
static s32 boo_should_be_active(void) {
struct MarioState* marioState = nearest_mario_state_to_object(o);
int distanceToPlayer = dist_between_objects(o, marioState->marioObj);
u8 inRoom = FALSE;
for (int i = 0; i < MAX_PLAYERS; i++) {
if (gMarioStates[i].currentRoom == o->oRoom || gMarioStates[i].currentRoom == 0) { inRoom = TRUE; }
}
f32 activationRadius;
if (cur_obj_has_behavior(bhvBalconyBigBoo)) {
activationRadius = 5000.0f;
} else {
activationRadius = 1500.0f;
}
if (cur_obj_has_behavior(bhvMerryGoRoundBigBoo) || cur_obj_has_behavior(bhvMerryGoRoundBoo)) {
if (gMarioOnMerryGoRound == TRUE) {
return TRUE;
} else {
return FALSE;
}
} else if (o->oRoom == -1) {
if (distanceToPlayer < activationRadius) {
return TRUE;
}
} else if (!boo_should_be_stopped()) {
if (distanceToPlayer < activationRadius && inRoom) {
return TRUE;
}
}
return FALSE;
}
void bhv_courtyard_boo_triplet_init(void) {
s32 i;
struct Object *boo;
if (gHudDisplay.stars < 12) {
obj_mark_for_deletion(o);
} else {
for (i = 0; i < 3; i++) {
boo = spawn_object_relative(
0x01,
sCourtyardBooTripletPositions[i][0],
sCourtyardBooTripletPositions[i][1],
sCourtyardBooTripletPositions[i][2],
o,
MODEL_BOO,
bhvGhostHuntBoo
);
boo->oMoveAngleYaw = random_u16();
}
}
}
static void boo_approach_target_opacity_and_update_scale(void) {
f32 scale;
if (o->oBooTargetOpacity != o->oOpacity) {
if (o->oBooTargetOpacity > o->oOpacity) {
o->oOpacity += 20;
if (o->oBooTargetOpacity < o->oOpacity) {
o->oOpacity = o->oBooTargetOpacity;
}
} else {
o->oOpacity -= 20;
if (o->oBooTargetOpacity > o->oOpacity) {
o->oOpacity = o->oBooTargetOpacity;
}
}
}
scale = (o->oOpacity/255.0f * 0.4 + 0.6) * o->oBooBaseScale;
obj_scale(o, scale); // why no cur_obj_scale? was cur_obj_scale written later?
}
static void boo_oscillate(s32 ignoreOpacity) {
o->oFaceAnglePitch = sins(o->oBooOscillationTimer) * 0x400;
if (o->oOpacity == 0xFF || ignoreOpacity == TRUE) {
o->header.gfx.scale[0] = sins(o->oBooOscillationTimer) * 0.08 + o->oBooBaseScale;
o->header.gfx.scale[1] = -sins(o->oBooOscillationTimer) * 0.08 + o->oBooBaseScale;
o->header.gfx.scale[2] = o->header.gfx.scale[0];
o->oGravity = sins(o->oBooOscillationTimer) * o->oBooBaseScale;
o->oBooOscillationTimer += 0x400;
}
}
static s32 boo_vanish_or_appear(void) {
struct Object* player = nearest_player_to_object(o);
int angleToPlayer = obj_angle_to_object(o, player);
s16 relativeAngleToMario = abs_angle_diff(angleToPlayer, o->oMoveAngleYaw);
s16 relativeMarioFaceAngle = abs_angle_diff(o->oMoveAngleYaw, player->oFaceAngleYaw);
// magic?
s16 relativeAngleToMarioThreshhold = 0x1568;
s16 relativeMarioFaceAngleThreshhold = 0x6b58;
s32 doneAppearing = FALSE;
o->oVelY = 0.0f;
if (relativeAngleToMario > relativeAngleToMarioThreshhold || relativeMarioFaceAngle < relativeMarioFaceAngleThreshhold) {
if (o->oOpacity == 40) {
o->oBooTargetOpacity = 255;
cur_obj_play_sound_2(SOUND_OBJ_BOO_LAUGH_LONG);
}
if (o->oOpacity > 180) {
doneAppearing = TRUE;
}
} else if (o->oOpacity == 255) {
o->oBooTargetOpacity = 40;
}
return doneAppearing;
}
static void boo_set_move_yaw_for_during_hit(s32 hurt) {
struct Object* player = nearest_player_to_object(o);
int angleToPlayer = obj_angle_to_object(o, player);
cur_obj_become_intangible();
o->oFlags &= ~OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW;
o->oBooMoveYawBeforeHit = (f32) o->oMoveAngleYaw;
if (hurt != FALSE) {
o->oBooMoveYawDuringHit = player->oMoveAngleYaw;
} else if (coss((s16)o->oMoveAngleYaw - (s16)angleToPlayer) < 0.0f) {
o->oBooMoveYawDuringHit = o->oMoveAngleYaw;
} else {
o->oBooMoveYawDuringHit = (s16)(o->oMoveAngleYaw + 0x8000);
}
}
static void boo_move_during_hit(s32 roll, f32 fVel) {
// Boos seem to have been supposed to oscillate up then down then back again
// when hit. However it seems the programmers forgot to scale the cosine,
// so the Y velocity goes from 1 to -1 and back to 1 over 32 frames.
// This is such a small change that the Y position only changes by 5 units.
// It's completely unnoticable in-game.
s32 oscillationVel = o->oTimer * 0x800 + 0x800;
o->oForwardVel = fVel;
o->oVelY = coss(oscillationVel);
o->oMoveAngleYaw = o->oBooMoveYawDuringHit;
if (roll != FALSE) {
o->oFaceAngleYaw += D_8032F0CC[o->oTimer];
o->oFaceAngleRoll += D_8032F0CC[o->oTimer];
}
}
static void big_boo_shake_after_hit(void) {
// Oscillate yaw
s32 oscillationVel = o->oTimer * 0x2000 - 0x3E000;
o->oFaceAngleYaw += coss(oscillationVel) * 0x400;
}
static void boo_reset_after_hit(void) {
o->oMoveAngleYaw = o->oBooMoveYawBeforeHit;
o->oFlags |= OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW;
o->oInteractStatus = 0;
}
// called iff boo/big boo/cage boo is in action 2, which only occurs if it was non-attack-ly interacted with/bounced on?
static s32 boo_update_after_bounced_on(f32 a0) {
boo_stop();
if (o->oTimer == 0) {
boo_set_move_yaw_for_during_hit(FALSE);
}
if (o->oTimer < 32) {
boo_move_during_hit(FALSE, D_8032F0CC[o->oTimer]/5000.0f * a0);
} else {
cur_obj_become_tangible();
boo_reset_after_hit();
o->oAction = 1;
return TRUE;
}
return FALSE;
}
// called iff big boo nonlethally hit
static s32 big_boo_update_during_nonlethal_hit(f32 a0) {
boo_stop();
if (o->oTimer == 0) {
boo_set_move_yaw_for_during_hit(TRUE);
}
if (o->oTimer < 32) {
boo_move_during_hit(TRUE, D_8032F0CC[o->oTimer]/5000.0f * a0);
} else if (o->oTimer < 48) {
big_boo_shake_after_hit();
} else {
cur_obj_become_tangible();
boo_reset_after_hit();
o->oAction = 1;
return TRUE;
}
return FALSE;
}
// called every frame once mario lethally hits the boo until the boo is deleted,
// returns whether death is complete
static s32 boo_update_during_death(void) {
struct Object* player = nearest_player_to_object(o);
struct Object *parentBigBoo;
if (o->oTimer == 0) {
o->oForwardVel = 40.0f;
o->oMoveAngleYaw = player->oMoveAngleYaw;
o->oBooDeathStatus = BOO_DEATH_STATUS_DYING;
o->oFlags &= ~OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW;
} else {
if (o->oTimer == 5) {
o->oBooTargetOpacity = 0;
}
if (o->oTimer > 30 || o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
spawn_mist_particles();
o->oBooDeathStatus = BOO_DEATH_STATUS_DEAD;
if (o->oBooParentBigBoo != NULL) {
parentBigBoo = o->oBooParentBigBoo;
#ifndef VERSION_JP
if (!cur_obj_has_behavior(bhvBoo)) {
parentBigBoo->oBigBooNumMinionBoosKilled++;
}
#else
parentBigBoo->oBigBooNumMinionBoosKilled++;
#endif
}
return TRUE;
}
}
o->oVelY = 5.0f;
o->oFaceAngleRoll += 0x800;
o->oFaceAngleYaw += 0x800;
return FALSE;
}
static s32 obj_has_attack_type(u32 attackType) {
if ((o->oInteractStatus & INT_STATUS_ATTACK_MASK) == attackType) {
return TRUE;
} else {
return FALSE;
}
}
static s32 boo_get_attack_status(void) {
s32 attackStatus = BOO_NOT_ATTACKED;
if (o->oInteractStatus & INT_STATUS_INTERACTED) {
if ((o->oInteractStatus & INT_STATUS_WAS_ATTACKED) && obj_has_attack_type(ATTACK_FROM_ABOVE) == FALSE) {
cur_obj_become_intangible();
o->oInteractStatus = 0;
cur_obj_play_sound_2(SOUND_OBJ_BOO_LAUGH_SHORT);
attackStatus = BOO_ATTACKED;
} else {
cur_obj_play_sound_2(SOUND_OBJ_BOO_BOUNCE_TOP);
o->oInteractStatus = 0;
attackStatus = BOO_BOUNCED_ON;
}
}
return attackStatus;
}
// boo idle/chasing movement?
static void boo_chase_mario(f32 a0, s16 a1, f32 a2) {
struct MarioState* marioState = nearest_mario_state_to_object(o);
struct Object* player = marioState->marioObj;
int angleToPlayer = obj_angle_to_object(o, player);
f32 sp1C;
s16 sp1A;
if (boo_vanish_or_appear()) {
o->oInteractType = 0x8000;
u8 isMerryGoRoundBoo = (cur_obj_has_behavior(bhvMerryGoRoundBigBoo) || cur_obj_has_behavior(bhvMerryGoRoundBoo));
if (!isMerryGoRoundBoo && cur_obj_lateral_dist_from_obj_to_home(player) > 1500.0f) {
sp1A = cur_obj_angle_to_home();
} else {
sp1A = angleToPlayer;
}
cur_obj_rotate_yaw_toward(sp1A, a1);
o->oVelY = 0.0f;
if (mario_is_in_air_action(marioState) == 0) {
sp1C = o->oPosY - player->oPosY;
if (a0 < sp1C && sp1C < 500.0f) {
o->oVelY = increment_velocity_toward_range(o->oPosY, player->oPosY + 50.0f, 10.f, 2.0f);
}
}
cur_obj_set_vel_from_mario_vel(marioState, 10.0f - o->oBooNegatedAggressiveness, a2);
if (o->oForwardVel != 0.0f) {
boo_oscillate(FALSE);
}
} else {
o->oInteractType = 0;
// why is boo_stop not used here
o->oForwardVel = 0.0f;
o->oVelY = 0.0f;
o->oGravity = 0.0f;
}
}
static void boo_act_0(void) {
o->activeFlags |= ACTIVE_FLAG_MOVE_THROUGH_GRATE;
if (o->oBehParams2ndByte == 2) {
o->oRoom = 10;
}
cur_obj_set_pos_to_home();
o->oMoveAngleYaw = o->oBooInitialMoveYaw;
boo_stop();
o->oBooParentBigBoo = cur_obj_nearest_object_with_behavior(bhvGhostHuntBigBoo);
o->oBooBaseScale = 1.0f;
o->oBooTargetOpacity = 0xFF;
if (boo_should_be_active()) {
// Condition is met if the object is bhvBalconyBigBoo or bhvMerryGoRoundBoo
if (o->oBehParams2ndByte == 2) {
o->oBooParentBigBoo = NULL;
o->oAction = 5;
} else {
o->oAction = 1;
}
}
}
static void boo_act_5(void) {
if (o->oTimer < 30) {
o->oVelY = 0.0f;
o->oForwardVel = 13.0f;
boo_oscillate(FALSE);
o->oWallHitboxRadius = 0.0f;
} else {
o->oAction = 1;
o->oWallHitboxRadius = 30.0f;
}
}
static void boo_act_1(void) {
s32 attackStatus;
if (o->oTimer == 0) {
o->oBooNegatedAggressiveness = -random_float() * 5.0f;
o->oBooTurningSpeed = (s32)(random_float() * 128.0f);
}
boo_chase_mario(-100.0f, o->oBooTurningSpeed + 0x180, 0.5f);
attackStatus = boo_get_attack_status();
if (boo_should_be_stopped()) {
o->oAction = 0;
}
if (attackStatus == BOO_BOUNCED_ON) {
o->oAction = 2;
}
if (attackStatus == BOO_ATTACKED) {
o->oAction = 3;
}
if (attackStatus == BOO_ATTACKED) {
create_sound_spawner(SOUND_OBJ_DYING_ENEMY1);
}
}
static void boo_act_2(void) {
if (boo_update_after_bounced_on(20.0f)) {
o->oAction = 1;
}
}
static void boo_act_3(void) {
if (boo_update_during_death()) {
if (o->oBehParams2ndByte != 0) {
obj_mark_for_deletion(o);
} else {
o->oAction = 4;
cur_obj_disable();
}
}
}
// Called when a Go on a Ghost Hunt boo dies
static void boo_act_4(void) {
s32 dialogID;
// If there are no remaining "minion" boos, show the dialog of the Big Boo
if (cur_obj_nearest_object_with_behavior(bhvGhostHuntBoo) == NULL) {
dialogID = DIALOG_108;
} else {
dialogID = DIALOG_107;
}
struct MarioState* marioState = nearest_mario_state_to_object(o);
if (marioState->playerIndex != 0 || cur_obj_update_dialog(&gMarioStates[0], 2, 2, dialogID, 0)) {
create_sound_spawner(SOUND_OBJ_DYING_ENEMY1);
obj_mark_for_deletion(o);
if (dialogID == DIALOG_108) { // If the Big Boo should spawn, play the jingle
play_puzzle_jingle();
}
}
}
static void (*sBooActions[])(void) = {
boo_act_0,
boo_act_1,
boo_act_2,
boo_act_3,
boo_act_4,
boo_act_5
};
void bhv_boo_loop(void) {
if (o->oAction < 3) {
if (!network_sync_object_initialized(o)) {
struct SyncObject* so = boo_network_init_object();
so->syncDeathEvent = FALSE;
}
}
else {
if (network_sync_object_initialized(o)) {
network_send_object_reliability(o, TRUE);
network_forget_sync_object(&syncObjects[o->oSyncID]);
}
}
//PARTIAL_UPDATE
cur_obj_update_floor_and_walls();
cur_obj_call_action_function(sBooActions);
cur_obj_move_standard(78);
boo_approach_target_opacity_and_update_scale();
if (obj_has_behavior(o->parentObj, bhvMerryGoRoundBooManager)) {
if (o->activeFlags == ACTIVE_FLAG_DEACTIVATED) {
o->parentObj->oMerryGoRoundBooManagerNumBoosKilled++;
}
}
o->oInteractStatus = 0;
}
static u8 bigBooActivated = FALSE;
static void big_boo_act_0(void) {
if (cur_obj_has_behavior(bhvBalconyBigBoo)) {
obj_set_secondary_camera_focus();
// number of killed boos set > 5 so that boo always loads
// redundant? this is also done in behavior_data.s
o->oBigBooNumMinionBoosKilled = 10;
}
o->oBooParentBigBoo = NULL;
#ifndef VERSION_JP
if (boo_should_be_active() && gDebugInfo[5][0] + 5 <= o->oBigBooNumMinionBoosKilled) {
#else
if (boo_should_be_active() && o->oBigBooNumMinionBoosKilled >= 5) {
#endif
o->oAction = 1;
bigBooActivated = TRUE;
cur_obj_set_pos_to_home();
o->oMoveAngleYaw = o->oBooInitialMoveYaw;
cur_obj_unhide();
o->oBooTargetOpacity = 0xFF;
o->oBooBaseScale = 3.0f;
o->oHealth = 3;
cur_obj_scale(3.0f);
cur_obj_become_tangible();
} else {
cur_obj_hide();
cur_obj_become_intangible();
boo_stop();
}
}
static void big_boo_act_1(void) {
s32 attackStatus;
s16 sp22;
f32 sp1C;
if (o->oHealth == 3) {
sp22 = 0x180; sp1C = 0.5f;
} else if (o->oHealth == 2) {
sp22 = 0x240; sp1C = 0.6f;
} else {
sp22 = 0x300; sp1C = 0.8f;
}
boo_chase_mario(-100.0f, sp22, sp1C);
attackStatus = boo_get_attack_status();
// redundant; this check is in boo_should_be_stopped
if (cur_obj_has_behavior(bhvMerryGoRoundBigBoo)) {
u8 inRoom = FALSE;
for (int i = 0; i < MAX_PLAYERS; i++) {
if (gMarioStates[i].currentRoom == BBH_DYNAMIC_SURFACE_ROOM || gMarioStates[i].currentRoom == BBH_NEAR_MERRY_GO_ROUND_ROOM) { inRoom = TRUE; }
}
//if (gMarioOnMerryGoRound == FALSE) {
if (!inRoom) {
o->oAction = 0;
}
} else if (boo_should_be_stopped()) {
o->oAction = 0;
}
if (attackStatus == BOO_BOUNCED_ON) {
o->oAction = 2;
}
if (attackStatus == BOO_ATTACKED) {
o->oAction = 3;
}
if (attackStatus == 1) {
create_sound_spawner(SOUND_OBJ_THWOMP);
}
}
static void big_boo_act_2(void) {
if (boo_update_after_bounced_on(20.0f)) {
o->oAction = 1;
}
}
static void big_boo_spawn_ghost_hunt_star(void) {
spawn_default_star(980.0f, 1100.0f, 250.0f);
}
static void big_boo_spawn_balcony_star(void) {
spawn_default_star(700.0f, 3200.0f, 1900.0f);
}
static void big_boo_spawn_merry_go_round_star(void) {
struct Object *merryGoRound;
spawn_default_star(-1600.0f, -2100.0f, 205.0f);
merryGoRound = cur_obj_nearest_object_with_behavior(bhvMerryGoRound);
if (merryGoRound != NULL) {
merryGoRound->oMerryGoRoundStopped = TRUE;
}
}
static void big_boo_act_3(void) {
if (o->oTimer == 0) {
o->oHealth--;
}
if (o->oHealth <= 0) {
if (boo_update_during_death()) {
cur_obj_disable();
o->oAction = 4;
obj_set_angle(o, 0, 0, 0);
if (o->oBehParams2ndByte == 0) {
big_boo_spawn_ghost_hunt_star();
} else if (o->oBehParams2ndByte == 1) {
big_boo_spawn_merry_go_round_star();
} else {
big_boo_spawn_balcony_star();
}
}
} else {
if (o->oTimer == 0) {
spawn_mist_particles();
o->oBooBaseScale -= 0.5;
}
if (big_boo_update_during_nonlethal_hit(40.0f)) {
o->oAction = 1;
}
}
}
static void big_boo_act_4(void) {
#ifndef VERSION_JP
boo_stop();
#endif
struct Object* player = nearest_player_to_object(o);
int distanceToPlayer = dist_between_objects(o, player);
if (o->oBehParams2ndByte == 0) {
obj_set_pos(o, 973, 0, 626);
if (o->oTimer > 60 && distanceToPlayer < 600.0f) {
obj_set_pos(o, 973, 0, 717);
spawn_object_relative(0, 0, 0, 0, o, MODEL_BBH_STAIRCASE_STEP, bhvBooBossSpawnedBridge);
spawn_object_relative(1, 0, 0, -200, o, MODEL_BBH_STAIRCASE_STEP, bhvBooBossSpawnedBridge);
spawn_object_relative(2, 0, 0, 200, o, MODEL_BBH_STAIRCASE_STEP, bhvBooBossSpawnedBridge);
obj_mark_for_deletion(o);
}
} else {
obj_mark_for_deletion(o);
}
}
static void (*sBooGivingStarActions[])(void) = {
big_boo_act_0,
big_boo_act_1,
big_boo_act_2,
big_boo_act_3,
big_boo_act_4
};
u8 big_boo_ignore_update(void) {
return cur_obj_has_behavior(bhvGhostHuntBigBoo) && !bigBooActivated;
}
void bhv_big_boo_loop(void) {
if (o->oAction == 0) {
if (!network_sync_object_initialized(o)) {
bigBooActivated = FALSE;
struct SyncObject* so = boo_network_init_object();
so->syncDeathEvent = FALSE;
so->ignore_if_true = big_boo_ignore_update;
}
} else if (o->oHealth <= 0) {
if (network_sync_object_initialized(o)) {
network_send_object_reliability(o, TRUE);
network_forget_sync_object(&syncObjects[o->oSyncID]);
}
}
//PARTIAL_UPDATE
obj_set_hitbox(o, &sBooGivingStarHitbox);
o->oGraphYOffset = o->oBooBaseScale * 60.0f;
cur_obj_update_floor_and_walls();
cur_obj_call_action_function(sBooGivingStarActions);
cur_obj_move_standard(78);
boo_approach_target_opacity_and_update_scale();
o->oInteractStatus = 0;
}
static void boo_with_cage_act_0(void) {
o->oBooParentBigBoo = NULL;
o->oBooTargetOpacity = 0xFF;
o->oBooBaseScale = 2.0f;
cur_obj_scale(2.0f);
cur_obj_become_tangible();
if (boo_should_be_active()) {
o->oAction = 1;
}
}
static void boo_with_cage_act_1(void) {
s32 attackStatus;
boo_chase_mario(100.0f, 512, 0.5f);
attackStatus = boo_get_attack_status();
if (boo_should_be_stopped()) {
o->oAction = 0;
}
if (attackStatus == BOO_BOUNCED_ON) {
o->oAction = 2;
}
if (attackStatus == BOO_ATTACKED) {
o->oAction = 3;
}
}
static void boo_with_cage_act_2(void) {
if (boo_update_after_bounced_on(20.0f)) {
o->oAction = 1;
}
}
static void boo_with_cage_act_3(void) {
if (boo_update_during_death()) {
obj_mark_for_deletion(o);
}
}
void bhv_boo_with_cage_init(void) {
struct Object* cage;
if (gHudDisplay.stars < 12) {
obj_mark_for_deletion(o);
} else {
cage = spawn_object(o, MODEL_HAUNTED_CAGE, bhvBooCage);
cage->oBehParams = o->oBehParams;
}
}
static void (*sBooWithCageActions[])(void) = {
boo_with_cage_act_0,
boo_with_cage_act_1,
boo_with_cage_act_2,
boo_with_cage_act_3
};
void bhv_boo_with_cage_loop(void) {
if (!network_sync_object_initialized(o)) { boo_network_init_object(); }
//PARTIAL_UPDATE
cur_obj_update_floor_and_walls();
cur_obj_call_action_function(sBooWithCageActions);
cur_obj_move_standard(78);
boo_approach_target_opacity_and_update_scale();
o->oInteractStatus = 0;
}
void bhv_merry_go_round_boo_manager_loop(void) {
if (!network_sync_object_initialized(o)) {
network_init_object(o, SYNC_DISTANCE_ONLY_EVENTS);
network_init_object_field(o, &o->oAction);
network_init_object_field(o, &o->oMerryGoRoundBooManagerNumBoosSpawned);
}
struct Object* player = nearest_player_to_object(o);
int distanceToPlayer = dist_between_objects(o, player);
switch (o->oAction) {
case 0:
if (distanceToPlayer < 1000.0f) {
if (networkType == NT_SERVER && o->oMerryGoRoundBooManagerNumBoosKilled < 5) {
if (o->oMerryGoRoundBooManagerNumBoosSpawned < 5) {
if (o->oMerryGoRoundBooManagerNumBoosSpawned - o->oMerryGoRoundBooManagerNumBoosKilled < 2) {
struct Object* boo = spawn_object(o, MODEL_BOO, bhvMerryGoRoundBoo);
network_set_sync_id(boo);
struct Object* spawn_objects[] = { boo };
u32 models[] = { MODEL_BOO };
network_send_spawn_objects(spawn_objects, models, 1);
o->oMerryGoRoundBooManagerNumBoosSpawned++;
network_send_object(o);
}
}
o->oAction++;
}
if (o->oMerryGoRoundBooManagerNumBoosKilled > 4) {
if (networkType == NT_SERVER) {
struct Object* boo = spawn_object(o, MODEL_BOO, bhvMerryGoRoundBigBoo);
obj_copy_behavior_params(boo, o);
network_set_sync_id(boo);
struct Object* spawn_objects[] = { boo };
u32 models[] = { MODEL_BOO };
network_send_spawn_objects(spawn_objects, models, 1);
o->oAction = 2;
network_send_object(o);
}
#ifndef VERSION_JP
play_puzzle_jingle();
#else
play_sound(SOUND_GENERAL2_RIGHT_ANSWER, gDefaultSoundArgs);
#endif
}
}
break;
case 1:
if (o->oTimer > 60) {
o->oAction = 0;
}
break;
case 2:
break;
}
}
void obj_set_secondary_camera_focus(void) {
gSecondCameraFocus = o;
}
void bhv_animated_texture_loop(void) {
cur_obj_set_pos_to_home_with_debug();
}
void bhv_boo_in_castle_loop(void) {
if (!network_sync_object_initialized(o)) { boo_network_init_object(); }
struct MarioState* marioState = nearest_mario_state_to_object(o);
struct Object* player = marioState->marioObj;
int distanceToPlayer = dist_between_objects(o, player);
int angleToPlayer = obj_angle_to_object(o, player);
u8 inRoom = FALSE;
for (int i = 0; i < MAX_PLAYERS; i++) {
if (marioState->floor == NULL) { continue; }
inRoom = inRoom || (marioState->floor->room == 1);
}
s16 targetAngle;
o->oBooBaseScale = 2.0f;
if (o->oAction == 0) {
cur_obj_hide();
if (gHudDisplay.stars < 12) {
obj_mark_for_deletion(o);
}
if (inRoom) {
o->oAction++;
}
} else if (o->oAction == 1) {
cur_obj_unhide();
o->oOpacity = 180;
if (o->oTimer == 0) {
cur_obj_scale(o->oBooBaseScale);
}
if (distanceToPlayer < 1000.0f) {
o->oAction++;
cur_obj_play_sound_2(SOUND_OBJ_BOO_LAUGH_LONG);
}
o->oForwardVel = 0.0f;
targetAngle = angleToPlayer;
} else {
cur_obj_forward_vel_approach_upward(32.0f, 1.0f);
o->oHomeX = -1000.0f;
o->oHomeZ = -9000.0f;
targetAngle = cur_obj_angle_to_home();
if (o->oPosZ < -5000.0f) {
if (o->oOpacity > 0) {
o->oOpacity -= 20;
} else {
o->oOpacity = 0;
}
}
if (o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM) {
o->oAction = 1;
}
}
o->oVelY = 0.0f;
targetAngle = cur_obj_angle_to_home();
cur_obj_rotate_yaw_toward(targetAngle, 0x5A8);
boo_oscillate(TRUE);
cur_obj_move_using_fvel_and_gravity();
}
void bhv_boo_boss_spawned_bridge_loop(void) {
f32 targetY;
switch (o->oBehParams2ndByte) {
case 1:
targetY = 0.0f;
break;
case 0:
targetY = -206.0f;
break;
case 2:
targetY = -413.0f;
break;
}
switch(o->oAction) {
case 0:
o->oPosY = o->oHomeY - 620.0f;
o->oAction++;
// fallthrough
case 1:
o->oPosY += 8.0f;
cur_obj_play_sound_1(SOUND_ENV_ELEVATOR2);
if (o->oPosY > targetY) {
o->oPosY = targetY;
o->oAction++;
}
break;
case 2:
if (o->oTimer == 0) {
cur_obj_play_sound_2(SOUND_GENERAL_UNKNOWN4_LOWPRIO);
}
if (cur_obj_move_up_and_down(o->oTimer)) {
o->oAction++;
}
break;
case 3:
if (o->oTimer == 0 && o->oBehParams2ndByte == 1) {
play_puzzle_jingle();
}
break;
}
}