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

591 lines
21 KiB
C

/**
* Behavior for bhvChainChomp, bhvChainChompChainPart, bhvWoodenPost, and bhvChainChompGate.
* bhvChainChomp spawns its bhvWoodenPost in its behavior script. It spawns 5 chain
* parts. Part 0 is the "pivot", which is positioned at the wooden post while
* the chomp is chained up. Parts 1-4 are the other parts, starting from the
* chain chomp and moving toward the pivot.
* Processing order is bhvWoodenPost, bhvChainChompGate, bhvChainChomp, bhvChainChompChainPart.
* The chain parts are processed starting at the post and ending at the chomp.
*/
/**
* Hitbox for chain chomp.
*/
static struct ObjectHitbox sChainChompHitbox = {
/* interactType: */ INTERACT_MR_BLIZZARD,
/* downOffset: */ 0,
/* damageOrCoinValue: */ 3,
/* health: */ 1,
/* numLootCoins: */ 0,
/* radius: */ 80,
/* height: */ 160,
/* hurtboxRadius: */ 80,
/* hurtboxHeight: */ 160,
};
/**
* Update function for chain chomp part / pivot.
*/
void bhv_chain_chomp_chain_part_update(void) {
struct ChainSegment *segment;
if (o->parentObj->behavior != &bhvChainChomp || o->parentObj->oAction == CHAIN_CHOMP_ACT_UNLOAD_CHAIN) {
obj_mark_for_deletion(o);
} else if (o->oBehParams2ndByte != CHAIN_CHOMP_CHAIN_PART_BP_PIVOT) {
segment = &o->parentObj->oChainChompSegments[o->oBehParams2ndByte];
// Set position relative to the pivot
o->oPosX = o->parentObj->parentObj->oPosX + segment->posX;
o->oPosY = o->parentObj->parentObj->oPosY + segment->posY;
o->oPosZ = o->parentObj->parentObj->oPosZ + segment->posZ;
;
} else if (o->parentObj->oChainChompReleaseStatus != CHAIN_CHOMP_NOT_RELEASED) {
cur_obj_update_floor_and_walls();
cur_obj_move_standard(78);
}
}
/**
* When mario gets close enough, allocate chain segments and spawn their objects.
*/
static void chain_chomp_act_uninitialized(void) {
struct ChainSegment *segments;
s32 i;
#ifndef NODRAWINGDISTANCE
struct Object* player = nearest_player_to_object(o);
int distanceToPlayer = dist_between_objects(o, player);
if (distanceToPlayer < 3000.0f) {
#endif
segments = mem_pool_alloc(gObjectMemoryPool, 5 * sizeof(struct ChainSegment));
if (segments != NULL) {
// Each segment represents the offset of a chain part to the pivot.
// Segment 0 connects the pivot to the chain chomp itself. Segment
// 1 connects the pivot to the chain part next to the chain chomp
// (chain part 1), etc.
o->oChainChompSegments = segments;
for (i = 0; i <= 4; i++) {
chain_segment_init(&segments[i]);
}
cur_obj_set_pos_to_home();
// Spawn the pivot and set to parent
if ((o->parentObj =
spawn_object(o, CHAIN_CHOMP_CHAIN_PART_BP_PIVOT, bhvChainChompChainPart))
!= NULL) {
// Spawn the non-pivot chain parts, starting from the chain
// chomp and moving toward the pivot
for (i = 1; i <= 4; i++) {
spawn_object_relative(i, 0, 0, 0, o, MODEL_METALLIC_BALL, bhvChainChompChainPart);
}
o->oAction = CHAIN_CHOMP_ACT_MOVE;
cur_obj_unhide();
}
}
#ifndef NODRAWINGDISTANCE
}
#endif
}
/**
* Apply gravity to each chain part, and cap its distance to the previous
* part as well as from the pivot.
*/
static void chain_chomp_update_chain_segments(void) {
struct ChainSegment *prevSegment;
struct ChainSegment *segment;
f32 offsetX;
f32 offsetY;
f32 offsetZ;
f32 offset;
f32 segmentVelY;
f32 maxTotalOffset;
s32 i;
if (o->oVelY < 0.0f) {
segmentVelY = o->oVelY;
} else {
segmentVelY = -20.0f;
}
// Segment 0 connects the pivot to the chain chomp itself, and segment i>0
// connects the pivot to chain part i (1 is closest to the chain chomp).
for (i = 1; i <= 4; i++) {
prevSegment = &o->oChainChompSegments[i - 1];
segment = &o->oChainChompSegments[i];
// Apply gravity
if ((segment->posY += segmentVelY) < 0.0f) {
segment->posY = 0.0f;
}
// Cap distance to previous chain part (so that the tail follows the
// chomp)
offsetX = segment->posX - prevSegment->posX;
offsetY = segment->posY - prevSegment->posY;
offsetZ = segment->posZ - prevSegment->posZ;
offset = sqrtf(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ);
if (offset > o->oChainChompMaxDistBetweenChainParts) {
offset = o->oChainChompMaxDistBetweenChainParts / offset;
offsetX *= offset;
offsetY *= offset;
offsetZ *= offset;
}
// Cap distance to pivot (so that it stretches when the chomp moves far
// from the wooden post)
offsetX += prevSegment->posX;
offsetY += prevSegment->posY;
offsetZ += prevSegment->posZ;
offset = sqrtf(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ);
maxTotalOffset = o->oChainChompMaxDistFromPivotPerChainPart * (5 - i);
if (offset > maxTotalOffset) {
offset = maxTotalOffset / offset;
offsetX *= offset;
offsetY *= offset;
offsetZ *= offset;
}
segment->posX = offsetX;
segment->posY = offsetY;
segment->posZ = offsetZ;
}
}
/**
* Lunging increases the maximum distance from the pivot and changes the maximum
* distance between chain parts. Restore these values to normal.
*/
static void chain_chomp_restore_normal_chain_lengths(void) {
approach_f32_ptr(&o->oChainChompMaxDistFromPivotPerChainPart, 750.0f / 5, 4.0f);
o->oChainChompMaxDistBetweenChainParts = o->oChainChompMaxDistFromPivotPerChainPart;
}
/**
* Turn toward mario. Wait a bit and then enter the lunging sub-action.
*/
static void chain_chomp_sub_act_turn(void) {
o->oGravity = -4.0f;
chain_chomp_restore_normal_chain_lengths();
obj_move_pitch_approach(0, 0x100);
struct Object* player = nearest_player_to_object(o);
int distanceToPlayer = dist_between_objects(o, player);
int angleToPlayer = obj_angle_to_object(o, player);
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
cur_obj_rotate_yaw_toward(angleToPlayer, 0x400);
if (abs_angle_diff(angleToPlayer, o->oMoveAngleYaw) < 0x800 && distanceToPlayer < 3000) {
if (o->oTimer > 30) {
if (cur_obj_check_anim_frame(0)) {
cur_obj_reverse_animation();
if (o->oTimer > 40) {
// Increase the maximum distance from the pivot and enter
// the lunging sub-action.
cur_obj_play_sound_2(SOUND_GENERAL_CHAIN_CHOMP2);
o->oSubAction = CHAIN_CHOMP_SUB_ACT_LUNGE;
o->oChainChompMaxDistFromPivotPerChainPart = 900.0f / 5;
o->oForwardVel = 140.0f;
o->oVelY = 20.0f;
o->oGravity = 0.0f;
o->oChainChompTargetPitch = obj_get_pitch_from_vel();
}
} else {
o->oTimer -= 1;
}
} else {
o->oForwardVel = 0.0f;
}
} else {
cur_obj_play_sound_2(SOUND_GENERAL_CHAIN_CHOMP1);
o->oForwardVel = 10.0f;
o->oVelY = 20.0f;
}
} else {
cur_obj_rotate_yaw_toward(angleToPlayer, 0x190);
o->oTimer = 0;
}
}
static void chain_chomp_sub_act_lunge(void) {
f32 val04;
obj_face_pitch_approach(o->oChainChompTargetPitch, 0x400);
if (o->oForwardVel != 0.0f) {
if (o->oChainChompRestrictedByChain == TRUE) {
o->oForwardVel = o->oVelY = 0.0f;
o->oChainChompUnk104 = 30.0f;
}
// TODO: What is this
if ((val04 = 900.0f - o->oChainChompDistToPivot) > 220.0f) {
val04 = 220.0f;
}
o->oChainChompMaxDistBetweenChainParts =
val04 / 220.0f * o->oChainChompMaxDistFromPivotPerChainPart;
o->oTimer = 0;
;
} else {
// Turn toward pivot
cur_obj_rotate_yaw_toward(atan2s(o->oChainChompSegments[0].posZ, o->oChainChompSegments[0].posX),
0x1000);
if (o->oChainChompUnk104 != 0.0f) {
approach_f32_ptr(&o->oChainChompUnk104, 0.0f, 0.8f);
} else {
o->oSubAction = CHAIN_CHOMP_SUB_ACT_TURN;
}
o->oChainChompMaxDistBetweenChainParts = o->oChainChompUnk104;
if (gGlobalTimer % 2 != 0) {
o->oChainChompMaxDistBetweenChainParts = -o->oChainChompUnk104;
}
}
if (o->oTimer < 30) {
cur_obj_reverse_animation();
}
}
/**
* Fall to the ground and interrupt mario into a cutscene action.
*/
static void chain_chomp_released_trigger_cutscene(void) {
o->oForwardVel = 0.0f;
o->oGravity = -4.0f;
//! Can delay this if we get into a cutscene-unfriendly action after the
// last post ground pound and before this
if (set_mario_npc_dialog(&gMarioStates[0], 2) == 2 && (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND)
&& cutscene_object(CUTSCENE_STAR_SPAWN, o) == 1) {
o->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_LUNGE_AROUND;
o->oTimer = 0;
}
}
/**
* Lunge 4 times, each time moving toward mario +/- 0x2000 angular units.
* Finally, begin a lunge toward x=1450, z=562 (near the gate).
*/
static void chain_chomp_released_lunge_around(void) {
chain_chomp_restore_normal_chain_lengths();
// Finish bounce
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
struct Object* player = nearest_player_to_object(o);
int angleToPlayer = obj_angle_to_object(o, player);
// Before first bounce, turn toward mario and wait 2 seconds
if (o->oChainChompNumLunges == 0) {
if (cur_obj_rotate_yaw_toward(angleToPlayer, 0x320)) {
if (o->oTimer > 60) {
o->oChainChompNumLunges += 1;
// enable wall collision
o->oWallHitboxRadius = 200.0f;
}
} else {
o->oTimer = 0;
}
} else {
if (++o->oChainChompNumLunges <= 5) {
cur_obj_play_sound_2(SOUND_GENERAL_CHAIN_CHOMP1);
o->oMoveAngleYaw = angleToPlayer + random_sign() * 0x2000;
o->oForwardVel = 30.0f;
o->oVelY = 50.0f;
} else {
o->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_BREAK_GATE;
o->oHomeX = 1450.0f;
o->oHomeZ = 562.0f;
o->oMoveAngleYaw = cur_obj_angle_to_home();
o->oForwardVel = cur_obj_lateral_dist_to_home() / 8;
o->oVelY = 50.0f;
}
}
}
}
/**
* Continue lunging until a wall collision occurs. Mark the gate as destroyed,
* wait for the chain chomp to land, and then begin a jump toward the final
* target, x=3288, z=-1770.
*/
static void chain_chomp_released_break_gate(void) {
if (!o->oChainChompHitGate) {
// If hit wall, assume it's the gate and bounce off of it
//! The wall may not be the gate
//! If the chain chomp gets stuck, it may never hit a wall, resulting
// in a softlock
if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
o->oChainChompHitGate = TRUE;
o->oMoveAngleYaw = cur_obj_reflect_move_angle_off_wall();
o->oForwardVel *= 0.4f;
}
} else if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
o->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_JUMP_AWAY;
o->oHomeX = 3288.0f;
o->oHomeZ = -1770.0f;
o->oMoveAngleYaw = cur_obj_angle_to_home();
o->oForwardVel = cur_obj_lateral_dist_to_home() / 50.0f;
o->oVelY = 120.0f;
}
}
/**
* Wait until the chain chomp lands.
*/
static void chain_chomp_released_jump_away(void) {
if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
gObjCutsceneDone = TRUE;
o->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_END_CUTSCENE;
}
}
/**
* Release mario and transition to the unload chain action.
*/
static void chain_chomp_released_end_cutscene(void) {
if (cutscene_object(CUTSCENE_STAR_SPAWN, o) == -1) {
set_mario_npc_dialog(&gMarioStates[0], 0);
o->oAction = CHAIN_CHOMP_ACT_UNLOAD_CHAIN;
}
}
/**
* All chain chomp movement behavior, including the cutscene after being
* released.
*/
static void chain_chomp_act_move(void) {
f32 maxDistToPivot;
// Unload chain if mario is far enough
#ifndef NODRAWINGDISTANCE
struct Object* player = nearest_player_to_object(o);
int distanceToPlayer = dist_between_objects(o, player);
if (o->oChainChompReleaseStatus == CHAIN_CHOMP_NOT_RELEASED && distanceToPlayer > 4000.0f) {
o->oAction = CHAIN_CHOMP_ACT_UNLOAD_CHAIN;
o->oForwardVel = o->oVelY = 0.0f;
} else {
#endif
cur_obj_update_floor_and_walls();
switch (o->oChainChompReleaseStatus) {
case CHAIN_CHOMP_NOT_RELEASED:
switch (o->oSubAction) {
case CHAIN_CHOMP_SUB_ACT_TURN:
chain_chomp_sub_act_turn();
break;
case CHAIN_CHOMP_SUB_ACT_LUNGE:
chain_chomp_sub_act_lunge();
break;
}
break;
case CHAIN_CHOMP_RELEASED_TRIGGER_CUTSCENE:
chain_chomp_released_trigger_cutscene();
break;
case CHAIN_CHOMP_RELEASED_LUNGE_AROUND:
chain_chomp_released_lunge_around();
break;
case CHAIN_CHOMP_RELEASED_BREAK_GATE:
chain_chomp_released_break_gate();
break;
case CHAIN_CHOMP_RELEASED_JUMP_AWAY:
chain_chomp_released_jump_away();
break;
case CHAIN_CHOMP_RELEASED_END_CUTSCENE:
chain_chomp_released_end_cutscene();
break;
}
cur_obj_move_standard(78);
// Segment 0 connects the pivot to the chain chomp itself
o->oChainChompSegments[0].posX = o->oPosX - o->parentObj->oPosX;
o->oChainChompSegments[0].posY = o->oPosY - o->parentObj->oPosY;
o->oChainChompSegments[0].posZ = o->oPosZ - o->parentObj->oPosZ;
o->oChainChompDistToPivot =
sqrtf(o->oChainChompSegments[0].posX * o->oChainChompSegments[0].posX
+ o->oChainChompSegments[0].posY * o->oChainChompSegments[0].posY
+ o->oChainChompSegments[0].posZ * o->oChainChompSegments[0].posZ);
// If the chain is fully stretched
maxDistToPivot = o->oChainChompMaxDistFromPivotPerChainPart * 5;
if (o->oChainChompDistToPivot > maxDistToPivot) {
f32 ratio = maxDistToPivot / o->oChainChompDistToPivot;
o->oChainChompDistToPivot = maxDistToPivot;
o->oChainChompSegments[0].posX *= ratio;
o->oChainChompSegments[0].posY *= ratio;
o->oChainChompSegments[0].posZ *= ratio;
if (o->oChainChompReleaseStatus == CHAIN_CHOMP_NOT_RELEASED) {
// Restrict chain chomp position
o->oPosX = o->parentObj->oPosX + o->oChainChompSegments[0].posX;
o->oPosY = o->parentObj->oPosY + o->oChainChompSegments[0].posY;
o->oPosZ = o->parentObj->oPosZ + o->oChainChompSegments[0].posZ;
o->oChainChompRestrictedByChain = TRUE;
} else {
// Move pivot like the chain chomp is pulling it along
f32 oldPivotY = o->parentObj->oPosY;
o->parentObj->oPosX = o->oPosX - o->oChainChompSegments[0].posX;
o->parentObj->oPosY = o->oPosY - o->oChainChompSegments[0].posY;
o->parentObj->oVelY = o->parentObj->oPosY - oldPivotY;
o->parentObj->oPosZ = o->oPosZ - o->oChainChompSegments[0].posZ;
}
} else {
o->oChainChompRestrictedByChain = FALSE;
}
chain_chomp_update_chain_segments();
// Begin a lunge if mario tries to attack
if (obj_check_attacks(&sChainChompHitbox, o->oAction)) {
o->oSubAction = CHAIN_CHOMP_SUB_ACT_LUNGE;
o->oChainChompMaxDistFromPivotPerChainPart = 900.0f / 5;
o->oForwardVel = 0.0f;
o->oVelY = 300.0f;
o->oGravity = -4.0f;
o->oChainChompTargetPitch = -0x3000;
}
#ifndef NODRAWINGDISTANCE
}
#endif
}
/**
* Hide and free the chain chomp segments. The chain objects will unload
* themselves when they see that the chain chomp is in this action.
*/
static void chain_chomp_act_unload_chain(void) {
cur_obj_hide();
mem_pool_free(gObjectMemoryPool, o->oChainChompSegments);
o->oAction = CHAIN_CHOMP_ACT_UNINITIALIZED;
if (o->oChainChompReleaseStatus != CHAIN_CHOMP_NOT_RELEASED) {
for (u8 i = 0; i < 5; i++) {
obj_mark_for_deletion(&o->oChainChompSegments[i]);
}
obj_mark_for_deletion(o);
obj_mark_for_deletion(o->parentObj);
}
}
/**
* Update function for chain chomp.
*/
void bhv_chain_chomp_update(void) {
if (!network_sync_object_initialized(o)) {
struct SyncObject* so = network_init_object(o, 1000.0f);
so->syncDeathEvent = FALSE;
network_init_object_field(o, &o->oChainChompUnk104);
network_init_object_field(o, &o->header.gfx.unk38.animFrame);
}
switch (o->oAction) {
case CHAIN_CHOMP_ACT_UNINITIALIZED:
chain_chomp_act_uninitialized();
break;
case CHAIN_CHOMP_ACT_MOVE:
chain_chomp_act_move();
break;
case CHAIN_CHOMP_ACT_UNLOAD_CHAIN:
chain_chomp_act_unload_chain();
break;
}
}
/**
* Update function for wooden post.
*/
void bhv_wooden_post_update(void) {
if (!network_sync_object_initialized(o)) {
network_init_object(o, SYNC_DISTANCE_ONLY_EVENTS);
network_init_object_field(o, &o->oWoodenPostMarioPounding);
network_init_object_field(o, &o->oWoodenPostOffsetY);
network_init_object_field(o, &o->oWoodenPostSpeedY);
network_init_object_field(o, &o->oWoodenPostTotalMarioAngle);
network_init_object_field(o, &o->oTimer);
}
// When ground pounded by mario, drop by -45 + -20
if (!o->oWoodenPostMarioPounding) {
if ((o->oWoodenPostMarioPounding = cur_obj_is_mario_ground_pounding_platform())) {
cur_obj_play_sound_2(SOUND_GENERAL_POUND_WOOD_POST);
o->oWoodenPostSpeedY = -70.0f;
network_send_object(o);
}
} else if (approach_f32_ptr(&o->oWoodenPostSpeedY, 0.0f, 25.0f)) {
// Stay still until mario is done ground pounding
o->oWoodenPostMarioPounding = cur_obj_is_mario_ground_pounding_platform();
} else if ((o->oWoodenPostOffsetY += o->oWoodenPostSpeedY) < -190.0f) {
// Once pounded, if this is the chain chomp's post, release the chain
// chomp
o->oWoodenPostOffsetY = -190.0f;
if (o->parentObj != o) {
play_puzzle_jingle();
o->parentObj->oChainChompReleaseStatus = CHAIN_CHOMP_RELEASED_TRIGGER_CUTSCENE;
o->parentObj = o;
}
}
if (o->oWoodenPostOffsetY != 0.0f) {
o->oPosY = o->oHomeY + o->oWoodenPostOffsetY;
} else if (!(o->oBehParams & WOODEN_POST_BP_NO_COINS_MASK)) {
struct Object* player = nearest_player_to_object(o);
int distanceToPlayer = dist_between_objects(o, player);
int angleToPlayer = obj_angle_to_object(o, player);
// Reset the timer once mario is far enough
if (distanceToPlayer > 400.0f) {
o->oTimer = o->oWoodenPostTotalMarioAngle = 0;
} else {
// When mario runs around the post 3 times within 200 frames, spawn
// coins
o->oWoodenPostTotalMarioAngle += (s16)(angleToPlayer - o->oWoodenPostPrevAngleToMario);
if (absi(o->oWoodenPostTotalMarioAngle) > 0x30000 && o->oTimer < 200) {
network_send_object(o);
obj_spawn_loot_yellow_coins(o, 5, 20.0f);
set_object_respawn_info_bits(o, 1);
}
}
o->oWoodenPostPrevAngleToMario = angleToPlayer;
}
}
/**
* Init function for chain chomp gate.
*/
void bhv_chain_chomp_gate_init(void) {
o->parentObj = cur_obj_nearest_object_with_behavior(bhvChainChomp);
}
/**
* Update function for chain chomp gate
*/
void bhv_chain_chomp_gate_update(void) {
if (o->parentObj->oChainChompHitGate) {
spawn_mist_particles_with_sound(SOUND_GENERAL_WALL_EXPLOSION);
set_camera_shake_from_point(SHAKE_POS_SMALL, o->oPosX, o->oPosY, o->oPosZ);
spawn_mist_particles_variable(0, 0x7F, 200.0f);
spawn_triangle_break_particles(30, 0x8A, 3.0f, 4);
obj_mark_for_deletion(o);
}
}