sm64pc/src/game/obj_behaviors_2.c

1018 lines
31 KiB
C

#include <ultra64.h>
#include "sm64.h"
#include "behavior_actions.h"
#include "engine/behavior_script.h"
#include "camera.h"
#include "game_init.h"
#include "engine/math_util.h"
#include "object_helpers.h"
#include "mario_actions_cutscene.h"
#include "behavior_data.h"
#include "mario.h"
#include "engine/surface_collision.h"
#include "obj_behaviors_2.h"
#include "audio/external.h"
#include "seq_ids.h"
#include "dialog_ids.h"
#include "level_update.h"
#include "memory.h"
#include "platform_displacement.h"
#include "rendering_graph_node.h"
#include "engine/surface_load.h"
#include "obj_behaviors.h"
#include "object_constants.h"
#include "interaction.h"
#include "object_list_processor.h"
#include "spawn_sound.h"
#include "geo_misc.h"
#include "save_file.h"
#include "level_table.h"
extern struct Animation *wiggler_seg5_anims_0500C874[];
extern struct Animation *spiny_egg_seg5_anims_050157E4[];
extern struct ObjectNode *gObjectLists;
extern u8 jrb_seg7_trajectory_unagi_1[];
extern u8 jrb_seg7_trajectory_unagi_2[];
extern u8 dorrie_seg6_collision_0600FBB8[];
extern u8 dorrie_seg6_collision_0600F644[];
extern u8 ssl_seg7_collision_070284B0[];
extern u8 ssl_seg7_collision_07028274[];
extern u8 ssl_seg7_collision_07028370[];
extern u8 ssl_seg7_collision_070282F8[];
extern u8 ccm_seg7_trajectory_penguin_race[];
extern u8 bob_seg7_trajectory_koopa[];
extern u8 thi_seg7_trajectory_koopa[];
extern u8 rr_seg7_collision_07029038[];
extern u8 ccm_seg7_collision_070163F8[];
extern u8 checkerboard_platform_seg8_collision_0800D710[];
extern u8 bitfs_seg7_collision_070157E0[];
extern u8 rr_seg7_trajectory_0702EC3C[];
extern u8 rr_seg7_trajectory_0702ECC0[];
extern u8 ccm_seg7_trajectory_0701669C[];
extern u8 bitfs_seg7_trajectory_070159AC[];
extern u8 hmc_seg7_trajectory_0702B86C[];
extern u8 lll_seg7_trajectory_0702856C[];
extern u8 lll_seg7_trajectory_07028660[];
extern u8 rr_seg7_trajectory_0702ED9C[];
extern u8 rr_seg7_trajectory_0702EEE0[];
extern u8 bitdw_seg7_collision_0700F70C[];
extern u8 bits_seg7_collision_0701ADD8[];
extern u8 bits_seg7_collision_0701AE5C[];
extern u8 bob_seg7_collision_bridge[];
extern u8 bitfs_seg7_collision_07015928[];
extern u8 rr_seg7_collision_07029750[];
extern u8 rr_seg7_collision_07029858[];
extern u8 vcutm_seg7_collision_0700AC44[];
extern u8 bits_seg7_collision_0701ACAC[];
extern u8 bits_seg7_collision_0701AC28[];
extern u8 bitdw_seg7_collision_0700F7F0[];
extern u8 bitdw_seg7_collision_0700F898[];
extern u8 ttc_seg7_collision_07014F70[];
extern u8 ttc_seg7_collision_07015008[];
extern u8 ttc_seg7_collision_070152B4[];
extern u8 ttc_seg7_collision_070153E0[];
extern u8 ttc_seg7_collision_07015584[];
extern u8 ttc_seg7_collision_07015650[];
extern u8 ttc_seg7_collision_07015754[];
extern u8 ttc_seg7_collision_070157D8[];
extern u8 bits_seg7_collision_0701A9A0[];
extern u8 bits_seg7_collision_0701AA0C[];
extern u8 bitfs_seg7_collision_07015714[];
extern u8 bitfs_seg7_collision_07015768[];
extern u8 rr_seg7_collision_070295F8[];
extern u8 rr_seg7_collision_0702967C[];
extern u8 bitdw_seg7_collision_0700F688[];
extern u8 bits_seg7_collision_0701AA84[];
extern u8 rr_seg7_collision_07029508[];
extern u8 bits_seg7_collision_0701B734[];
extern u8 bits_seg7_collision_0701B59C[];
extern u8 bits_seg7_collision_0701B404[];
extern u8 bits_seg7_collision_0701B26C[];
extern u8 bits_seg7_collision_0701B0D4[];
extern u8 bitdw_seg7_collision_0700FD9C[];
extern u8 bitdw_seg7_collision_0700FC7C[];
extern u8 bitdw_seg7_collision_0700FB5C[];
extern u8 bitdw_seg7_collision_0700FA3C[];
extern u8 bitdw_seg7_collision_0700F91C[];
extern u8 rr_seg7_collision_0702A6B4[];
extern u8 rr_seg7_collision_0702A32C[];
extern u8 rr_seg7_collision_07029FA4[];
extern u8 rr_seg7_collision_07029C1C[];
extern u8 rr_seg7_collision_07029924[];
extern u8 bits_seg7_collision_0701AD54[];
extern u8 bitfs_seg7_collision_070157E0[];
extern u8 bitfs_seg7_collision_07015124[];
extern struct Animation *spiny_seg5_anims_05016EAC[];
#define POS_OP_SAVE_POSITION 0
#define POS_OP_COMPUTE_VELOCITY 1
#define POS_OP_RESTORE_POSITION 2
#define o gCurrentObject
/**
* The treadmill that plays sounds and controls the others on random setting.
*/
struct Object *sMasterTreadmill;
f32 sObjSavedPosX;
f32 sObjSavedPosY;
f32 sObjSavedPosZ;
void shelled_koopa_attack_handler(s32 attackType);
void wiggler_jumped_on_attack_handler(void);
void huge_goomba_weakly_attacked(void);
static s32 obj_is_rendering_enabled(void) {
if (o->header.gfx.node.flags & GRAPH_RENDER_ACTIVE) {
return TRUE;
} else {
return FALSE;
}
}
static s16 obj_get_pitch_from_vel(void) {
return -atan2s(o->oForwardVel, o->oVelY);
}
/**
* Show dialog proposing a race.
* If the player accepts the race, then leave time stop enabled and mario in the
* text action so that the racing object can wait before starting the race.
* If the player declines the race, then disable time stop and allow mario to
* move again.
*/
static s32 obj_update_race_proposition_dialog(s16 dialogID) {
s32 dialogResponse =
cur_obj_update_dialog_with_cutscene(2, DIALOG_UNK2_FLAG_0 | DIALOG_UNK2_LEAVE_TIME_STOP_ENABLED, CUTSCENE_RACE_DIALOG, dialogID);
if (dialogResponse == 2) {
set_mario_npc_dialog(0);
disable_time_stop_including_mario();
}
return dialogResponse;
}
static void obj_set_dist_from_home(f32 distFromHome) {
o->oPosX = o->oHomeX + distFromHome * coss(o->oMoveAngleYaw);
o->oPosZ = o->oHomeZ + distFromHome * sins(o->oMoveAngleYaw);
}
static s32 obj_is_near_to_and_facing_mario(f32 maxDist, s16 maxAngleDiff) {
if (o->oDistanceToMario < maxDist
&& abs_angle_diff(o->oMoveAngleYaw, o->oAngleToMario) < maxAngleDiff) {
return TRUE;
}
return FALSE;
}
//! Although having no return value, this function
//! must be u32 to match other functions on -O2.
static BAD_RETURN(u32) obj_perform_position_op(s32 op) {
switch (op) {
case POS_OP_SAVE_POSITION:
sObjSavedPosX = o->oPosX;
sObjSavedPosY = o->oPosY;
sObjSavedPosZ = o->oPosZ;
break;
case POS_OP_COMPUTE_VELOCITY:
o->oVelX = o->oPosX - sObjSavedPosX;
o->oVelY = o->oPosY - sObjSavedPosY;
o->oVelZ = o->oPosZ - sObjSavedPosZ;
break;
case POS_OP_RESTORE_POSITION:
o->oPosX = sObjSavedPosX;
o->oPosY = sObjSavedPosY;
o->oPosZ = sObjSavedPosZ;
break;
}
}
static void platform_on_track_update_pos_or_spawn_ball(s32 ballIndex, f32 x, f32 y, f32 z) {
struct Object *trackBall;
struct Waypoint *initialPrevWaypoint;
struct Waypoint *nextWaypoint;
struct Waypoint *prevWaypoint;
UNUSED s32 unused;
f32 amountToMove;
f32 dx;
f32 dy;
f32 dz;
f32 distToNextWaypoint;
if (ballIndex == 0 || ((u16)(o->oBehParams >> 16) & 0x0080)) {
initialPrevWaypoint = o->oPlatformOnTrackPrevWaypoint;
nextWaypoint = initialPrevWaypoint;
if (ballIndex != 0) {
amountToMove = 300.0f * ballIndex;
} else {
obj_perform_position_op(POS_OP_SAVE_POSITION);
o->oPlatformOnTrackPrevWaypointFlags = 0;
amountToMove = o->oForwardVel;
}
do {
prevWaypoint = nextWaypoint;
nextWaypoint += 1;
if (nextWaypoint->flags == WAYPOINT_FLAGS_END) {
if (ballIndex == 0) {
o->oPlatformOnTrackPrevWaypointFlags = WAYPOINT_FLAGS_END;
}
if (((u16)(o->oBehParams >> 16) & PLATFORM_ON_TRACK_BP_RETURN_TO_START)) {
nextWaypoint = o->oPlatformOnTrackStartWaypoint;
} else {
return;
}
}
dx = nextWaypoint->pos[0] - x;
dy = nextWaypoint->pos[1] - y;
dz = nextWaypoint->pos[2] - z;
distToNextWaypoint = sqrtf(dx * dx + dy * dy + dz * dz);
// Move directly to the next waypoint, even if it's farther away
// than amountToMove
amountToMove -= distToNextWaypoint;
x += dx;
y += dy;
z += dz;
} while (amountToMove > 0.0f);
// If we moved farther than amountToMove, move in the opposite direction
// No risk of near-zero division: If distToNextWaypoint is close to
// zero, then that means we didn't cross a waypoint this frame (since
// otherwise distToNextWaypoint would equal the distance between two
// waypoints, which should never be that small). But this implies that
// amountToMove - distToNextWaypoint <= 0, and amountToMove is at least
// 0.1 (from platform on track behavior).
distToNextWaypoint = amountToMove / distToNextWaypoint;
x += dx * distToNextWaypoint;
y += dy * distToNextWaypoint;
z += dz * distToNextWaypoint;
if (ballIndex != 0) {
trackBall = spawn_object_relative(o->oPlatformOnTrackBaseBallIndex + ballIndex, 0, 0, 0, o,
MODEL_TRAJECTORY_MARKER_BALL, bhvTrackBall);
if (trackBall != NULL) {
trackBall->oPosX = x;
trackBall->oPosY = y;
trackBall->oPosZ = z;
}
} else {
if (prevWaypoint != initialPrevWaypoint) {
if (o->oPlatformOnTrackPrevWaypointFlags == 0) {
o->oPlatformOnTrackPrevWaypointFlags = initialPrevWaypoint->flags;
}
o->oPlatformOnTrackPrevWaypoint = prevWaypoint;
}
o->oPosX = x;
o->oPosY = y;
o->oPosZ = z;
obj_perform_position_op(POS_OP_COMPUTE_VELOCITY);
o->oPlatformOnTrackPitch =
atan2s(sqrtf(o->oVelX * o->oVelX + o->oVelZ * o->oVelZ), -o->oVelY);
o->oPlatformOnTrackYaw = atan2s(o->oVelZ, o->oVelX);
}
}
}
static void cur_obj_spin_all_dimensions(f32 arg0, f32 arg1) {
f32 val24;
f32 val20;
f32 val1C;
f32 c;
f32 s;
f32 val10;
f32 val0C;
f32 val08;
f32 val04;
f32 val00;
if (o->oForwardVel == 0.0f) {
val24 = val20 = val1C = 0.0f;
if (o->oMoveFlags & OBJ_MOVE_IN_AIR) {
val20 = 50.0f;
} else {
if (o->oFaceAnglePitch < 0) {
val1C = -arg0;
} else if (o->oFaceAnglePitch > 0) {
val1C = arg0;
}
if (o->oFaceAngleRoll < 0) {
val24 = -arg1;
} else if (o->oFaceAngleRoll > 0) {
val24 = arg1;
}
}
c = coss(o->oFaceAnglePitch);
s = sins(o->oFaceAnglePitch);
val08 = val1C * c + val20 * s;
val0C = val20 * c - val1C * s;
c = coss(o->oFaceAngleRoll);
s = sins(o->oFaceAngleRoll);
val04 = val24 * c + val0C * s;
val0C = val0C * c - val24 * s;
c = coss(o->oFaceAngleYaw);
s = sins(o->oFaceAngleYaw);
val10 = val04 * c - val08 * s;
val08 = val08 * c + val04 * s;
val04 = val24 * c - val1C * s;
val00 = val1C * c + val24 * s;
o->oPosX = o->oHomeX - val04 + val10;
o->oGraphYOffset = val20 - val0C;
o->oPosZ = o->oHomeZ + val00 - val08;
}
}
static void obj_rotate_yaw_and_bounce_off_walls(s16 targetYaw, s16 turnAmount) {
if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
targetYaw = cur_obj_reflect_move_angle_off_wall();
}
cur_obj_rotate_yaw_toward(targetYaw, turnAmount);
}
static s16 obj_get_pitch_to_home(f32 latDistToHome) {
return atan2s(latDistToHome, o->oPosY - o->oHomeY);
}
static void obj_compute_vel_from_move_pitch(f32 speed) {
o->oForwardVel = speed * coss(o->oMoveAnglePitch);
o->oVelY = speed * -sins(o->oMoveAnglePitch);
}
static s32 clamp_s16(s16 *value, s16 minimum, s16 maximum) {
if (*value <= minimum) {
*value = minimum;
} else if (*value >= maximum) {
*value = maximum;
} else {
return FALSE;
}
return TRUE;
}
static s32 clamp_f32(f32 *value, f32 minimum, f32 maximum) {
if (*value <= minimum) {
*value = minimum;
} else if (*value >= maximum) {
*value = maximum;
} else {
return FALSE;
}
return TRUE;
}
static void cur_obj_init_anim_extend(s32 arg0) {
cur_obj_init_animation_with_sound(arg0);
cur_obj_extend_animation_if_at_end();
}
static s32 cur_obj_init_anim_and_check_if_end(s32 arg0) {
cur_obj_init_animation_with_sound(arg0);
return cur_obj_check_if_near_animation_end();
}
static s32 cur_obj_init_anim_check_frame(s32 arg0, s32 arg1) {
cur_obj_init_animation_with_sound(arg0);
return cur_obj_check_anim_frame(arg1);
}
static s32 cur_obj_set_anim_if_at_end(s32 arg0) {
if (cur_obj_check_if_at_animation_end()) {
cur_obj_init_animation_with_sound(arg0);
return TRUE;
}
return FALSE;
}
static s32 cur_obj_play_sound_at_anim_range(s8 arg0, s8 arg1, u32 sound) {
s32 val04;
if ((val04 = o->header.gfx.unk38.animAccel / 0x10000) <= 0) {
val04 = 1;
}
if (cur_obj_check_anim_frame_in_range(arg0, val04) || cur_obj_check_anim_frame_in_range(arg1, val04)) {
cur_obj_play_sound_2(sound);
return TRUE;
}
return FALSE;
}
static s16 obj_turn_pitch_toward_mario(f32 targetOffsetY, s16 turnAmount) {
s16 targetPitch;
o->oPosY -= targetOffsetY;
targetPitch = obj_turn_toward_object(o, gMarioObject, O_MOVE_ANGLE_PITCH_INDEX, turnAmount);
o->oPosY += targetOffsetY;
return targetPitch;
}
static s32 approach_f32_ptr(f32 *px, f32 target, f32 delta) {
if (*px > target) {
delta = -delta;
}
*px += delta;
if ((*px - target) * delta >= 0) {
*px = target;
return TRUE;
}
return FALSE;
}
static s32 obj_forward_vel_approach(f32 target, f32 delta) {
return approach_f32_ptr(&o->oForwardVel, target, delta);
}
static s32 obj_y_vel_approach(f32 target, f32 delta) {
return approach_f32_ptr(&o->oVelY, target, delta);
}
static s32 obj_move_pitch_approach(s16 target, s16 delta) {
o->oMoveAnglePitch = approach_s16_symmetric(o->oMoveAnglePitch, target, delta);
if ((s16) o->oMoveAnglePitch == target) {
return TRUE;
}
return FALSE;
}
static s32 obj_face_pitch_approach(s16 targetPitch, s16 deltaPitch) {
o->oFaceAnglePitch = approach_s16_symmetric(o->oFaceAnglePitch, targetPitch, deltaPitch);
if ((s16) o->oFaceAnglePitch == targetPitch) {
return TRUE;
}
return FALSE;
}
static s32 obj_face_yaw_approach(s16 targetYaw, s16 deltaYaw) {
o->oFaceAngleYaw = approach_s16_symmetric(o->oFaceAngleYaw, targetYaw, deltaYaw);
if ((s16) o->oFaceAngleYaw == targetYaw) {
return TRUE;
}
return FALSE;
}
static s32 obj_face_roll_approach(s16 targetRoll, s16 deltaRoll) {
o->oFaceAngleRoll = approach_s16_symmetric(o->oFaceAngleRoll, targetRoll, deltaRoll);
if ((s16) o->oFaceAngleRoll == targetRoll) {
return TRUE;
}
return FALSE;
}
static s32 obj_smooth_turn(s16 *angleVel, s32 *angle, s16 targetAngle, f32 targetSpeedProportion,
s16 accel, s16 minSpeed, s16 maxSpeed) {
s16 currentSpeed;
s16 currentAngle = (s16)(*angle);
*angleVel =
approach_s16_symmetric(*angleVel, (targetAngle - currentAngle) * targetSpeedProportion, accel);
currentSpeed = absi(*angleVel);
clamp_s16(&currentSpeed, minSpeed, maxSpeed);
*angle = approach_s16_symmetric(*angle, targetAngle, currentSpeed);
return (s16)(*angle) == targetAngle;
}
static void obj_roll_to_match_yaw_turn(s16 targetYaw, s16 maxRoll, s16 rollSpeed) {
s16 targetRoll = o->oMoveAngleYaw - targetYaw;
clamp_s16(&targetRoll, -maxRoll, maxRoll);
obj_face_roll_approach(targetRoll, rollSpeed);
}
static s16 random_linear_offset(s16 base, s16 range) {
return base + (s16)(range * random_float());
}
static s16 random_mod_offset(s16 base, s16 step, s16 mod) {
return base + step * (random_u16() % mod);
}
static s16 obj_random_fixed_turn(s16 delta) {
return o->oMoveAngleYaw + (s16) random_sign() * delta;
}
/**
* Begin by increasing the object's scale by *scaleVel, and slowly decreasing
* scaleVel. Once the object starts to shrink, wait a bit, and then begin to
* scale the object toward endScale. The first time it reaches below
* shootFireScale during this time, return 1.
* Return -1 once it's reached endScale.
*/
static s32 obj_grow_then_shrink(f32 *scaleVel, f32 shootFireScale, f32 endScale) {
if (o->oTimer < 2) {
o->header.gfx.scale[0] += *scaleVel;
if ((*scaleVel -= 0.01f) > -0.03f) {
o->oTimer = 0;
}
} else if (o->oTimer > 10) {
if (approach_f32_ptr(&o->header.gfx.scale[0], endScale, 0.05f)) {
return -1;
} else if (*scaleVel != 0.0f && o->header.gfx.scale[0] < shootFireScale) {
*scaleVel = 0.0f;
return 1;
}
}
return 0;
}
static s32 oscillate_toward(s32 *value, f32 *vel, s32 target, f32 velCloseToZero, f32 accel,
f32 slowdown) {
s32 startValue = *value;
*value += (s32) *vel;
if (*value == target
|| ((*value - target) * (startValue - target) < 0 && *vel > -velCloseToZero
&& *vel < velCloseToZero)) {
*value = target;
*vel = 0.0f;
return TRUE;
} else {
if (*value >= target) {
accel = -accel;
}
if (*vel * accel < 0.0f) {
accel *= slowdown;
}
*vel += accel;
}
return FALSE;
}
static void obj_update_blinking(s32 *blinkTimer, s16 baseCycleLength, s16 cycleLengthRange,
s16 blinkLength) {
if (*blinkTimer != 0) {
*blinkTimer -= 1;
} else {
*blinkTimer = random_linear_offset(baseCycleLength, cycleLengthRange);
}
if (*blinkTimer > blinkLength) {
o->oAnimState = 0;
} else {
o->oAnimState = 1;
}
}
static s32 obj_resolve_object_collisions(s32 *targetYaw) {
struct Object *otherObject;
f32 dx;
f32 dz;
s16 angle;
f32 radius;
f32 otherRadius;
f32 relativeRadius;
f32 newCenterX;
f32 newCenterZ;
if (o->numCollidedObjs != 0) {
otherObject = o->collidedObjs[0];
if (otherObject != gMarioObject) {
//! If one object moves after collisions are detected and this code
// runs, the objects can move toward each other (transport cloning)
dx = otherObject->oPosX - o->oPosX;
dz = otherObject->oPosZ - o->oPosZ;
angle = atan2s(dx, dz); //! This should be atan2s(dz, dx)
radius = o->hitboxRadius;
otherRadius = otherObject->hitboxRadius;
relativeRadius = radius / (radius + otherRadius);
newCenterX = o->oPosX + dx * relativeRadius;
newCenterZ = o->oPosZ + dz * relativeRadius;
o->oPosX = newCenterX - radius * coss(angle);
o->oPosZ = newCenterZ - radius * sins(angle);
otherObject->oPosX = newCenterX + otherRadius * coss(angle);
otherObject->oPosZ = newCenterZ + otherRadius * sins(angle);
if (targetYaw != NULL && abs_angle_diff(o->oMoveAngleYaw, angle) < 0x4000) {
// Bounce off object (or it would, if the above atan2s bug
// were fixed)
*targetYaw = (s16)(angle - o->oMoveAngleYaw + angle + 0x8000);
return TRUE;
}
}
}
return FALSE;
}
static s32 obj_bounce_off_walls_edges_objects(s32 *targetYaw) {
if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
*targetYaw = cur_obj_reflect_move_angle_off_wall();
} else if (o->oMoveFlags & OBJ_MOVE_HIT_EDGE) {
*targetYaw = (s16)(o->oMoveAngleYaw + 0x8000);
} else if (!obj_resolve_object_collisions(targetYaw)) {
return FALSE;
}
return TRUE;
}
static s32 obj_resolve_collisions_and_turn(s16 targetYaw, s16 turnSpeed) {
obj_resolve_object_collisions(NULL);
if (cur_obj_rotate_yaw_toward(targetYaw, turnSpeed)) {
return FALSE;
} else {
return TRUE;
}
}
static void obj_die_if_health_non_positive(void) {
if (o->oHealth <= 0) {
if (o->oDeathSound == 0) {
spawn_mist_particles_with_sound(SOUND_OBJ_DEFAULT_DEATH);
} else if (o->oDeathSound > 0) {
spawn_mist_particles_with_sound(o->oDeathSound);
} else {
spawn_mist_particles();
}
if ((s32)o->oNumLootCoins < 0) {
spawn_object(o, MODEL_BLUE_COIN, bhvMrIBlueCoin);
} else {
obj_spawn_loot_yellow_coins(o, o->oNumLootCoins, 20.0f);
}
// This doesn't do anything
obj_spawn_loot_yellow_coins(o, o->oNumLootCoins, 20.0f);
if (o->oHealth < 0) {
cur_obj_hide();
cur_obj_become_intangible();
} else {
obj_mark_for_deletion(o);
}
}
}
static void obj_unused_die(void) {
o->oHealth = 0;
obj_die_if_health_non_positive();
}
static void obj_set_knockback_action(s32 attackType) {
switch (attackType) {
case ATTACK_KICK_OR_TRIP:
case ATTACK_FAST_ATTACK:
o->oAction = OBJ_ACT_VERTICAL_KNOCKBACK;
o->oForwardVel = 20.0f;
o->oVelY = 50.0f;
break;
default:
o->oAction = OBJ_ACT_HORIZONTAL_KNOCKBACK;
o->oForwardVel = 50.0f;
o->oVelY = 30.0f;
break;
}
o->oFlags &= ~OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW;
o->oMoveAngleYaw = obj_angle_to_object(gMarioObject, o);
}
static void obj_set_squished_action(void) {
cur_obj_play_sound_2(SOUND_OBJ_STOMPED);
o->oAction = OBJ_ACT_SQUISHED;
}
static s32 obj_die_if_above_lava_and_health_non_positive(void) {
if (o->oMoveFlags & OBJ_MOVE_UNDERWATER_ON_GROUND) {
if (o->oGravity + o->oBuoyancy > 0.0f
|| find_water_level(o->oPosX, o->oPosZ) - o->oPosY < 150.0f) {
return FALSE;
}
} else if (!(o->oMoveFlags & OBJ_MOVE_ABOVE_LAVA)) {
if (o->oMoveFlags & OBJ_MOVE_ENTERED_WATER) {
if (o->oWallHitboxRadius < 200.0f) {
cur_obj_play_sound_2(SOUND_OBJ_DIVING_INTO_WATER);
} else {
cur_obj_play_sound_2(SOUND_OBJ_DIVING_IN_WATER);
}
}
return FALSE;
}
obj_die_if_health_non_positive();
return TRUE;
}
static s32 obj_handle_attacks(struct ObjectHitbox *hitbox, s32 attackedMarioAction,
u8 *attackHandlers) {
s32 attackType;
obj_set_hitbox(o, hitbox);
//! Die immediately if above lava
if (obj_die_if_above_lava_and_health_non_positive()) {
return 1;
} else if (o->oInteractStatus & INT_STATUS_INTERACTED) {
if (o->oInteractStatus & INT_STATUS_ATTACKED_MARIO) {
if (o->oAction != attackedMarioAction) {
o->oAction = attackedMarioAction;
o->oTimer = 0;
}
} else {
attackType = o->oInteractStatus & INT_STATUS_ATTACK_MASK;
switch (attackHandlers[attackType - 1]) {
case ATTACK_HANDLER_NOP:
break;
case ATTACK_HANDLER_DIE_IF_HEALTH_NON_POSITIVE:
obj_die_if_health_non_positive();
break;
case ATTACK_HANDLER_KNOCKBACK:
obj_set_knockback_action(attackType);
break;
case ATTACK_HANDLER_SQUISHED:
obj_set_squished_action();
break;
case ATTACK_HANDLER_SPECIAL_KOOPA_LOSE_SHELL:
shelled_koopa_attack_handler(attackType);
break;
case ATTACK_HANDLER_SET_SPEED_TO_ZERO:
obj_set_speed_to_zero();
break;
case ATTACK_HANDLER_SPECIAL_WIGGLER_JUMPED_ON:
wiggler_jumped_on_attack_handler();
break;
case ATTACK_HANDLER_SPECIAL_HUGE_GOOMBA_WEAKLY_ATTACKED:
huge_goomba_weakly_attacked();
break;
case ATTACK_HANDLER_SQUISHED_WITH_BLUE_COIN:
o->oNumLootCoins = -1;
obj_set_squished_action();
break;
}
o->oInteractStatus = 0;
return attackType;
}
}
o->oInteractStatus = 0;
return 0;
}
static void obj_act_knockback(UNUSED f32 baseScale) {
cur_obj_update_floor_and_walls();
if (o->header.gfx.unk38.curAnim != NULL) {
cur_obj_extend_animation_if_at_end();
}
//! Dies immediately if above lava
if ((o->oMoveFlags
& (OBJ_MOVE_MASK_ON_GROUND | OBJ_MOVE_MASK_IN_WATER | OBJ_MOVE_HIT_WALL | OBJ_MOVE_ABOVE_LAVA))
|| (o->oAction == OBJ_ACT_VERTICAL_KNOCKBACK && o->oTimer >= 9)) {
obj_die_if_health_non_positive();
}
cur_obj_move_standard(-78);
}
static void obj_act_squished(f32 baseScale) {
f32 targetScaleY = baseScale * 0.3f;
cur_obj_update_floor_and_walls();
if (o->header.gfx.unk38.curAnim != NULL) {
cur_obj_extend_animation_if_at_end();
}
if (approach_f32_ptr(&o->header.gfx.scale[1], targetScaleY, baseScale * 0.14f)) {
o->header.gfx.scale[0] = o->header.gfx.scale[2] = baseScale * 2.0f - o->header.gfx.scale[1];
if (o->oTimer >= 16) {
obj_die_if_health_non_positive();
}
}
o->oForwardVel = 0.0f;
cur_obj_move_standard(-78);
}
static s32 obj_update_standard_actions(f32 scale) {
if (o->oAction < 100) {
return TRUE;
} else {
cur_obj_become_intangible();
switch (o->oAction) {
case OBJ_ACT_HORIZONTAL_KNOCKBACK:
case OBJ_ACT_VERTICAL_KNOCKBACK:
obj_act_knockback(scale);
break;
case OBJ_ACT_SQUISHED:
obj_act_squished(scale);
break;
}
return FALSE;
}
}
static s32 obj_check_attacks(struct ObjectHitbox *hitbox, s32 attackedMarioAction) {
s32 attackType;
obj_set_hitbox(o, hitbox);
//! Dies immediately if above lava
if (obj_die_if_above_lava_and_health_non_positive()) {
return 1;
} else if (o->oInteractStatus & INT_STATUS_INTERACTED) {
if (o->oInteractStatus & INT_STATUS_ATTACKED_MARIO) {
if (o->oAction != attackedMarioAction) {
o->oAction = attackedMarioAction;
o->oTimer = 0;
}
} else {
attackType = o->oInteractStatus & INT_STATUS_ATTACK_MASK;
obj_die_if_health_non_positive();
o->oInteractStatus = 0;
return attackType;
}
}
o->oInteractStatus = 0;
return 0;
}
static s32 obj_move_for_one_second(s32 endAction) {
cur_obj_update_floor_and_walls();
cur_obj_extend_animation_if_at_end();
if (o->oTimer > 30) {
o->oAction = endAction;
return TRUE;
}
cur_obj_move_standard(-78);
return FALSE;
}
/**
* If we are far from home (> threshold away), then set oAngleToMario to the
* angle to home and oDistanceToMario to 25000.
* If we are close to home, but Mario is far from us (> threshold away), then
* keep oAngleToMario the same and set oDistanceToMario to 20000.
* If we are close to both home and Mario, then keep both oAngleToMario and
* oDistanceToMario the same.
*
* The point of this function is to avoid having to write extra code to get
* the object to return to home. When mario is far away and the object is far
* from home, it could theoretically re-use the "approach mario" logic to approach
* its home instead.
* However, most objects that use this function handle the far-from-home case
* separately anyway.
* This function causes seemingly erroneous behavior in some objects that try to
* attack mario (e.g. fly guy shooting fire or lunging), especially when combined
* with partial updates.
*/
static void treat_far_home_as_mario(f32 threshold) {
f32 dx = o->oHomeX - o->oPosX;
f32 dy = o->oHomeY - o->oPosY;
f32 dz = o->oHomeZ - o->oPosZ;
f32 distance = sqrtf(dx * dx + dy * dy + dz * dz);
if (distance > threshold) {
o->oAngleToMario = atan2s(dz, dx);
o->oDistanceToMario = 25000.0f;
} else {
dx = o->oHomeX - gMarioObject->oPosX;
dy = o->oHomeY - gMarioObject->oPosY;
dz = o->oHomeZ - gMarioObject->oPosZ;
distance = sqrtf(dx * dx + dy * dy + dz * dz);
if (distance > threshold) {
o->oDistanceToMario = 20000.0f;
}
}
}
#include "behaviors/koopa.inc.c" // TODO: Text arg field name
#include "behaviors/pokey.inc.c"
#include "behaviors/swoop.inc.c"
#include "behaviors/fly_guy.inc.c"
#include "behaviors/goomba.inc.c"
#include "behaviors/chain_chomp.inc.c" // TODO: chain_chomp_sub_act_lunge documentation
#include "behaviors/wiggler.inc.c" // TODO
#include "behaviors/spiny.inc.c"
#include "behaviors/enemy_lakitu.inc.c" // TODO
#include "behaviors/cloud.inc.c"
#include "behaviors/camera_lakitu.inc.c" // TODO: 104 label, follow cam documentation
#include "behaviors/monty_mole.inc.c" // TODO
#include "behaviors/platform_on_track.inc.c"
#include "behaviors/seesaw_platform.inc.c"
#include "behaviors/ferris_wheel.inc.c"
#include "behaviors/water_bomb.inc.c" // TODO: Shadow position
#include "behaviors/ttc_rotating_solid.inc.c"
#include "behaviors/ttc_pendulum.inc.c"
#include "behaviors/ttc_treadmill.inc.c" // TODO
#include "behaviors/ttc_moving_bar.inc.c"
#include "behaviors/ttc_cog.inc.c"
#include "behaviors/ttc_pit_block.inc.c"
#include "behaviors/ttc_elevator.inc.c"
#include "behaviors/ttc_2d_rotator.inc.c"
#include "behaviors/ttc_spinner.inc.c"
#include "behaviors/mr_blizzard.inc.c"
#include "behaviors/sliding_platform_2.inc.c"
#include "behaviors/rotating_octagonal_plat.inc.c"
#include "behaviors/animated_floor_switch.inc.c"
#include "behaviors/activated_bf_plat.inc.c"
#include "behaviors/recovery_heart.inc.c"
#include "behaviors/water_bomb_cannon.inc.c"
#include "behaviors/unagi.inc.c"
#include "behaviors/dorrie.inc.c"
#include "behaviors/haunted_chair.inc.c"
#include "behaviors/mad_piano.inc.c"
#include "behaviors/flying_bookend_switch.inc.c"
/**
* Used by fly guy, piranha plant, and fire spitters.
*/
void obj_spit_fire(s16 relativePosX, s16 relativePosY, s16 relativePosZ, f32 scale, s32 model,
f32 startSpeed, f32 endSpeed, s16 movePitch) {
struct Object *sp2C;
sp2C = spawn_object_relative_with_scale(1, relativePosX, relativePosY, relativePosZ, scale, o,
model, bhvSmallPiranhaFlame);
if (sp2C != NULL) {
sp2C->oSmallPiranhaFlameUnkF4 = startSpeed;
sp2C->oSmallPiranhaFlameUnkF8 = endSpeed;
sp2C->oSmallPiranhaFlameUnkFC = model;
sp2C->oMoveAnglePitch = movePitch;
}
}
#include "behaviors/fire_piranha_plant.inc.c"
#include "behaviors/fire_spitter.inc.c"
#include "behaviors/flame.inc.c"
#include "behaviors/snufit.inc.c"
#include "behaviors/horizontal_grindel.inc.c"
#include "behaviors/eyerok.inc.c"
#include "behaviors/klepto.inc.c"
#include "behaviors/bird.inc.c"
#include "behaviors/racing_penguin.inc.c"
#include "behaviors/coffin.inc.c"
#include "behaviors/clam.inc.c"
#include "behaviors/skeeter.inc.c"
#include "behaviors/swing_platform.inc.c"
#include "behaviors/donut_platform.inc.c"
#include "behaviors/ddd_pole.inc.c"
#include "behaviors/reds_star_marker.inc.c"
#include "behaviors/triplet_butterfly.inc.c"
#include "behaviors/bubba.inc.c"