diff --git a/data/behavior_data.c b/data/behavior_data.c index a52c1812d..11351f35a 100644 --- a/data/behavior_data.c +++ b/data/behavior_data.c @@ -556,6 +556,15 @@ const BehaviorScript bhvBubbleMaybe[] = { DEACTIVATE(), }; +const BehaviorScript bhvBubblePlayer[] = { + BEGIN(OBJ_LIST_DEFAULT), + ID(id_bhvBubblePlayer), + OR_INT(oFlags, OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE), + BEGIN_LOOP(), + CALL_NATIVE(bhv_bubble_player_loop), + END_LOOP(), +}; + const BehaviorScript bhvSmallWaterWave[] = { BEGIN(OBJ_LIST_UNIMPORTANT), ID(id_bhvSmallWaterWave), @@ -6657,3 +6666,4 @@ const BehaviorScript bhvIntroScene[] = { CALL_NATIVE(bhv_intro_scene_loop), END_LOOP(), }; + diff --git a/data/behavior_table.c b/data/behavior_table.c index d26846cd4..cdc8b8acb 100644 --- a/data/behavior_table.c +++ b/data/behavior_table.c @@ -19,6 +19,7 @@ const BehaviorScript* gBehaviorTable[id_bhv_max_count] = { [id_bhvBetaChestLid] = bhvBetaChestLid, [id_bhvBubbleParticleSpawner] = bhvBubbleParticleSpawner, [id_bhvBubbleMaybe] = bhvBubbleMaybe, + [id_bhvBubblePlayer] = bhvBubblePlayer, [id_bhvSmallWaterWave] = bhvSmallWaterWave, [id_bhvWaterAirBubble] = bhvWaterAirBubble, [id_bhvSmallParticle] = bhvSmallParticle, diff --git a/include/behavior_data.h b/include/behavior_data.h index 6ec2c741f..ef36748ef 100644 --- a/include/behavior_data.h +++ b/include/behavior_data.h @@ -20,6 +20,7 @@ extern const BehaviorScript bhvBetaChestBottom[]; extern const BehaviorScript bhvBetaChestLid[]; extern const BehaviorScript bhvBubbleParticleSpawner[]; extern const BehaviorScript bhvBubbleMaybe[]; +extern const BehaviorScript bhvBubblePlayer[]; extern const BehaviorScript bhvSmallWaterWave[]; extern const BehaviorScript bhvSmallWaterWave398[]; extern const BehaviorScript bhvWaterAirBubble[]; diff --git a/include/behavior_table.h b/include/behavior_table.h index 05fbf5427..2ddc0a80c 100644 --- a/include/behavior_table.h +++ b/include/behavior_table.h @@ -23,6 +23,7 @@ enum BehaviorId { id_bhvBetaChestLid, id_bhvBubbleParticleSpawner, id_bhvBubbleMaybe, + id_bhvBubblePlayer, id_bhvSmallWaterWave, id_bhvWaterAirBubble, id_bhvSmallParticle, diff --git a/include/model_ids.h b/include/model_ids.h index fdd242c93..e66990850 100644 --- a/include/model_ids.h +++ b/include/model_ids.h @@ -29,6 +29,8 @@ #define MODEL_LUIGI 0xE2 // luigi_geo #define MODEL_LUIGI2 0xE3 // luigi2_geo +#define MODEL_BUBBLE_PLAYER 0xE4 // water_bomb_geo + /* Various static level geometry, the geo layout differs but terrain object presets treat them the same.*/ #define MODEL_LEVEL_GEOMETRY_03 0x03 diff --git a/include/sm64.h b/include/sm64.h index 632224e07..87c16546b 100644 --- a/include/sm64.h +++ b/include/sm64.h @@ -402,6 +402,7 @@ #define ACT_GRABBED 0x00020370 // (0x170 | ACT_FLAG_STATIONARY | ACT_FLAG_INVULNERABLE) #define ACT_IN_CANNON 0x00001371 // (0x171 | ACT_FLAG_STATIONARY | ACT_FLAG_INTANGIBLE) #define ACT_TORNADO_TWIRLING 0x10020372 // (0x172 | ACT_FLAG_STATIONARY | ACT_FLAG_INVULNERABLE | ACT_FLAG_SWIMMING_OR_FLYING) +#define ACT_BUBBLED (0x173 | ACT_FLAG_MOVING) // group 0x180: object actions #define ACT_PUNCHING 0x00800380 // (0x180 | ACT_FLAG_STATIONARY | ACT_FLAG_ATTACKING) diff --git a/include/types.h b/include/types.h index c91a6d45f..93eb3faf2 100644 --- a/include/types.h +++ b/include/types.h @@ -368,6 +368,7 @@ struct MarioState /*0xC8*/ s16 currentRoom; /*0xCA*/ struct Object* heldByObj; /*????*/ u8 isSnoring; + /*????*/ struct Object* bubbleObj; }; #define PLAY_MODE_NORMAL 0 diff --git a/levels/scripts.c b/levels/scripts.c index 1121b921a..e7ea643fb 100644 --- a/levels/scripts.c +++ b/levels/scripts.c @@ -69,6 +69,7 @@ const LevelScript level_main_scripts_entry[] = { LOAD_MODEL_FROM_GEO(MODEL_MARIO2, mario2_geo), LOAD_MODEL_FROM_GEO(MODEL_LUIGI, luigi_geo), LOAD_MODEL_FROM_GEO(MODEL_LUIGI2, luigi2_geo), + LOAD_MODEL_FROM_GEO(MODEL_BUBBLE_PLAYER, water_bomb_geo), LOAD_MODEL_FROM_GEO(MODEL_SMOKE, smoke_geo), LOAD_MODEL_FROM_GEO(MODEL_SPARKLES, sparkles_geo), LOAD_MODEL_FROM_GEO(MODEL_BUBBLE, bubble_geo), diff --git a/src/game/behavior_actions.h b/src/game/behavior_actions.h index 7c5e00a81..3b0c624cc 100644 --- a/src/game/behavior_actions.h +++ b/src/game/behavior_actions.h @@ -40,6 +40,7 @@ void bhv_beta_chest_bottom_loop(void); void bhv_beta_chest_lid_loop(void); void bhv_bubble_wave_init(void); void bhv_bubble_maybe_loop(void); +void bhv_bubble_player_loop(void); void bhv_small_water_loop(void); void bhv_water_air_bubble_init(void); void bhv_water_air_bubble_loop(void); diff --git a/src/game/behaviors/bbh_tilting_trap.inc.c b/src/game/behaviors/bbh_tilting_trap.inc.c index ae6544f66..c3223b76b 100644 --- a/src/game/behaviors/bbh_tilting_trap.inc.c +++ b/src/game/behaviors/bbh_tilting_trap.inc.c @@ -21,6 +21,7 @@ void bhv_bbh_tilting_trap_platform_loop(void) { f32 z = 0; u8 playersTouched = 0; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { x += gMarioStates[i].marioObj->oPosX; y += gMarioStates[i].marioObj->oPosY; diff --git a/src/game/behaviors/boo.inc.c b/src/game/behaviors/boo.inc.c index 7082fa860..1e552aaf3 100644 --- a/src/game/behaviors/boo.inc.c +++ b/src/game/behaviors/boo.inc.c @@ -51,6 +51,7 @@ void bhv_boo_init(void) { 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 (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].currentRoom == BBH_DYNAMIC_SURFACE_ROOM || gMarioStates[i].currentRoom == BBH_NEAR_MERRY_GO_ROUND_ROOM) { return FALSE; } } return TRUE; @@ -80,6 +81,7 @@ static s32 boo_should_be_active(void) { u8 inRoom = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].currentRoom == o->oRoom || gMarioStates[i].currentRoom == 0) { inRoom = TRUE; } } @@ -614,6 +616,7 @@ static void big_boo_act_1(void) { if (cur_obj_has_behavior(bhvMerryGoRoundBigBoo)) { u8 inRoom = FALSE; for (int 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) { inRoom = TRUE; } } @@ -924,6 +927,7 @@ void bhv_boo_in_castle_loop(void) { u8 inRoom = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (marioState->floor == NULL) { continue; } inRoom = inRoom || (marioState->floor->room == 1); } diff --git a/src/game/behaviors/castle_floor_trap.inc.c b/src/game/behaviors/castle_floor_trap.inc.c index a0e42332c..7655b3cdf 100644 --- a/src/game/behaviors/castle_floor_trap.inc.c +++ b/src/game/behaviors/castle_floor_trap.inc.c @@ -3,6 +3,7 @@ void bhv_floor_trap_in_castle_loop(void) { u8 onPlatform = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } onPlatform = onPlatform || (gMarioStates[i].marioObj->platform == o); } if (onPlatform) diff --git a/src/game/behaviors/chuckya.inc.c b/src/game/behaviors/chuckya.inc.c index 446ed2146..f3c298b90 100644 --- a/src/game/behaviors/chuckya.inc.c +++ b/src/game/behaviors/chuckya.inc.c @@ -2,6 +2,7 @@ void common_anchor_mario_behavior(f32 sp28, f32 sp2C, s32 sp30) { for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } struct MarioState* marioState = &gMarioStates[i]; struct Object* player = gMarioStates[i].marioObj; if (marioState->heldByObj != o->parentObj && marioState->heldByObj != o) { continue; } diff --git a/src/game/behaviors/door.inc.c b/src/game/behaviors/door.inc.c index a71030c7a..e8e04461d 100644 --- a/src/game/behaviors/door.inc.c +++ b/src/game/behaviors/door.inc.c @@ -83,8 +83,16 @@ void bhv_door_loop(void) { play_warp_door_open_noise(); break; } - if (o->oAction == 0) - load_object_collision_model(); + + // make doors intangible when you're bubbled + if (o->oAction == 0) { + if (gCurrCourseNum != COURSE_NONE && gMarioStates[0].action == ACT_BUBBLED) { + o->oIntangibleTimer = -1; + } else { + load_object_collision_model(); + o->oIntangibleTimer = 0; + } + } bhv_star_door_loop_2(); } diff --git a/src/game/behaviors/elevator.inc.c b/src/game/behaviors/elevator.inc.c index ca2d82e78..b7a38d6ec 100644 --- a/src/game/behaviors/elevator.inc.c +++ b/src/game/behaviors/elevator.inc.c @@ -11,6 +11,7 @@ void elevator_act_0(void) { u8 onPlatform = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } onPlatform = onPlatform || gMarioStates[i].marioObj->platform == o; } diff --git a/src/game/behaviors/floating_platform.inc.c b/src/game/behaviors/floating_platform.inc.c index 469ea09f3..5a221ed92 100644 --- a/src/game/behaviors/floating_platform.inc.c +++ b/src/game/behaviors/floating_platform.inc.c @@ -22,6 +22,7 @@ void floating_platform_act_0(void) { f32 z = 0; u8 playersTouched = 0; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { x += gMarioStates[i].marioObj->oPosX; z += gMarioStates[i].marioObj->oPosZ; diff --git a/src/game/behaviors/king_bobomb.inc.c b/src/game/behaviors/king_bobomb.inc.c index 1d11cca58..9f0e14738 100644 --- a/src/game/behaviors/king_bobomb.inc.c +++ b/src/game/behaviors/king_bobomb.inc.c @@ -3,16 +3,21 @@ struct MarioState* king_bobomb_nearest_mario_state() { struct MarioState* nearest = NULL; f32 nearestDist = 0; - for (int i = 0; i < MAX_PLAYERS; i++) { - float ydiff = (o->oPosY - gMarioStates[i].marioObj->oPosY); - if (ydiff >= 1200) { continue; } + u8 checkActive = TRUE; + do { + for (int i = 0; i < MAX_PLAYERS; i++) { + if (checkActive && !is_player_active(&gMarioStates[i])) { continue; } + float ydiff = (o->oPosY - gMarioStates[i].marioObj->oPosY); + if (ydiff >= 1200) { continue; } - float dist = dist_between_objects(o, gMarioStates[i].marioObj); - if (nearest == NULL || dist < nearestDist) { - nearest = &gMarioStates[i]; - nearestDist = dist; + float dist = dist_between_objects(o, gMarioStates[i].marioObj); + if (nearest == NULL || dist < nearestDist) { + nearest = &gMarioStates[i]; + nearestDist = dist; + } } - } + checkActive = FALSE; + } while (nearest == NULL); return nearest; } @@ -62,6 +67,7 @@ void king_bobomb_act_0(void) { int mario_is_far_below_object(f32 arg0) { for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (arg0 >= o->oPosY - gMarioStates[i].marioObj->oPosY) { return FALSE; } } return TRUE; diff --git a/src/game/behaviors/mushroom_1up.inc.c b/src/game/behaviors/mushroom_1up.inc.c index eea21e386..1b6b25cb4 100644 --- a/src/game/behaviors/mushroom_1up.inc.c +++ b/src/game/behaviors/mushroom_1up.inc.c @@ -3,10 +3,10 @@ void bhv_1up_interact(void) { UNUSED s32 sp1C; - struct Object* player = nearest_player_to_object(o); - if (obj_check_if_collided_with_object(o, player) == 1) { + struct MarioState* marioState = nearest_mario_state_to_object(o); + if (marioState->playerIndex == 0 && obj_check_if_collided_with_object(o, marioState->marioObj) == 1) { play_sound(SOUND_GENERAL_COLLECT_1UP, gDefaultSoundArgs); - gMarioState->numLives++; + marioState->numLives++; o->activeFlags = ACTIVE_FLAG_DEACTIVATED; } } diff --git a/src/game/behaviors/platform_on_track.inc.c b/src/game/behaviors/platform_on_track.inc.c index 9d64cfe2d..36e7013b9 100644 --- a/src/game/behaviors/platform_on_track.inc.c +++ b/src/game/behaviors/platform_on_track.inc.c @@ -219,6 +219,7 @@ static void platform_on_track_act_move_along_track(void) { u8 anyMarioOnPlatform = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { anyMarioOnPlatform = TRUE; } } @@ -248,6 +249,7 @@ static void platform_on_track_act_fall(void) { u8 anyMarioOnPlatform = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { anyMarioOnPlatform = TRUE; } } @@ -270,6 +272,7 @@ static void platform_on_track_rock_ski_lift(void) { struct Object* player = NULL; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform != o) { continue; } player = gMarioStates[i].marioObj; break; @@ -317,6 +320,7 @@ void bhv_platform_on_track_update(void) { u8 anyMarioOnPlatform = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { anyMarioOnPlatform = TRUE; } } diff --git a/src/game/behaviors/purple_switch.inc.c b/src/game/behaviors/purple_switch.inc.c index 0647de5c3..8738d94a8 100644 --- a/src/game/behaviors/purple_switch.inc.c +++ b/src/game/behaviors/purple_switch.inc.c @@ -15,6 +15,7 @@ void bhv_purple_switch_loop(void) { u8 anyPlayerOnPlatform = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { anyPlayerOnPlatform = TRUE; break; diff --git a/src/game/behaviors/racing_penguin.inc.c b/src/game/behaviors/racing_penguin.inc.c index 77f1c1245..e4ed66b02 100644 --- a/src/game/behaviors/racing_penguin.inc.c +++ b/src/game/behaviors/racing_penguin.inc.c @@ -128,6 +128,7 @@ static void racing_penguin_act_race(void) { u8 isInAir = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } isInAir = isInAir || mario_is_in_air_action(&gMarioStates[i]); } diff --git a/src/game/behaviors/recovery_heart.inc.c b/src/game/behaviors/recovery_heart.inc.c index 939f1fa01..7854ecada 100644 --- a/src/game/behaviors/recovery_heart.inc.c +++ b/src/game/behaviors/recovery_heart.inc.c @@ -16,6 +16,7 @@ void bhv_recovery_heart_loop(void) { obj_set_hitbox(o, &sRecoveryHeartHitbox); for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (obj_check_if_collided_with_object(o, gMarioStates[i].marioObj)) { collided = TRUE; } } @@ -40,6 +41,7 @@ void bhv_recovery_heart_loop(void) { struct MarioState* nearestState = nearest_mario_state_to_object(o); for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (&gMarioStates[i] == nearestState || dist_between_objects(o, gMarioStates[i].marioObj) < 1000) { gMarioStates[i].healCounter += 4; } diff --git a/src/game/behaviors/seesaw_platform.inc.c b/src/game/behaviors/seesaw_platform.inc.c index 80c21e06b..56d851b20 100644 --- a/src/game/behaviors/seesaw_platform.inc.c +++ b/src/game/behaviors/seesaw_platform.inc.c @@ -45,6 +45,7 @@ void bhv_seesaw_platform_update(void) { f32 z = 0; u8 playersTouched = 0; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { x += gMarioStates[i].marioObj->oPosX; y += gMarioStates[i].marioObj->oPosY; diff --git a/src/game/behaviors/tower_platform.inc.c b/src/game/behaviors/tower_platform.inc.c index 395a38a07..6aaddf646 100644 --- a/src/game/behaviors/tower_platform.inc.c +++ b/src/game/behaviors/tower_platform.inc.c @@ -132,6 +132,7 @@ void bhv_tower_platform_group_loop(void) { u8 anyPlayerInRange = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->oPosY > o->oHomeY - 1000.0f) { anyPlayerInRange = TRUE; } } diff --git a/src/game/behaviors/water_bomb.inc.c b/src/game/behaviors/water_bomb.inc.c index 23e99d536..6da769e17 100644 --- a/src/game/behaviors/water_bomb.inc.c +++ b/src/game/behaviors/water_bomb.inc.c @@ -44,6 +44,7 @@ void bhv_water_bomb_spawner_update(void) { struct Object* player = NULL; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } f32 latDist = lateral_dist_between_objects(o, gMarioStates[i].marioObj); if (latDist < latDistToMario) { latDistToMario = latDist; diff --git a/src/game/behaviors/water_objs.inc.c b/src/game/behaviors/water_objs.inc.c index e907e93ec..5701bfb9a 100644 --- a/src/game/behaviors/water_objs.inc.c +++ b/src/game/behaviors/water_objs.inc.c @@ -70,6 +70,44 @@ void bhv_small_water_wave_loop(void) { obj_mark_for_deletion(o); } +void bhv_bubble_player_loop(void) { + struct MarioState* marioState = &gMarioStates[o->heldByPlayerIndex]; + + // grab positions to find the mid-point + f32* torsoPos = marioState->marioBodyState->torsoPos; + f32* pos = marioState->pos; + + // sanity check torsoPos + if (marioState->marioObj->header.gfx.node.flags & GRAPH_RENDER_INVISIBLE) { + torsoPos = marioState->pos; + } + + // set the position + offset + o->oPosX = (torsoPos[0] + pos[0]) / 2; + o->oPosY = (torsoPos[1] + pos[1]) / 2 + 30.0f; + o->oPosZ = (torsoPos[2] + pos[2]) / 2; + + // slowly rotate the bubble + o->oFaceAnglePitch += 300; + o->oFaceAngleYaw += 230; + o->oFaceAngleRoll += 170; + + // scale the bubble + extern u32 gGlobalTimer; + f32 scale = sins(gGlobalTimer * 800) * 0.1f + 1.4f; + o->header.gfx.scale[0] = scale; + o->header.gfx.scale[1] = sins(gGlobalTimer * 1500) * 0.2f + scale; + o->header.gfx.scale[2] = scale; + + // check if the bubble popped + if (marioState->action != ACT_BUBBLED) { + spawn_mist_particles(); + create_sound_spawner(SOUND_OBJ_DIVING_IN_WATER); + marioState->bubbleObj = NULL; + obj_mark_for_deletion(o); + } +} + void scale_bubble_sin(void) { o->header.gfx.scale[0] = sins(o->oWaterObjUnkF4) * 0.5 + 2.0; o->oWaterObjUnkF4 += o->oWaterObjUnkFC; diff --git a/src/game/behaviors/whomp.inc.c b/src/game/behaviors/whomp.inc.c index 678e6c516..fee3fdb94 100644 --- a/src/game/behaviors/whomp.inc.c +++ b/src/game/behaviors/whomp.inc.c @@ -187,6 +187,7 @@ void whomp_on_ground(void) { if (o->oSubAction == 0) { u8 anyMarioOnPlatform = FALSE; for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { anyMarioOnPlatform = TRUE; } } if (anyMarioOnPlatform) { diff --git a/src/game/interaction.c b/src/game/interaction.c index b52baa8ac..dadc0c8e2 100644 --- a/src/game/interaction.c +++ b/src/game/interaction.c @@ -1908,6 +1908,12 @@ void mario_process_interactions(struct MarioState *m) { void check_death_barrier(struct MarioState *m) { if (m->pos[1] < m->floorHeight + 2048.0f) { + if (gCurrCourseNum != COURSE_TOTWC) { + m->pos[1] = m->floorHeight + 2048.0f; + if (m->vel[1] < 0) { m->vel[1] = 0; } + mario_set_bubbled(m); + return; + } if (level_trigger_warp(m, WARP_OP_WARP_FLOOR) == 20 && !(m->flags & MARIO_UNKNOWN_18)) { play_sound(SOUND_MARIO_WAAAOOOW, m->marioObj->header.gfx.cameraToObject); } diff --git a/src/game/level_update.c b/src/game/level_update.c index 7aa9f355f..b754e53bc 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -379,6 +379,14 @@ void init_mario_after_warp(void) { init_door_warp(&gPlayerSpawnInfos[i], sWarpDest.arg); } + // set to a minimum of two lives on level change + if (sWarpDest.type == WARP_TYPE_CHANGE_LEVEL) { + gMarioStates[i].numLives = max(gMarioStates[i].numLives, 2); + gMarioStates[i].health = 0x880; + gMarioStates[i].healCounter = 0; + gMarioStates[i].hurtCounter = 0; + } + if (sWarpDest.type == WARP_TYPE_CHANGE_LEVEL || sWarpDest.type == WARP_TYPE_CHANGE_AREA) { gPlayerSpawnInfos[i].areaIndex = sWarpDest.areaIdx; if (i == 0) { load_mario_area(); } @@ -388,6 +396,11 @@ void init_mario_after_warp(void) { init_mario(); set_mario_initial_action(gMarioState, marioSpawnType, sWarpDest.arg); + // enforce bubble on area change + if (gMarioState->playerIndex == 0 && gMarioState->numLives == -1) { + mario_set_bubbled(gMarioState); + } + gMarioState->interactObj = spawnNode->object; gMarioState->usedObj = spawnNode->object; } @@ -745,9 +758,12 @@ s16 level_trigger_warp(struct MarioState *m, s32 warpOp) { break; case WARP_OP_DEATH: - if (m->numLives == 0) { - sDelayedWarpOp = WARP_OP_GAME_OVER; + if (m->numLives < 2) { + m->numLives = 2; } + /*if (m->numLives == 0) { + sDelayedWarpOp = WARP_OP_GAME_OVER; + }*/ sDelayedWarpTimer = 48; sSourceWarpNodeId = WARP_NODE_DEATH; play_transition(WARP_TRANSITION_FADE_INTO_BOWSER, 0x30, 0x00, 0x00, 0x00); @@ -757,11 +773,11 @@ s16 level_trigger_warp(struct MarioState *m, s32 warpOp) { case WARP_OP_WARP_FLOOR: sSourceWarpNodeId = WARP_NODE_WARP_FLOOR; if (area_get_warp_node(sSourceWarpNodeId) == NULL) { - if (m->numLives == 0) { + /*if (m->numLives == 0) { sDelayedWarpOp = WARP_OP_GAME_OVER; - } else { + } else {*/ sSourceWarpNodeId = WARP_NODE_DEATH; - } + //} } sDelayedWarpTimer = 20; play_transition(WARP_TRANSITION_FADE_INTO_CIRCLE, 0x14, 0x00, 0x00, 0x00); @@ -937,7 +953,7 @@ void update_hud_values(void) { } #else if (gMarioState->numCoins > 999) { - gMarioState->numLives = (s8) 999; //! Wrong variable + gMarioState->numCoins = (s16) 999; } #endif diff --git a/src/game/mario.c b/src/game/mario.c index 07921ae64..2a756a039 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -387,6 +387,22 @@ void play_mario_sound(struct MarioState *m, s32 actionSound, s32 marioSound) { * ACTIONS * **************************************************/ +void mario_set_bubbled(struct MarioState* m) { + if (m->playerIndex != 0) { return; } + if (m->action == ACT_BUBBLED) { return; } + set_mario_action(m, ACT_BUBBLED, 0); + if (m->numLives != -1) { + m->numLives--; + } + m->healCounter = 0; + m->hurtCounter = 31; + gCamera->cutscene = 0; + m->statusForCamera->action = m->action; + m->statusForCamera->cameraEvent = 0; + extern s16 gCutsceneTimer; + gCutsceneTimer = 0; +} + /** * Sets Mario's other velocities from his forward speed. */ @@ -1204,6 +1220,8 @@ s32 transition_submerged_to_walking(struct MarioState *m) { * non-submerged action. This also applies the water surface camera preset. */ s32 set_water_plunge_action(struct MarioState *m) { + if (m->action == ACT_BUBBLED) { return FALSE; } + m->forwardVel = m->forwardVel / 4.0f; m->vel[1] = m->vel[1] / 2.0f; @@ -1828,7 +1846,9 @@ s32 execute_mario_action(UNUSED struct Object *o) { * End of cheat stuff */ if (gMarioState->action) { - gMarioState->marioObj->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE; + if (gMarioState->action != ACT_BUBBLED) { + gMarioState->marioObj->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE; + } mario_reset_bodystate(gMarioState); update_mario_inputs(gMarioState); mario_handle_special_floors(gMarioState); @@ -1989,8 +2009,10 @@ void init_mario(void) { gMarioState->quicksandDepth = 0.0f; gMarioState->heldObj = NULL; + gMarioState->heldByObj = NULL; gMarioState->riddenObj = NULL; gMarioState->usedObj = NULL; + gMarioState->bubbleObj = NULL; gMarioState->waterLevel = find_water_level(gMarioSpawnInfo->startPos[0], gMarioSpawnInfo->startPos[2]); @@ -2085,7 +2107,7 @@ void init_mario_from_save_file(void) { save_file_get_total_star_count(gCurrSaveFileNum - 1, COURSE_MIN - 1, COURSE_MAX - 1); gMarioState->numKeys = 0; - gMarioState->numLives = 4; + gMarioState->numLives = 3; gMarioState->health = 0x880; gMarioState->prevNumStarsForDialog = gMarioState->numStars; diff --git a/src/game/mario.h b/src/game/mario.h index ad0d005a8..3d927d74d 100644 --- a/src/game/mario.h +++ b/src/game/mario.h @@ -25,6 +25,7 @@ void play_mario_landing_sound_once(struct MarioState *m, u32 soundBits); void play_mario_heavy_landing_sound(struct MarioState *m, u32 soundBits); void play_mario_heavy_landing_sound_once(struct MarioState *m, u32 soundBits); void play_mario_sound(struct MarioState *m, s32 primarySoundBits, s32 scondarySoundBits); +void mario_set_bubbled(struct MarioState* m); void mario_set_forward_vel(struct MarioState *m, f32 speed); s32 mario_get_floor_class(struct MarioState *m); u32 mario_get_terrain_sound_addend(struct MarioState *m); diff --git a/src/game/mario_actions_airborne.c b/src/game/mario_actions_airborne.c index 47ec3b7b7..4094ddd23 100644 --- a/src/game/mario_actions_airborne.c +++ b/src/game/mario_actions_airborne.c @@ -1556,7 +1556,13 @@ s32 act_lava_boost(struct MarioState *m) { } if (m->health < 0x100) { - level_trigger_warp(m, WARP_OP_DEATH); + if (m != &gMarioStates[0]) { + // never kill remote marios + m->health = 0x100; + } else { + m->health = 0xFF; + return drop_and_set_mario_action(m, ACT_DEATH_ON_BACK, 0); + } } m->marioBodyState->eyeState = MARIO_EYES_DEAD; diff --git a/src/game/mario_actions_automatic.c b/src/game/mario_actions_automatic.c index e9c0f3b15..dba268596 100644 --- a/src/game/mario_actions_automatic.c +++ b/src/game/mario_actions_automatic.c @@ -17,6 +17,8 @@ #include "level_table.h" #include "thread6.h" #include "object_helpers.h" +#include "obj_behaviors.h" +#include "level_update.h" #define POLE_NONE 0 #define POLE_TOUCHED_FLOOR 1 @@ -850,6 +852,94 @@ s32 act_tornado_twirling(struct MarioState *m) { return FALSE; } +s32 act_bubbled(struct MarioState* m) { + struct MarioState* targetMarioState = nearest_mario_state_to_object(m->marioObj); + struct Object* target = targetMarioState->marioObj; + int angleToPlayer = obj_angle_to_object(m->marioObj, target); + int distanceToPlayer = dist_between_objects(m->marioObj, target); + + // trigger warp if all are bubbled + if (m->playerIndex == 0) { + u8 allInBubble = TRUE; + for (int i = 0; i < MAX_PLAYERS; i++) { + if (gMarioStates[i].action != ACT_BUBBLED) { + allInBubble = FALSE; + break; + } + } + if (allInBubble) { + level_trigger_warp(m, WARP_OP_DEATH); + return set_mario_action(m, ACT_DEATH_ON_BACK, 0); + } + } + + // create bubble + if (m->bubbleObj == NULL) { + //m->bubbleObj = spawn_object(m->marioObj, MODEL_BUBBLE, bhvBubblePlayer); + m->bubbleObj = spawn_object(m->marioObj, MODEL_BUBBLE_PLAYER, bhvBubblePlayer); + m->bubbleObj->heldByPlayerIndex = m->playerIndex; + } + + // force inactive state + if (m->heldObj != NULL) { mario_drop_held_object(m); } + m->heldByObj = NULL; + m->marioObj->oIntangibleTimer = -1; + m->squishTimer = 0; + set_mario_animation(m, MARIO_ANIM_SLEEP_IDLE); + + // force inputs + m->faceAngle[0] = 0; + m->faceAngle[1] = m->intendedYaw; + m->forwardVel = m->intendedMag; + if (m->input & INPUT_A_DOWN) { m->vel[1] += 3.0f; } + if (m->input & INPUT_Z_DOWN) { m->vel[1] -= 3.0f; } + + // set and smooth velocity + Vec3f oldVel = { m->vel[0], m->vel[1], m->vel[2] }; + set_vel_from_pitch_and_yaw(m); + for (int i = 0; i < 3; i++) { + m->vel[i] = (oldVel[i] * 0.9f + m->vel[i] * 0.1f); + } + + // move player + switch (perform_air_step(m, 0)) { + case AIR_STEP_LANDED: + m->vel[1] += 10.0f; + break; + + case AIR_STEP_HIT_WALL: + case AIR_STEP_HIT_LAVA_WALL: + m->vel[0] *= -0.99f; + m->vel[2] *= -0.99f; + break; + } + + // always look toward target + m->faceAngle[1] = angleToPlayer; + m->marioObj->header.gfx.angle[1] = angleToPlayer; + + // make invisible on -1 lives + if (m->numLives == -1) { + m->marioObj->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE; + } + + // pop bubble + if (m->playerIndex == 0 && distanceToPlayer < 200 && is_player_active(targetMarioState) && m->numLives != -1) { + m->hurtCounter = 0; + m->healCounter = 31; + m->health = 0x100; + m->marioObj->oIntangibleTimer = 0; + m->peakHeight = m->pos[1]; + m->vel[0] = 0; + m->vel[1] = 0; + m->vel[2] = 0; + m->marioObj->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE; + return set_mario_action(m, ACT_IDLE, 0); + } + + return FALSE; +} + s32 check_common_automatic_cancels(struct MarioState *m) { if (m->pos[1] < m->waterLevel - 100) { return set_water_plunge_action(m); @@ -886,6 +976,7 @@ s32 mario_execute_automatic_action(struct MarioState *m) { case ACT_GRABBED: cancel = act_grabbed(m); break; case ACT_IN_CANNON: cancel = act_in_cannon(m); break; case ACT_TORNADO_TWIRLING: cancel = act_tornado_twirling(m); break; + case ACT_BUBBLED: cancel = act_bubbled(m); break; } /* clang-format on */ diff --git a/src/game/mario_actions_cutscene.c b/src/game/mario_actions_cutscene.c index 619958f51..aff21d2da 100644 --- a/src/game/mario_actions_cutscene.c +++ b/src/game/mario_actions_cutscene.c @@ -730,7 +730,8 @@ s32 act_fall_after_star_grab(struct MarioState *m) { s32 common_death_handler(struct MarioState *m, s32 animation, s32 frameToDeathWarp) { s32 animFrame = set_mario_animation(m, animation); if (animFrame == frameToDeathWarp) { - level_trigger_warp(m, WARP_OP_DEATH); + //level_trigger_warp(m, WARP_OP_DEATH); + mario_set_bubbled(m); } m->marioBodyState->eyeState = MARIO_EYES_DEAD; stop_and_set_height_to_floor(m); @@ -810,7 +811,8 @@ s32 act_eaten_by_bubba(struct MarioState *m) { } if (m->actionTimer++ == 60) { - level_trigger_warp(m, WARP_OP_DEATH); + //level_trigger_warp(m, WARP_OP_DEATH); + mario_set_bubbled(m); } return FALSE; } @@ -1230,7 +1232,7 @@ s32 act_death_exit(struct MarioState *m) { play_sound(SOUND_MARIO_OOOF2, m->marioObj->header.gfx.cameraToObject); #endif queue_rumble_data_mario(m, 5, 80); - m->numLives--; + //m->numLives--; // restore 7.75 units of health m->healCounter = 31; } @@ -1246,7 +1248,7 @@ s32 act_unused_death_exit(struct MarioState *m) { #else play_sound(SOUND_MARIO_OOOF2, m->marioObj->header.gfx.cameraToObject); #endif - m->numLives--; + //m->numLives--; // restore 7.75 units of health m->healCounter = 31; } @@ -1263,7 +1265,7 @@ s32 act_falling_death_exit(struct MarioState *m) { play_sound(SOUND_MARIO_OOOF2, m->marioObj->header.gfx.cameraToObject); #endif queue_rumble_data_mario(m, 5, 80); - m->numLives--; + //m->numLives--; // restore 7.75 units of health m->healCounter = 31; } @@ -1308,7 +1310,7 @@ s32 act_special_death_exit(struct MarioState *m) { if (launch_mario_until_land(m, ACT_HARD_BACKWARD_GROUND_KB, MARIO_ANIM_BACKWARD_AIR_KB, -24.0f)) { queue_rumble_data_mario(m, 5, 80); - m->numLives--; + //m->numLives--; m->healCounter = 31; } // show Mario @@ -1606,9 +1608,11 @@ s32 act_squished(struct MarioState *m) { if (m->actionTimer >= 15) { // 1 unit of health if (m->health < 0x0100) { - level_trigger_warp(m, WARP_OP_DEATH); + //level_trigger_warp(m, WARP_OP_DEATH); // woosh, he's gone! - set_mario_action(m, ACT_DISAPPEARED, 0); + //set_mario_action(m, ACT_DISAPPEARED, 0); + drop_and_set_mario_action(m, ACT_DEATH_ON_BACK, 0); + m->squishTimer = 0; } else if (m->hurtCounter == 0) { // un-squish animation m->squishTimer = 30; @@ -1654,9 +1658,10 @@ s32 act_squished(struct MarioState *m) { } m->hurtCounter = 0; - level_trigger_warp(m, WARP_OP_DEATH); + //level_trigger_warp(m, WARP_OP_DEATH); // woosh, he's gone! - set_mario_action(m, ACT_DISAPPEARED, 0); + //set_mario_action(m, ACT_DISAPPEARED, 0); + mario_set_bubbled(m); } stop_and_set_height_to_floor(m); set_mario_animation(m, MARIO_ANIM_A_POSE); diff --git a/src/game/mario_actions_submerged.c b/src/game/mario_actions_submerged.c index bb6397568..b71cf5481 100644 --- a/src/game/mario_actions_submerged.c +++ b/src/game/mario_actions_submerged.c @@ -922,7 +922,8 @@ static s32 act_drowning(struct MarioState *m) { set_mario_animation(m, MARIO_ANIM_DROWNING_PART2); m->marioBodyState->eyeState = MARIO_EYES_DEAD; if (m->marioObj->header.gfx.unk38.animFrame == 30) { - level_trigger_warp(m, WARP_OP_DEATH); + //level_trigger_warp(m, WARP_OP_DEATH); + mario_set_bubbled(m); } break; } @@ -942,7 +943,8 @@ static s32 act_water_death(struct MarioState *m) { set_mario_animation(m, MARIO_ANIM_WATER_DYING); if (set_mario_animation(m, MARIO_ANIM_WATER_DYING) == 35) { - level_trigger_warp(m, WARP_OP_DEATH); + //level_trigger_warp(m, WARP_OP_DEATH); + mario_set_bubbled(m); } return FALSE; @@ -1049,7 +1051,8 @@ static s32 act_caught_in_whirlpool(struct MarioState *m) { if ((marioObj->oMarioWhirlpoolPosY += m->vel[1]) < 0.0f) { marioObj->oMarioWhirlpoolPosY = 0.0f; if (distance < 16.1f && m->actionTimer++ == 16) { - level_trigger_warp(m, WARP_OP_DEATH); + //level_trigger_warp(m, WARP_OP_DEATH); + mario_set_bubbled(m); } } diff --git a/src/game/mario_step.c b/src/game/mario_step.c index fe67ff11a..326378908 100644 --- a/src/game/mario_step.c +++ b/src/game/mario_step.c @@ -644,7 +644,7 @@ s32 perform_air_step(struct MarioState *m, u32 stepArg) { m->terrainSoundAddend = mario_get_terrain_sound_addend(m); - if (m->action != ACT_FLYING) { + if (m->action != ACT_FLYING && m->action != ACT_BUBBLED) { apply_gravity(m); } apply_vertical_wind(m); diff --git a/src/game/obj_behaviors.c b/src/game/obj_behaviors.c index eed6f68bc..0a3225a58 100644 --- a/src/game/obj_behaviors.c +++ b/src/game/obj_behaviors.c @@ -498,6 +498,7 @@ void obj_move_xyz_using_fvel_and_yaw(struct Object *obj) { */ s32 is_point_within_radius_of_mario(f32 x, f32 y, f32 z, s32 dist) { for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } struct Object* player = gMarioStates[i].marioObj; f32 mGfxX = player->header.gfx.pos[0]; f32 mGfxY = player->header.gfx.pos[1]; @@ -512,19 +513,30 @@ s32 is_point_within_radius_of_mario(f32 x, f32 y, f32 z, s32 dist) { return FALSE; } +u8 is_player_active(struct MarioState* m) { + if (m->action == ACT_BUBBLED) { return FALSE; } + return TRUE; +} + /** * Returns closest MarioState */ struct MarioState* nearest_mario_state_to_object(struct Object *obj) { struct MarioState* nearest = NULL; f32 nearestDist = 0; - for (int i = 0; i < MAX_PLAYERS; i++) { - float dist = dist_between_objects(obj, gMarioStates[i].marioObj); - if (nearest == NULL || dist < nearestDist) { - nearest = &gMarioStates[i]; - nearestDist = dist; + u8 checkActive = TRUE; + do { + for (int i = 0; i < MAX_PLAYERS; i++) { + if (gMarioStates[i].marioObj == obj) { continue; } + if (checkActive && !is_player_active(&gMarioStates[i])) { continue; } + float dist = dist_between_objects(obj, gMarioStates[i].marioObj); + if (nearest == NULL || dist < nearestDist) { + nearest = &gMarioStates[i]; + nearestDist = dist; + } } - } + checkActive = FALSE; + } while (nearest == NULL); return nearest; } diff --git a/src/game/obj_behaviors.h b/src/game/obj_behaviors.h index 36095cb57..c43444dbe 100644 --- a/src/game/obj_behaviors.h +++ b/src/game/obj_behaviors.h @@ -161,6 +161,7 @@ void bhv_free_bowling_ball_loop(void); /* likely unused */ void bhv_rr_cruiser_wing_init(void); void bhv_rr_cruiser_wing_loop(void); struct Object* spawn_default_star(f32 sp20, f32 sp24, f32 sp28); +u8 is_player_active(struct MarioState* m); struct MarioState* nearest_mario_state_to_object(struct Object* obj); struct Object* nearest_player_to_object(struct Object* obj); #endif // OBJ_BEHAVIORS_H diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c index 7eead6f5e..ed02ed68c 100644 --- a/src/game/object_helpers.c +++ b/src/game/object_helpers.c @@ -2264,6 +2264,7 @@ s32 cur_obj_wait_then_blink(s32 timeUntilBlinking, s32 numBlinks) { s32 cur_obj_is_mario_ground_pounding_platform(void) { for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { if (gMarioStates[i].action == ACT_GROUND_POUND_LAND) { return TRUE; @@ -2401,6 +2402,7 @@ s32 cur_obj_is_mario_on_platform(void) { s32 cur_obj_is_any_player_on_platform(void) { for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (gMarioStates[i].marioObj->platform == o) { return TRUE; } @@ -2457,6 +2459,7 @@ s32 bit_shift_left(s32 a0) { s32 cur_obj_mario_far_away(void) { for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } struct Object* player = gMarioStates[i].marioObj; f32 dx = o->oHomeX - player->oPosX; f32 dy = o->oHomeY - player->oPosY; @@ -2594,6 +2597,7 @@ void cur_obj_if_hit_wall_bounce_away(void) { s32 cur_obj_hide_if_mario_far_away_y(f32 distY) { for (int i = 0; i < MAX_PLAYERS; i++) { + if (!is_player_active(&gMarioStates[i])) { continue; } if (absf(o->oPosY - gMarioStates[i].marioObj->oPosY) < distY) { cur_obj_unhide(); return FALSE;