sm64coopdx/src/game/behaviors/monty_mole.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

498 lines
15 KiB
C

/**
* Behavior for bhvMontyMole, bhvMontyMoleHole, and bhvMontyMoleRock.
* The monty mole holes are placed statically. The monty moles repeatedly
* search for an available hole to pop out of.
*/
/**
* The first hole in the list of monty mole holes. The list is a singly linked
* list using the parentObj field.
*/
struct Object *sMontyMoleHoleList;
/**
* The number of nearby monty moles that have been killed in a row.
*/
s32 sMontyMoleKillStreak;
/**
* The position of the last killed monty mole, used for determining whether
* the next killed monty mole is nearby.
*/
f32 sMontyMoleLastKilledPosX;
f32 sMontyMoleLastKilledPosY;
f32 sMontyMoleLastKilledPosZ;
/**
* Link all objects with the given behavior using parentObj.
* The result is a singly linked list in reverse processing order. Return the
* start of this list.
*/
static struct Object *link_objects_with_behavior(const BehaviorScript *behavior) {
const BehaviorScript *behaviorAddr;
struct Object *obj;
struct Object *lastObject;
struct ObjectNode *listHead;
behaviorAddr = segmented_to_virtual(behavior);
lastObject = NULL;
listHead = &gObjectLists[get_object_list_from_behavior(behaviorAddr)];
obj = (struct Object *) listHead->next;
while (obj != (struct Object *) listHead) {
if (obj->behavior == behaviorAddr && obj->activeFlags != ACTIVE_FLAG_DEACTIVATED) {
obj->parentObj = lastObject;
lastObject = obj;
}
obj = (struct Object *) obj->header.next;
}
return lastObject;
}
/**
* Select a random hole that is within minDistToMario and 1500 of mario, and
* whose cooldown is zero. Return NULL if no hole is available.
*/
static struct Object *monty_mole_select_available_hole(f32 minDistToMario) {
struct Object *hole = sMontyMoleHoleList;
s32 numAvailableHoles = 0;
while (hole != NULL) {
if (hole->oMontyMoleHoleCooldown == 0) {
if (hole->oDistanceToMario < 1500.0f && hole->oDistanceToMario > minDistToMario) {
numAvailableHoles++;
}
}
hole = hole->parentObj;
}
if (numAvailableHoles != 0) {
s32 selectedHole = (s32)(random_float() * numAvailableHoles);
hole = sMontyMoleHoleList;
numAvailableHoles = 0;
while (hole != NULL) {
if (hole->oMontyMoleHoleCooldown == 0) {
if (hole->oDistanceToMario < 1500.0f && hole->oDistanceToMario > minDistToMario) {
if (numAvailableHoles == selectedHole) {
return hole;
}
numAvailableHoles++;
}
}
hole = hole->parentObj;
}
}
return NULL;
}
/**
* Update function for bhvMontyMoleHole.
*/
void bhv_monty_mole_hole_update(void) {
// If hole list hasn't been constructed yet, construct it
if (o->parentObj == o) {
sMontyMoleHoleList = link_objects_with_behavior(bhvMontyMoleHole);
sMontyMoleKillStreak = 0;
} else if (o->oMontyMoleHoleCooldown > 0) {
o->oMontyMoleHoleCooldown -= 1;
}
}
/**
* Spawn dirt particles when rising out of the ground.
*/
void monty_mole_spawn_dirt_particles(s8 offsetY, s8 velYBase) {
static struct SpawnParticlesInfo sMontyMoleRiseFromGroundParticles = {
/* behParam: */ 0,
/* count: */ 3,
/* model: */ MODEL_SAND_DUST,
/* offsetY: */ 0,
/* forwardVelBase: */ 4,
/* forwardVelRange: */ 4,
/* velYBase: */ 10,
/* velYRange: */ 15,
/* gravity: */ -4,
/* dragStrength: */ 0,
/* sizeBase: */ 10.0f,
/* sizeRange: */ 7.0f,
};
sMontyMoleRiseFromGroundParticles.offsetY = offsetY;
sMontyMoleRiseFromGroundParticles.velYBase = velYBase;
cur_obj_spawn_particles(&sMontyMoleRiseFromGroundParticles);
}
/**
* Init function for bhvMontyMole.
*/
void bhv_monty_mole_init(void) {
o->oMontyMoleCurrentHole = NULL;
}
/**
* Search for an available hole. Once one is available, claim it and enter
* either the rise from hole or jump out of hole action.
*/
static void monty_mole_act_select_hole(void) {
f32 minDistToMario;
if (o->oBehParams2ndByte != MONTY_MOLE_BP_NO_ROCK) {
minDistToMario = 200.0f;
} else if (gMarioStates[0].forwardVel < 8.0f) {
minDistToMario = 100.0f;
} else {
minDistToMario = 500.0f;
}
// Select a hole to pop out of
if ((o->oMontyMoleCurrentHole = monty_mole_select_available_hole(minDistToMario)) != NULL) {
cur_obj_play_sound_2(SOUND_OBJ2_MONTY_MOLE_APPEAR);
// Mark hole as unavailable
o->oMontyMoleCurrentHole->oMontyMoleHoleCooldown = -1;
// Position at hole
o->oPosX = o->oMontyMoleCurrentHole->oPosX;
o->oPosY = o->oFloorHeight = o->oMontyMoleCurrentHole->oPosY;
o->oPosZ = o->oMontyMoleCurrentHole->oPosZ;
o->oFaceAnglePitch = 0;
o->oMoveAngleYaw = o->oMontyMoleCurrentHole->oAngleToMario;
if (o->oDistanceToMario > 500.0f || minDistToMario > 100.0f || random_sign() < 0) {
o->oAction = MONTY_MOLE_ACT_RISE_FROM_HOLE;
o->oVelY = 3.0f;
o->oGravity = 0.0f;
monty_mole_spawn_dirt_particles(0, 10);
} else {
o->oAction = MONTY_MOLE_ACT_JUMP_OUT_OF_HOLE;
o->oVelY = 50.0f;
o->oGravity = -4.0f;
monty_mole_spawn_dirt_particles(0, 20);
}
cur_obj_unhide();
cur_obj_become_tangible();
}
}
/**
* Move upward until high enough, then enter the spawn rock action.
*/
static void monty_mole_act_rise_from_hole(void) {
cur_obj_init_animation_with_sound(1);
if (o->oMontyMoleHeightRelativeToFloor >= 49.0f) {
o->oPosY = o->oFloorHeight + 50.0f;
o->oVelY = 0.0f;
if (cur_obj_check_if_near_animation_end()) {
o->oAction = MONTY_MOLE_ACT_SPAWN_ROCK;
}
}
}
/**
* If mario is close enough, then spawn a rock and enter the throw rock action.
* Otherwise, enter the begin jump into hole action.
*/
static void monty_mole_act_spawn_rock(void) {
struct Object *rock;
if (cur_obj_init_anim_and_check_if_end(2)) {
if (o->oBehParams2ndByte != MONTY_MOLE_BP_NO_ROCK
&& abs_angle_diff(o->oAngleToMario, o->oMoveAngleYaw) < 0x4000
&& (rock = spawn_object(o, MODEL_PEBBLE, bhvMontyMoleRock)) != NULL) {
o->prevObj = rock;
o->oAction = MONTY_MOLE_ACT_THROW_ROCK;
} else {
o->oAction = MONTY_MOLE_ACT_BEGIN_JUMP_INTO_HOLE;
}
}
}
/**
* Wait until mario is relatively close, then set vely to 40 and enter the jump
* into hole action.
*/
static void monty_mole_act_begin_jump_into_hole(void) {
if (cur_obj_init_anim_and_check_if_end(3) || obj_is_near_to_and_facing_mario(&gMarioStates[0], 1000.0f, 0x4000)) {
o->oAction = MONTY_MOLE_ACT_JUMP_INTO_HOLE;
o->oVelY = 40.0f;
o->oGravity = -6.0f;
}
}
/**
* Throw the held rock, then enter the begin jump into hole action.
*/
static void monty_mole_act_throw_rock(void) {
if (cur_obj_init_anim_check_frame(8, 10)) {
cur_obj_play_sound_2(SOUND_OBJ_MONTY_MOLE_ATTACK);
o->prevObj = NULL;
}
if (cur_obj_check_if_near_animation_end()) {
o->oAction = MONTY_MOLE_ACT_BEGIN_JUMP_INTO_HOLE;
}
}
/**
* Tilt downward and wait until close to landing, then enter the hide action.
*/
static void monty_mole_act_jump_into_hole(void) {
cur_obj_init_anim_extend(0);
o->oFaceAnglePitch = -atan2s(o->oVelY, -4.0f);
if (o->oVelY < 0.0f && o->oMontyMoleHeightRelativeToFloor < 120.0f) {
o->oAction = MONTY_MOLE_ACT_HIDE;
o->oGravity = 0.0f;
monty_mole_spawn_dirt_particles(-80, 15);
}
}
/**
* Become intangible and enter the select hole action.
*/
static void monty_mole_hide_in_hole(void) {
o->oMontyMoleCurrentHole->oMontyMoleHoleCooldown = 30;
o->oAction = MONTY_MOLE_ACT_SELECT_HOLE;
o->oVelY = 0.0f;
//! Even though the object becomes intangible here, it is still possible
// for a bob-omb to interact with it later in the frame (since bob-ombs
// come after monty moles in processing order).
// This means that the monty mole can be attacked while in the select hole
// action. If no hole is available (e.g. because mario is too far away),
// the game will crash because of the line above that accesses
// oMontyMoleCurrentHole.
cur_obj_become_intangible();
}
/**
* Wait to land on the floor, then hide.
*/
static void monty_mole_act_hide(void) {
cur_obj_init_animation_with_sound(1);
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
cur_obj_hide();
monty_mole_hide_in_hole();
} else {
approach_f32_ptr(&o->oVelY, -4.0f, 0.5f);
}
}
/**
* Jump upward and then fall back down. Then enter the begin jump into hole
* action.
*/
static void monty_mole_act_jump_out_of_hole(void) {
if (o->oVelY > 0.0f) {
cur_obj_init_animation_with_sound(9);
} else {
cur_obj_init_anim_extend(4);
if (o->oMontyMoleHeightRelativeToFloor < 50.0f) {
o->oPosY = o->oFloorHeight + 50.0f;
o->oAction = MONTY_MOLE_ACT_BEGIN_JUMP_INTO_HOLE;
o->oVelY = o->oGravity = 0.0f;
}
}
}
/**
* Hitbox for monty mole.
*/
static struct ObjectHitbox sMontyMoleHitbox = {
/* interactType: */ INTERACT_BOUNCE_TOP,
/* downOffset: */ 0,
/* damageOrCoinValue: */ 2,
/* health: */ -1,
/* numLootCoins: */ 0,
/* radius: */ 70,
/* height: */ 50,
/* hurtboxRadius: */ 30,
/* hurtboxHeight: */ 40,
};
/**
* Update function for bhvMontyMole.
*/
void bhv_monty_mole_update(void) {
// PARTIAL_UPDATE
o->oDeathSound = SOUND_OBJ_DYING_ENEMY1;
cur_obj_update_floor_and_walls();
o->oMontyMoleHeightRelativeToFloor = o->oPosY - o->oFloorHeight;
switch (o->oAction) {
case MONTY_MOLE_ACT_SELECT_HOLE:
monty_mole_act_select_hole();
break;
case MONTY_MOLE_ACT_RISE_FROM_HOLE:
monty_mole_act_rise_from_hole();
break;
case MONTY_MOLE_ACT_SPAWN_ROCK:
monty_mole_act_spawn_rock();
break;
case MONTY_MOLE_ACT_BEGIN_JUMP_INTO_HOLE:
monty_mole_act_begin_jump_into_hole();
break;
case MONTY_MOLE_ACT_THROW_ROCK:
monty_mole_act_throw_rock();
break;
case MONTY_MOLE_ACT_JUMP_INTO_HOLE:
monty_mole_act_jump_into_hole();
break;
case MONTY_MOLE_ACT_HIDE:
monty_mole_act_hide();
break;
case MONTY_MOLE_ACT_JUMP_OUT_OF_HOLE:
monty_mole_act_jump_out_of_hole();
break;
}
// Spawn a 1-up if you kill 8 monty moles
if (obj_check_attacks(&sMontyMoleHitbox, o->oAction)) {
if (sMontyMoleKillStreak != 0) {
f32 dx = o->oPosX - sMontyMoleLastKilledPosX;
f32 dy = o->oPosY - sMontyMoleLastKilledPosY;
f32 dz = o->oPosZ - sMontyMoleLastKilledPosZ;
f32 distToLastKill = sqrtf(dx * dx + dy * dy + dz * dz);
//! The two farthest holes on the bottom level of TTM are more than
// 1500 units away from each other, so the counter resets if you
// attack moles in these holes consecutively.
if (distToLastKill < 1500.0f) {
if (sMontyMoleKillStreak == 7) {
play_puzzle_jingle();
spawn_object(o, MODEL_1UP, bhv1upWalking);
}
} else {
sMontyMoleKillStreak = 0;
}
}
//! No overflow check
sMontyMoleKillStreak += 1;
sMontyMoleLastKilledPosX = o->oPosX;
sMontyMoleLastKilledPosY = o->oPosY;
sMontyMoleLastKilledPosZ = o->oPosZ;
monty_mole_hide_in_hole();
// Throw rock if holding one
o->prevObj = NULL;
}
cur_obj_move_standard(78);
}
/**
* Wait for monty mole to throw the rock, then enter the move action.
*/
static void monty_mole_rock_act_held(void) {
// The position is offset since the monty mole is throwing it with its hand
o->oParentRelativePosX = 80.0f;
o->oParentRelativePosY = -50.0f;
o->oParentRelativePosZ = 0.0f;
if (o->parentObj->prevObj == NULL) {
f32 distToMario = o->oDistanceToMario;
if (distToMario > 600.0f) {
distToMario = 600.0f;
}
o->oAction = MONTY_MOLE_ROCK_ACT_MOVE;
// The angle is adjusted to compensate for the start position offset
o->oMoveAngleYaw = (s32)(o->parentObj->oMoveAngleYaw + 0x1F4 - distToMario * 0.1f);
o->oForwardVel = 40.0f;
o->oVelY = distToMario * 0.08f + 8.0f;
o->oMoveFlags = 0;
}
}
/**
* Hitbox for monty mole rock.
*/
static struct ObjectHitbox sMontyMoleRockHitbox = {
/* interactType: */ INTERACT_MR_BLIZZARD,
/* downOffset: */ 15,
/* damageOrCoinValue: */ 1,
/* health: */ 99,
/* numLootCoins: */ 0,
/* radius: */ 30,
/* height: */ 15,
/* hurtboxRadius: */ 30,
/* hurtboxHeight: */ 15,
};
/**
* The particles that spawn when a monty mole rock breaks.
*/
static struct SpawnParticlesInfo sMontyMoleRockBreakParticles = {
/* behParam: */ 0,
/* count: */ 2,
/* model: */ MODEL_PEBBLE,
/* offsetY: */ 10,
/* forwardVelBase: */ 4,
/* forwardVelRange: */ 4,
/* velYBase: */ 10,
/* velYRange: */ 15,
/* gravity: */ -4,
/* dragStrength: */ 0,
/* sizeBase: */ 8.0f,
/* sizeRange: */ 4.0f,
};
/**
* Move, then despawn after hitting the ground or water.
*/
static void monty_mole_rock_act_move(void) {
cur_obj_update_floor_and_walls();
if (o->oMoveFlags & (OBJ_MOVE_MASK_ON_GROUND | OBJ_MOVE_ENTERED_WATER)) {
cur_obj_spawn_particles(&sMontyMoleRockBreakParticles);
obj_mark_for_deletion(o);
}
cur_obj_move_standard(78);
}
/**
* Update function for bhvMontyMoleRock.
*/
void bhv_monty_mole_rock_update(void) {
// PARTIAL_UPDATE
//! Since we can prevent them from despawning using partial updates, we
// can fill up object slots to crash the game.
obj_check_attacks(&sMontyMoleRockHitbox, o->oAction);
switch (o->oAction) {
case MONTY_MOLE_ROCK_ACT_HELD:
monty_mole_rock_act_held();
break;
case MONTY_MOLE_ROCK_ACT_MOVE:
monty_mole_rock_act_move();
break;
}
}