From 52446ded91f151aa9f1e21500075693e0c75aa30 Mon Sep 17 00:00:00 2001 From: Altiami Date: Tue, 10 Mar 2026 16:15:25 -0700 Subject: [PATCH] Merry-Go-Round Fixes (includes fixes for rooms and remote object-spawned objects) (#1094) --- src/engine/behavior_script.c | 17 ++++++++++- src/engine/surface_load.c | 5 +++- src/game/behaviors/bbh_merry_go_round.inc.c | 31 +++++++++++++++++++-- src/game/behaviors/boo.inc.c | 16 ++++------- src/game/object_helpers.c | 22 ++++++++++----- 5 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/engine/behavior_script.c b/src/engine/behavior_script.c index bd6218775..8af14f249 100644 --- a/src/engine/behavior_script.c +++ b/src/engine/behavior_script.c @@ -797,7 +797,22 @@ static s32 bhv_cmd_load_collision_data(void) { // Command 0x2D: Sets the home position of the object to its current position. // Usage: SET_HOME() static s32 bhv_cmd_set_home(void) { - if (!(gCurrentObject->coopFlags & (COOP_OBJ_FLAG_LUA | COOP_OBJ_FLAG_NETWORK))) { + // COOP: only set home via behavior for the following cases + if ( + // if the object wasn't created via Lua + !(gCurrentObject->coopFlags & COOP_OBJ_FLAG_LUA) + // if the object wasn't created via network + // OR + // the object has never had its home set via behavior AND its home is default (e.g. (0, 0, 0)) + // (this case handles an object that needs its home set via behavior after being spawned by another player) + && ( + !(gCurrentObject->coopFlags & COOP_OBJ_FLAG_NETWORK) + || ( + !gCurrentObject->setHome + && gCurrentObject->oHomeX == 0.0f && gCurrentObject->oHomeY == 0.0f && gCurrentObject->oHomeZ == 0.0f + ) + ) + ) { gCurrentObject->oHomeX = gCurrentObject->oPosX; gCurrentObject->oHomeY = gCurrentObject->oPosY; gCurrentObject->oHomeZ = gCurrentObject->oPosZ; diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c index d78d7999d..345a73202 100644 --- a/src/engine/surface_load.c +++ b/src/engine/surface_load.c @@ -806,7 +806,10 @@ static void load_object_collision_model_internal(bool isSOC) { for (s32 i = 0; i < MAX_PLAYERS; i++) { f32 dist = dist_between_objects(gCurrentObject, gMarioStates[i].marioObj); - if (dist < tangibleDist) { anyPlayerInTangibleRange = TRUE; } + if (dist < tangibleDist) { + anyPlayerInTangibleRange = TRUE; + break; + } } // If the object collision is supposed to be loaded more than the diff --git a/src/game/behaviors/bbh_merry_go_round.inc.c b/src/game/behaviors/bbh_merry_go_round.inc.c index 3f38561c9..6cf25c912 100644 --- a/src/game/behaviors/bbh_merry_go_round.inc.c +++ b/src/game/behaviors/bbh_merry_go_round.inc.c @@ -10,6 +10,9 @@ * in the enclosure nor in the room around it. */ static void handle_merry_go_round_music(void) { + // COOP: raise scope of this variable since floor check is no longer strictly tied to music + u16 marioFloorType = 0; + // If the music should play, play it and check whether it still should. // Otherwise, don't play it and check whether it should. if (o->oMerryGoRoundMusicShouldPlay == FALSE) { @@ -23,7 +26,7 @@ static void handle_merry_go_round_music(void) { // Get Mario's floor and floor surface type struct Surface *marioFloor = NULL; struct Object *marioObject = gMarioObjects[0]; - u16 marioFloorType = 0; + // COOP: `marioFloorType` originally here if (marioObject) { find_floor(marioObject->oPosX, marioObject->oPosY, marioObject->oPosZ, &marioFloor); @@ -37,7 +40,9 @@ static void handle_merry_go_round_music(void) { // The cur_obj_is_mario_on_platform check is redundant since the merry-go-round // has surface type 0x1A, so Mario cannot be on the merry-go-round // without being on a floor with surface type 0x1A (SURFACE_MGR_MUSIC). - gMarioOnMerryGoRound = cur_obj_is_any_player_on_platform(); + + // COOP: `gMarioOnMerryGoRound` is used to determine if the merry-go-round Boos should be active + // for co-op, this means that this check needs to be separated from the music check, since music is client-side. if (cur_obj_is_mario_on_platform() || marioFloorType == SURFACE_MGR_MUSIC) { // If Mario is in the merry-go-round's enclosure, play only the merry-go-round music. play_secondary_music(SEQ_EVENT_MERRY_GO_ROUND, 0, 78, 50); @@ -60,6 +65,28 @@ static void handle_merry_go_round_music(void) { cur_obj_play_sound_1(SOUND_ENV_MERRY_GO_ROUND_CREAKING); } } + + // COOP: floor check happens here + // `marioFloorType` refers to the local player's character + gMarioOnMerryGoRound = marioFloorType == SURFACE_MGR_MUSIC || cur_obj_is_any_player_on_platform(); + if (!gMarioOnMerryGoRound) { + // check the other Marios' floors + // starting at 1 since local player was already checked + for (s32 i = 1; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } + + struct Object *marioObject = gMarioStates[i].marioObj; + if (marioObject == NULL) { continue; } + + struct Surface *marioFloor = NULL; + find_floor(marioObject->oPosX, marioObject->oPosY, marioObject->oPosZ, &marioFloor); + + if (marioFloor != NULL && marioFloor->type == SURFACE_MGR_MUSIC) { + gMarioOnMerryGoRound = TRUE; + break; + } + } + } } /** diff --git a/src/game/behaviors/boo.inc.c b/src/game/behaviors/boo.inc.c index 37bd4b6fe..5561e19c3 100644 --- a/src/game/behaviors/boo.inc.c +++ b/src/game/behaviors/boo.inc.c @@ -56,16 +56,11 @@ void bhv_boo_init(void) { static s32 boo_should_be_stopped(void) { if (cur_obj_has_behavior(bhvMerryGoRoundBigBoo) || cur_obj_has_behavior(bhvMerryGoRoundBoo)) { - for (s32 i = 0; i < MAX_PLAYERS; i++) { - if (!is_player_active(&gMarioStates[i])) { continue; } - if (gMarioStates[i].currentRoom != BBH_DYNAMIC_SURFACE_ROOM && gMarioStates[i].currentRoom != BBH_NEAR_MERRY_GO_ROUND_ROOM) { return TRUE; } - } - return FALSE; - /*if (!gMarioOnMerryGoRound) { + if (!gMarioOnMerryGoRound) { return TRUE; } else { return FALSE; - }*/ + } } else { if (o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM) { return TRUE; @@ -384,9 +379,7 @@ static void boo_chase_mario(f32 a0, s16 a1, f32 a2) { 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) { + if (cur_obj_lateral_dist_from_obj_to_home(player) > 1500.0f) { sp1A = cur_obj_angle_to_home(); } else { sp1A = angleToPlayer; @@ -535,7 +528,8 @@ static void (*sBooActions[])(void) = { }; void bhv_boo_loop(void) { - if (o->oAction < 3) { + // COOP: only sync when Boo isn't in a death state + if (o->oAction < 3 || o->oAction == 5) { if (!sync_object_is_initialized(o->oSyncID)) { struct SyncObject* so = boo_sync_object_init(); if (so) { so->syncDeathEvent = FALSE; } diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c index 695a3fbd0..f0bf4d162 100644 --- a/src/game/object_helpers.c +++ b/src/game/object_helpers.c @@ -2942,19 +2942,27 @@ void bhv_init_room(void) { void cur_obj_enable_rendering_if_mario_in_room(void) { if (!o) { return; } if (o->oRoom == -1) { return; } - if (gMarioCurrentRoom == 0) { return; } + // COOP: if any active player character's room is 0, then either: + // 1) There are no rooms in the area + // 2) They are on an object surface with no explicit room + // In vanilla, a room of 0 stops the game from checking if the object shouldn't be rendered + // In coop, this needs to be respected to ensure the object remains active in areas with rooms u8 marioInRoom = FALSE; + // check if any player character can "see" the object's room for (s32 i = 0; i < MAX_PLAYERS; i++) { - if (gMarioStates[i].currentRoom != 0) { + if (is_player_active(&gMarioStates[i])) { + // TODO: separate rendering and activation + if (gMarioStates[i].currentRoom == 0) { return; } s16 currentRoom = gMarioStates[i].currentRoom; - if (currentRoom == o->oRoom) { - marioInRoom = TRUE; - } else if (gDoorAdjacentRooms[currentRoom][0] == o->oRoom) { - marioInRoom = TRUE; - } else if (gDoorAdjacentRooms[currentRoom][1] == o->oRoom) { + if ( + currentRoom == o->oRoom + || gDoorAdjacentRooms[currentRoom][0] == o->oRoom + || gDoorAdjacentRooms[currentRoom][1] == o->oRoom + ) { marioInRoom = TRUE; + break; } } }