From dfb3b2523d7abd1e2a2e8339cfad667e5f71a059 Mon Sep 17 00:00:00 2001 From: Blockyyy <88585273+Blockyyy@users.noreply.github.com> Date: Fri, 27 Mar 2026 04:32:44 +0100 Subject: [PATCH] Fix lives (#1119) - Fix lives going below 0 right before a game over (also fixes a crash where if you exited with -1 lives and went into a subarea you would crash) - Restore how lives get removed upon death exit (like in vanilla) - Prevent bubbling from happening if you don't have enough lives (this fixes the annoying bug where you would get put back in a bubble and get popped immediately after over and over again) --- src/game/interaction.c | 2 +- src/game/level_update.c | 6 ++---- src/game/mario.c | 2 +- src/game/mario_actions_airborne.c | 2 +- src/game/mario_actions_cutscene.c | 21 +++++++++++++++------ src/game/mario_actions_submerged.c | 6 +++--- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/game/interaction.c b/src/game/interaction.c index 4304a23ae..599d6c65a 100644 --- a/src/game/interaction.c +++ b/src/game/interaction.c @@ -2445,7 +2445,7 @@ void check_death_barrier(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { switch (gCurrCourseNum) { case COURSE_COTMC: // (20) Cavern of the Metal Cap case COURSE_TOTWC: // (21) Tower of the Wing Cap diff --git a/src/game/level_update.c b/src/game/level_update.c index 1ad390601..ff6ae25b1 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -878,8 +878,7 @@ void verify_warp(struct MarioState *m, bool killMario) { return; } - m->numLives--; - if (m->numLives < 0) { + if (m->numLives <= 0) { sDelayedWarpOp = WARP_OP_GAME_OVER; } else { sSourceWarpNodeId = WARP_NODE_DEATH; @@ -934,8 +933,7 @@ s16 level_trigger_warp(struct MarioState *m, s32 warpOp) { break; case WARP_OP_DEATH: - m->numLives--; - if (m->numLives <= -1) { + if (m->numLives <= 0) { sDelayedWarpOp = WARP_OP_GAME_OVER; } sDelayedWarpTimer = 48; diff --git a/src/game/mario.c b/src/game/mario.c index 897bbc3fe..d929544f6 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -447,7 +447,7 @@ void mario_set_bubbled(struct MarioState* m) { gLocalBubbleCounter = 20; drop_and_set_mario_action(m, ACT_BUBBLED, 0); - if (m->numLives > -1) { + if (m->numLives > 0) { m->numLives--; } m->healCounter = 0; diff --git a/src/game/mario_actions_airborne.c b/src/game/mario_actions_airborne.c index 00b338447..6112358eb 100644 --- a/src/game/mario_actions_airborne.c +++ b/src/game/mario_actions_airborne.c @@ -1734,7 +1734,7 @@ s32 act_lava_boost(struct MarioState *m) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { m->health = 0xFF; mario_set_bubbled(m); } else { diff --git a/src/game/mario_actions_cutscene.c b/src/game/mario_actions_cutscene.c index 9162cd84a..293079dc4 100644 --- a/src/game/mario_actions_cutscene.c +++ b/src/game/mario_actions_cutscene.c @@ -849,7 +849,7 @@ s32 common_death_handler(struct MarioState *m, s32 animation, s32 frameToDeathWa smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return animFrame; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -922,8 +922,7 @@ s32 act_quicksand_death(struct MarioState *m) { bool allowDeath = true; smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -947,7 +946,7 @@ s32 act_eaten_by_bubba(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { m->health = 0xFF; mario_set_bubbled(m); } else { @@ -1437,6 +1436,12 @@ s32 act_exit_land_save_dialog(struct MarioState *m) { return FALSE; } +static void lose_life_after_death_exit(struct MarioState *m) { + if (sDelayedWarpArg != WARP_ARG_EXIT_COURSE) { + m->numLives--; + } +} + s32 act_death_exit(struct MarioState *m) { if (!m) { return 0; } if (15 < m->actionTimer++ @@ -1447,6 +1452,7 @@ s32 act_death_exit(struct MarioState *m) { play_character_sound(m, CHAR_SOUND_OOOF2); #endif queue_rumble_data_mario(m, 5, 80); + lose_life_after_death_exit(m); // restore 7.75 units of health m->healCounter = 31; } @@ -1463,6 +1469,7 @@ s32 act_unused_death_exit(struct MarioState *m) { #else play_character_sound(m, CHAR_SOUND_OOOF2); #endif + lose_life_after_death_exit(m); // restore 7.75 units of health m->healCounter = 31; } @@ -1479,6 +1486,7 @@ s32 act_falling_death_exit(struct MarioState *m) { #else play_character_sound(m, CHAR_SOUND_OOOF2); #endif + lose_life_after_death_exit(m); queue_rumble_data_mario(m, 5, 80); // restore 7.75 units of health m->healCounter = 31; @@ -1526,6 +1534,7 @@ s32 act_special_death_exit(struct MarioState *m) { if (launch_mario_until_land(m, ACT_HARD_BACKWARD_GROUND_KB, CHAR_ANIM_BACKWARD_AIR_KB, -24.0f)) { queue_rumble_data_mario(m, 5, 80); + lose_life_after_death_exit(m); m->healCounter = 31; } // show Mario @@ -1829,7 +1838,7 @@ s32 act_squished(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -1880,7 +1889,7 @@ s32 act_squished(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { // 0 units of health diff --git a/src/game/mario_actions_submerged.c b/src/game/mario_actions_submerged.c index 61f3a0e15..e4195f64c 100644 --- a/src/game/mario_actions_submerged.c +++ b/src/game/mario_actions_submerged.c @@ -1003,7 +1003,7 @@ static s32 act_drowning(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -1038,7 +1038,7 @@ static s32 act_water_death(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -1164,7 +1164,7 @@ static s32 act_caught_in_whirlpool(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { reset_rumble_timers(m); return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH);