Players turn into bubbles when they die

Player life counters are separate.
When one player dies they lose a life and are turned into a bubble.
If the other player pops it, they are alive again.
If all players are bubbled, they get kicked out of the level.
If the bubbled player ran out of lives, they can not come back to life
until the level is over.
Whenever a level change happens, everyone's life count is set to a
minimum of two.
No game overs.

Took heavy inspiration from Kaze Emanuar
This commit is contained in:
MysterD 2020-09-05 18:05:57 -07:00
parent 9427afb14b
commit 906ea3345e
39 changed files with 301 additions and 42 deletions

View File

@ -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(),
};

View File

@ -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,

View File

@ -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[];

View File

@ -23,6 +23,7 @@ enum BehaviorId {
id_bhvBetaChestLid,
id_bhvBubbleParticleSpawner,
id_bhvBubbleMaybe,
id_bhvBubblePlayer,
id_bhvSmallWaterWave,
id_bhvWaterAirBubble,
id_bhvSmallParticle,

View File

@ -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

View File

@ -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)

View File

@ -368,6 +368,7 @@ struct MarioState
/*0xC8*/ s16 currentRoom;
/*0xCA*/ struct Object* heldByObj;
/*????*/ u8 isSnoring;
/*????*/ struct Object* bubbleObj;
};
#define PLAY_MODE_NORMAL 0

View File

@ -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),

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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)

View File

@ -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; }

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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; }
}

View File

@ -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;

View File

@ -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]);
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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; }
}

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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 */

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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;