sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

obj_behaviors_2.c (29805B)


      1 #include <PR/ultratypes.h>
      2 
      3 #include "sm64.h"
      4 #include "actors/common0.h"
      5 #include "actors/group11.h"
      6 #include "actors/group17.h"
      7 #include "audio/external.h"
      8 #include "behavior_actions.h"
      9 #include "behavior_data.h"
     10 #include "camera.h"
     11 #include "dialog_ids.h"
     12 #include "engine/behavior_script.h"
     13 #include "engine/math_util.h"
     14 #include "engine/surface_collision.h"
     15 #include "engine/surface_load.h"
     16 #include "game_init.h"
     17 #include "geo_misc.h"
     18 #include "ingame_menu.h"
     19 #include "interaction.h"
     20 #include "level_table.h"
     21 #include "level_update.h"
     22 #include "levels/bitdw/header.h"
     23 #include "levels/bitfs/header.h"
     24 #include "levels/bits/header.h"
     25 #include "levels/bob/header.h"
     26 #include "levels/ccm/header.h"
     27 #include "levels/hmc/header.h"
     28 #include "levels/jrb/header.h"
     29 #include "levels/lll/header.h"
     30 #include "levels/rr/header.h"
     31 #include "levels/ssl/header.h"
     32 #include "levels/thi/header.h"
     33 #include "levels/ttc/header.h"
     34 #include "levels/vcutm/header.h"
     35 #include "mario.h"
     36 #include "mario_actions_cutscene.h"
     37 #include "memory.h"
     38 #include "obj_behaviors.h"
     39 #include "obj_behaviors_2.h"
     40 #include "object_constants.h"
     41 #include "object_helpers.h"
     42 #include "object_list_processor.h"
     43 #include "platform_displacement.h"
     44 #include "rendering_graph_node.h"
     45 #include "save_file.h"
     46 #include "seq_ids.h"
     47 #include "spawn_sound.h"
     48 
     49 #define POS_OP_SAVE_POSITION 0
     50 #define POS_OP_COMPUTE_VELOCITY 1
     51 #define POS_OP_RESTORE_POSITION 2
     52 
     53 #define o gCurrentObject
     54 
     55 /* BSS (declared to force order) */
     56 extern s32 sNumActiveFirePiranhaPlants;
     57 extern s32 sNumKilledFirePiranhaPlants;
     58 extern f32 sObjSavedPosX;
     59 extern f32 sObjSavedPosY;
     60 extern f32 sObjSavedPosZ;
     61 extern struct Object *sMontyMoleHoleList;
     62 extern s32 sMontyMoleKillStreak;
     63 extern f32 sMontyMoleLastKilledPosX;
     64 extern f32 sMontyMoleLastKilledPosY;
     65 extern f32 sMontyMoleLastKilledPosZ;
     66 extern struct Object *sMasterTreadmill;
     67 
     68 /**
     69  * The treadmill that plays sounds and controls the others on random setting.
     70  */
     71 struct Object *sMasterTreadmill;
     72 
     73 f32 sObjSavedPosX;
     74 f32 sObjSavedPosY;
     75 f32 sObjSavedPosZ;
     76 
     77 void wiggler_jumped_on_attack_handler(void);
     78 void huge_goomba_weakly_attacked(void);
     79 
     80 static s32 obj_is_rendering_enabled(void) {
     81     if (o->header.gfx.node.flags & GRAPH_RENDER_ACTIVE) {
     82         return TRUE;
     83     } else {
     84         return FALSE;
     85     }
     86 }
     87 
     88 static s16 obj_get_pitch_from_vel(void) {
     89     return -atan2s(o->oForwardVel, o->oVelY);
     90 }
     91 
     92 /**
     93  * Show dialog proposing a race.
     94  * If the player accepts the race, then leave time stop enabled and Mario in the
     95  * text action so that the racing object can wait before starting the race.
     96  * If the player declines the race, then disable time stop and allow Mario to
     97  * move again.
     98  */
     99 static s32 obj_update_race_proposition_dialog(s16 dialogID) {
    100     s32 dialogResponse =
    101         cur_obj_update_dialog_with_cutscene(MARIO_DIALOG_LOOK_UP,
    102         (DIALOG_FLAG_TURN_TO_MARIO | DIALOG_FLAG_TIME_STOP_ENABLED), CUTSCENE_RACE_DIALOG, dialogID);
    103 
    104     if (dialogResponse == DIALOG_RESPONSE_NO) {
    105         set_mario_npc_dialog(MARIO_DIALOG_STOP);
    106         disable_time_stop_including_mario();
    107     }
    108 
    109     return dialogResponse;
    110 }
    111 
    112 static void obj_set_dist_from_home(f32 distFromHome) {
    113     o->oPosX = o->oHomeX + distFromHome * coss(o->oMoveAngleYaw);
    114     o->oPosZ = o->oHomeZ + distFromHome * sins(o->oMoveAngleYaw);
    115 }
    116 
    117 static s32 obj_is_near_to_and_facing_mario(f32 maxDist, s16 maxAngleDiff) {
    118     if (o->oDistanceToMario < maxDist
    119         && abs_angle_diff(o->oMoveAngleYaw, o->oAngleToMario) < maxAngleDiff) {
    120         return TRUE;
    121     }
    122     return FALSE;
    123 }
    124 
    125 //! Although having no return value, this function
    126 //! must be u32 to match other functions on -O2.
    127 static BAD_RETURN(u32) obj_perform_position_op(s32 op) {
    128     switch (op) {
    129         case POS_OP_SAVE_POSITION:
    130             sObjSavedPosX = o->oPosX;
    131             sObjSavedPosY = o->oPosY;
    132             sObjSavedPosZ = o->oPosZ;
    133             break;
    134 
    135         case POS_OP_COMPUTE_VELOCITY:
    136             o->oVelX = o->oPosX - sObjSavedPosX;
    137             o->oVelY = o->oPosY - sObjSavedPosY;
    138             o->oVelZ = o->oPosZ - sObjSavedPosZ;
    139             break;
    140 
    141         case POS_OP_RESTORE_POSITION:
    142             o->oPosX = sObjSavedPosX;
    143             o->oPosY = sObjSavedPosY;
    144             o->oPosZ = sObjSavedPosZ;
    145             break;
    146     }
    147 }
    148 
    149 static void platform_on_track_update_pos_or_spawn_ball(s32 ballIndex, f32 x, f32 y, f32 z) {
    150     struct Object *trackBall;
    151     struct Waypoint *initialPrevWaypoint;
    152     struct Waypoint *nextWaypoint;
    153     struct Waypoint *prevWaypoint;
    154     UNUSED u8 filler[4];
    155     f32 amountToMove;
    156     f32 dx;
    157     f32 dy;
    158     f32 dz;
    159     f32 distToNextWaypoint;
    160 
    161     if (ballIndex == 0 || ((u16)(o->oBhvParams >> 16) & 0x0080)) {
    162         initialPrevWaypoint = o->oPlatformOnTrackPrevWaypoint;
    163         nextWaypoint = initialPrevWaypoint;
    164 
    165         if (ballIndex != 0) {
    166             amountToMove = 300.0f * ballIndex;
    167         } else {
    168             obj_perform_position_op(POS_OP_SAVE_POSITION);
    169             o->oPlatformOnTrackPrevWaypointFlags = 0;
    170             amountToMove = o->oForwardVel;
    171         }
    172 
    173         do {
    174             prevWaypoint = nextWaypoint;
    175 
    176             nextWaypoint++;
    177             if (nextWaypoint->flags == WAYPOINT_FLAGS_END) {
    178                 if (ballIndex == 0) {
    179                     o->oPlatformOnTrackPrevWaypointFlags = WAYPOINT_FLAGS_END;
    180                 }
    181 
    182                 if (((u16)(o->oBhvParams >> 16) & PLATFORM_ON_TRACK_BP_RETURN_TO_START)) {
    183                     nextWaypoint = o->oPlatformOnTrackStartWaypoint;
    184                 } else {
    185                     return;
    186                 }
    187             }
    188 
    189             dx = nextWaypoint->pos[0] - x;
    190             dy = nextWaypoint->pos[1] - y;
    191             dz = nextWaypoint->pos[2] - z;
    192 
    193             distToNextWaypoint = sqrtf(dx * dx + dy * dy + dz * dz);
    194 
    195             // Move directly to the next waypoint, even if it's farther away
    196             // than amountToMove
    197             amountToMove -= distToNextWaypoint;
    198             x += dx;
    199             y += dy;
    200             z += dz;
    201         } while (amountToMove > 0.0f);
    202 
    203         // If we moved farther than amountToMove, move in the opposite direction
    204         // No risk of near-zero division: If distToNextWaypoint is close to
    205         // zero, then that means we didn't cross a waypoint this frame (since
    206         // otherwise distToNextWaypoint would equal the distance between two
    207         // waypoints, which should never be that small). But this implies that
    208         // amountToMove - distToNextWaypoint <= 0, and amountToMove is at least
    209         // 0.1 (from platform on track behavior).
    210         distToNextWaypoint = amountToMove / distToNextWaypoint;
    211         x += dx * distToNextWaypoint;
    212         y += dy * distToNextWaypoint;
    213         z += dz * distToNextWaypoint;
    214 
    215         if (ballIndex != 0) {
    216             trackBall = spawn_object_relative(o->oPlatformOnTrackBaseBallIndex + ballIndex, 0, 0, 0, o,
    217                                               MODEL_TRAJECTORY_MARKER_BALL, bhvTrackBall);
    218 
    219             if (trackBall != NULL) {
    220                 trackBall->oPosX = x;
    221                 trackBall->oPosY = y;
    222                 trackBall->oPosZ = z;
    223             }
    224         } else {
    225             if (prevWaypoint != initialPrevWaypoint) {
    226                 if (o->oPlatformOnTrackPrevWaypointFlags == 0) {
    227                     o->oPlatformOnTrackPrevWaypointFlags = initialPrevWaypoint->flags;
    228                 }
    229                 o->oPlatformOnTrackPrevWaypoint = prevWaypoint;
    230             }
    231 
    232             o->oPosX = x;
    233             o->oPosY = y;
    234             o->oPosZ = z;
    235 
    236             obj_perform_position_op(POS_OP_COMPUTE_VELOCITY);
    237 
    238             o->oPlatformOnTrackPitch =
    239                 atan2s(sqrtf(o->oVelX * o->oVelX + o->oVelZ * o->oVelZ), -o->oVelY);
    240             o->oPlatformOnTrackYaw = atan2s(o->oVelZ, o->oVelX);
    241         }
    242     }
    243 }
    244 
    245 static void cur_obj_spin_all_dimensions(f32 arg0, f32 arg1) {
    246     f32 val24;
    247     f32 val20;
    248     f32 val1C;
    249     f32 c;
    250     f32 s;
    251     f32 val10;
    252     f32 val0C;
    253     f32 val08;
    254     f32 val04;
    255     f32 val00;
    256 
    257     if (o->oForwardVel == 0.0f) {
    258         val24 = val20 = val1C = 0.0f;
    259 
    260         if (o->oMoveFlags & OBJ_MOVE_IN_AIR) {
    261             val20 = 50.0f;
    262         } else {
    263             if (o->oFaceAnglePitch < 0) {
    264                 val1C = -arg0;
    265             } else if (o->oFaceAnglePitch > 0) {
    266                 val1C = arg0;
    267             }
    268 
    269             if (o->oFaceAngleRoll < 0) {
    270                 val24 = -arg1;
    271             } else if (o->oFaceAngleRoll > 0) {
    272                 val24 = arg1;
    273             }
    274         }
    275 
    276         c = coss(o->oFaceAnglePitch);
    277         s = sins(o->oFaceAnglePitch);
    278         val08 = val1C * c + val20 * s;
    279         val0C = val20 * c - val1C * s;
    280 
    281         c = coss(o->oFaceAngleRoll);
    282         s = sins(o->oFaceAngleRoll);
    283         val04 = val24 * c + val0C * s;
    284         val0C = val0C * c - val24 * s;
    285 
    286         c = coss(o->oFaceAngleYaw);
    287         s = sins(o->oFaceAngleYaw);
    288         val10 = val04 * c - val08 * s;
    289         val08 = val08 * c + val04 * s;
    290 
    291         val04 = val24 * c - val1C * s;
    292         val00 = val1C * c + val24 * s;
    293 
    294         o->oPosX = o->oHomeX - val04 + val10;
    295         o->oGraphYOffset = val20 - val0C;
    296         o->oPosZ = o->oHomeZ + val00 - val08;
    297     }
    298 }
    299 
    300 static void obj_rotate_yaw_and_bounce_off_walls(s16 targetYaw, s16 turnAmount) {
    301     if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
    302         targetYaw = cur_obj_reflect_move_angle_off_wall();
    303     }
    304     cur_obj_rotate_yaw_toward(targetYaw, turnAmount);
    305 }
    306 
    307 static s16 obj_get_pitch_to_home(f32 latDistToHome) {
    308     return atan2s(latDistToHome, o->oPosY - o->oHomeY);
    309 }
    310 
    311 static void obj_compute_vel_from_move_pitch(f32 speed) {
    312     o->oForwardVel = speed * coss(o->oMoveAnglePitch);
    313     o->oVelY = speed * -sins(o->oMoveAnglePitch);
    314 }
    315 
    316 static s32 clamp_s16(s16 *value, s16 minimum, s16 maximum) {
    317     if (*value <= minimum) {
    318         *value = minimum;
    319     } else if (*value >= maximum) {
    320         *value = maximum;
    321     } else {
    322         return FALSE;
    323     }
    324 
    325     return TRUE;
    326 }
    327 
    328 static s32 clamp_f32(f32 *value, f32 minimum, f32 maximum) {
    329     if (*value <= minimum) {
    330         *value = minimum;
    331     } else if (*value >= maximum) {
    332         *value = maximum;
    333     } else {
    334         return FALSE;
    335     }
    336 
    337     return TRUE;
    338 }
    339 
    340 static void cur_obj_init_anim_extend(s32 arg0) {
    341     cur_obj_init_animation_with_sound(arg0);
    342     cur_obj_extend_animation_if_at_end();
    343 }
    344 
    345 static s32 cur_obj_init_anim_and_check_if_end(s32 arg0) {
    346     cur_obj_init_animation_with_sound(arg0);
    347     return cur_obj_check_if_near_animation_end();
    348 }
    349 
    350 static s32 cur_obj_init_anim_check_frame(s32 arg0, s32 arg1) {
    351     cur_obj_init_animation_with_sound(arg0);
    352     return cur_obj_check_anim_frame(arg1);
    353 }
    354 
    355 static s32 cur_obj_set_anim_if_at_end(s32 arg0) {
    356     if (cur_obj_check_if_at_animation_end()) {
    357         cur_obj_init_animation_with_sound(arg0);
    358         return TRUE;
    359     }
    360     return FALSE;
    361 }
    362 
    363 static s32 cur_obj_play_sound_at_anim_range(s8 arg0, s8 arg1, u32 sound) {
    364     s32 val04;
    365 
    366     if ((val04 = o->header.gfx.animInfo.animAccel / 0x10000) <= 0) {
    367         val04 = 1;
    368     }
    369 
    370     if (cur_obj_check_anim_frame_in_range(arg0, val04) || cur_obj_check_anim_frame_in_range(arg1, val04)) {
    371         cur_obj_play_sound_2(sound);
    372         return TRUE;
    373     }
    374 
    375     return FALSE;
    376 }
    377 
    378 static s16 obj_turn_pitch_toward_mario(f32 targetOffsetY, s16 turnAmount) {
    379     s16 targetPitch;
    380 
    381     o->oPosY -= targetOffsetY;
    382     targetPitch = obj_turn_toward_object(o, gMarioObject, O_MOVE_ANGLE_PITCH_INDEX, turnAmount);
    383     o->oPosY += targetOffsetY;
    384 
    385     return targetPitch;
    386 }
    387 
    388 static s32 approach_f32_ptr(f32 *px, f32 target, f32 delta) {
    389     if (*px > target) {
    390         delta = -delta;
    391     }
    392 
    393     *px += delta;
    394 
    395     if ((*px - target) * delta >= 0) {
    396         *px = target;
    397         return TRUE;
    398     }
    399     return FALSE;
    400 }
    401 
    402 static s32 obj_forward_vel_approach(f32 target, f32 delta) {
    403     return approach_f32_ptr(&o->oForwardVel, target, delta);
    404 }
    405 
    406 static s32 obj_y_vel_approach(f32 target, f32 delta) {
    407     return approach_f32_ptr(&o->oVelY, target, delta);
    408 }
    409 
    410 static s32 obj_move_pitch_approach(s16 target, s16 delta) {
    411     o->oMoveAnglePitch = approach_s16_symmetric(o->oMoveAnglePitch, target, delta);
    412 
    413     if ((s16) o->oMoveAnglePitch == target) {
    414         return TRUE;
    415     }
    416 
    417     return FALSE;
    418 }
    419 
    420 static s32 obj_face_pitch_approach(s16 targetPitch, s16 deltaPitch) {
    421     o->oFaceAnglePitch = approach_s16_symmetric(o->oFaceAnglePitch, targetPitch, deltaPitch);
    422 
    423     if ((s16) o->oFaceAnglePitch == targetPitch) {
    424         return TRUE;
    425     }
    426 
    427     return FALSE;
    428 }
    429 
    430 static s32 obj_face_yaw_approach(s16 targetYaw, s16 deltaYaw) {
    431     o->oFaceAngleYaw = approach_s16_symmetric(o->oFaceAngleYaw, targetYaw, deltaYaw);
    432 
    433     if ((s16) o->oFaceAngleYaw == targetYaw) {
    434         return TRUE;
    435     }
    436 
    437     return FALSE;
    438 }
    439 
    440 static s32 obj_face_roll_approach(s16 targetRoll, s16 deltaRoll) {
    441     o->oFaceAngleRoll = approach_s16_symmetric(o->oFaceAngleRoll, targetRoll, deltaRoll);
    442 
    443     if ((s16) o->oFaceAngleRoll == targetRoll) {
    444         return TRUE;
    445     }
    446 
    447     return FALSE;
    448 }
    449 
    450 static s32 obj_smooth_turn(s16 *angleVel, s32 *angle, s16 targetAngle, f32 targetSpeedProportion,
    451                            s16 accel, s16 minSpeed, s16 maxSpeed) {
    452     s16 currentSpeed;
    453     s16 currentAngle = (s16)(*angle);
    454 
    455     *angleVel =
    456         approach_s16_symmetric(*angleVel, (targetAngle - currentAngle) * targetSpeedProportion, accel);
    457 
    458     currentSpeed = absi(*angleVel);
    459     clamp_s16(&currentSpeed, minSpeed, maxSpeed);
    460 
    461     *angle = approach_s16_symmetric(*angle, targetAngle, currentSpeed);
    462     return (s16)(*angle) == targetAngle;
    463 }
    464 
    465 static void obj_roll_to_match_yaw_turn(s16 targetYaw, s16 maxRoll, s16 rollSpeed) {
    466     s16 targetRoll = o->oMoveAngleYaw - targetYaw;
    467     clamp_s16(&targetRoll, -maxRoll, maxRoll);
    468     obj_face_roll_approach(targetRoll, rollSpeed);
    469 }
    470 
    471 static s16 random_linear_offset(s16 base, s16 range) {
    472     return base + (s16)(range * random_float());
    473 }
    474 
    475 static s16 random_mod_offset(s16 base, s16 step, s16 mod) {
    476     return base + step * (random_u16() % mod);
    477 }
    478 
    479 static s16 obj_random_fixed_turn(s16 delta) {
    480     return o->oMoveAngleYaw + (s16) random_sign() * delta;
    481 }
    482 
    483 /**
    484  * Begin by increasing the object's scale by *scaleVel, and slowly decreasing
    485  * scaleVel. Once the object starts to shrink, wait a bit, and then begin to
    486  * scale the object toward endScale. The first time it reaches below
    487  * shootFireScale during this time, return 1.
    488  * Return -1 once it's reached endScale.
    489  */
    490 static s32 obj_grow_then_shrink(f32 *scaleVel, f32 shootFireScale, f32 endScale) {
    491     if (o->oTimer < 2) {
    492         o->header.gfx.scale[0] += *scaleVel;
    493 
    494         if ((*scaleVel -= 0.01f) > -0.03f) {
    495             o->oTimer = 0;
    496         }
    497     } else if (o->oTimer > 10) {
    498         if (approach_f32_ptr(&o->header.gfx.scale[0], endScale, 0.05f)) {
    499             return -1;
    500         } else if (*scaleVel != 0.0f && o->header.gfx.scale[0] < shootFireScale) {
    501             *scaleVel = 0.0f;
    502             return 1;
    503         }
    504     }
    505 
    506     return 0;
    507 }
    508 
    509 static s32 oscillate_toward(s32 *value, f32 *vel, s32 target, f32 velCloseToZero, f32 accel,
    510                             f32 slowdown) {
    511     s32 startValue = *value;
    512     *value += (s32) *vel;
    513 
    514     if (*value == target
    515         || ((*value - target) * (startValue - target) < 0 && *vel > -velCloseToZero
    516             && *vel < velCloseToZero)) {
    517         *value = target;
    518         *vel = 0.0f;
    519         return TRUE;
    520     } else {
    521         if (*value >= target) {
    522             accel = -accel;
    523         }
    524         if (*vel * accel < 0.0f) {
    525             accel *= slowdown;
    526         }
    527 
    528         *vel += accel;
    529     }
    530 
    531     return FALSE;
    532 }
    533 
    534 static void obj_update_blinking(s32 *blinkTimer, s16 baseCycleLength, s16 cycleLengthRange,
    535                                 s16 blinkLength) {
    536     if (*blinkTimer != 0) {
    537         (*blinkTimer)--;
    538     } else {
    539         *blinkTimer = random_linear_offset(baseCycleLength, cycleLengthRange);
    540     }
    541 
    542     if (*blinkTimer > blinkLength) {
    543         o->oAnimState = 0;
    544     } else {
    545         o->oAnimState = 1;
    546     }
    547 }
    548 
    549 static s32 obj_resolve_object_collisions(s32 *targetYaw) {
    550     struct Object *otherObject;
    551     f32 dx;
    552     f32 dz;
    553     s16 angle;
    554     f32 radius;
    555     f32 otherRadius;
    556     f32 relativeRadius;
    557     f32 newCenterX;
    558     f32 newCenterZ;
    559 
    560     if (o->numCollidedObjs != 0) {
    561         otherObject = o->collidedObjs[0];
    562         if (otherObject != gMarioObject) {
    563             //! If one object moves after collisions are detected and this code
    564             //  runs, the objects can move toward each other (transport cloning)
    565 
    566             dx = otherObject->oPosX - o->oPosX;
    567             dz = otherObject->oPosZ - o->oPosZ;
    568             angle = atan2s(dx, dz); //! This should be atan2s(dz, dx)
    569 
    570             radius = o->hitboxRadius;
    571             otherRadius = otherObject->hitboxRadius;
    572             relativeRadius = radius / (radius + otherRadius);
    573 
    574             newCenterX = o->oPosX + dx * relativeRadius;
    575             newCenterZ = o->oPosZ + dz * relativeRadius;
    576 
    577             o->oPosX = newCenterX - radius * coss(angle);
    578             o->oPosZ = newCenterZ - radius * sins(angle);
    579 
    580             otherObject->oPosX = newCenterX + otherRadius * coss(angle);
    581             otherObject->oPosZ = newCenterZ + otherRadius * sins(angle);
    582 
    583             if (targetYaw != NULL && abs_angle_diff(o->oMoveAngleYaw, angle) < 0x4000) {
    584                 // Bounce off object (or it would, if the above atan2s bug
    585                 // were fixed)
    586                 *targetYaw = (s16)(angle - o->oMoveAngleYaw + angle + 0x8000);
    587                 return TRUE;
    588             }
    589         }
    590     }
    591 
    592     return FALSE;
    593 }
    594 
    595 static s32 obj_bounce_off_walls_edges_objects(s32 *targetYaw) {
    596     if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
    597         *targetYaw = cur_obj_reflect_move_angle_off_wall();
    598     } else if (o->oMoveFlags & OBJ_MOVE_HIT_EDGE) {
    599         *targetYaw = (s16)(o->oMoveAngleYaw + 0x8000);
    600     } else if (!obj_resolve_object_collisions(targetYaw)) {
    601         return FALSE;
    602     }
    603 
    604     return TRUE;
    605 }
    606 
    607 static s32 obj_resolve_collisions_and_turn(s16 targetYaw, s16 turnSpeed) {
    608     obj_resolve_object_collisions(NULL);
    609 
    610     if (cur_obj_rotate_yaw_toward(targetYaw, turnSpeed)) {
    611         return FALSE;
    612     } else {
    613         return TRUE;
    614     }
    615 }
    616 
    617 static void obj_die_if_health_non_positive(void) {
    618     if (o->oHealth <= 0) {
    619         if (o->oDeathSound == 0) {
    620             spawn_mist_particles_with_sound(SOUND_OBJ_DEFAULT_DEATH);
    621         } else if (o->oDeathSound > 0) {
    622             spawn_mist_particles_with_sound(o->oDeathSound);
    623         } else {
    624             spawn_mist_particles();
    625         }
    626 
    627         if (o->oNumLootCoins < 0) {
    628             spawn_object(o, MODEL_BLUE_COIN, bhvSpawnedBlueCoin);
    629         } else {
    630             obj_spawn_loot_yellow_coins(o, o->oNumLootCoins, 20.0f);
    631         }
    632         // This doesn't do anything
    633         obj_spawn_loot_yellow_coins(o, o->oNumLootCoins, 20.0f);
    634 
    635         if (o->oHealth < 0) {
    636             cur_obj_hide();
    637             cur_obj_become_intangible();
    638         } else {
    639             obj_mark_for_deletion(o);
    640         }
    641     }
    642 }
    643 
    644 UNUSED static void obj_unused_die(void) {
    645     o->oHealth = 0;
    646     obj_die_if_health_non_positive();
    647 }
    648 
    649 static void obj_set_knockback_action(s32 attackType) {
    650     switch (attackType) {
    651         case ATTACK_KICK_OR_TRIP:
    652         case ATTACK_FAST_ATTACK:
    653             o->oAction = OBJ_ACT_VERTICAL_KNOCKBACK;
    654             o->oForwardVel = 20.0f;
    655             o->oVelY = 50.0f;
    656             break;
    657 
    658         default:
    659             o->oAction = OBJ_ACT_HORIZONTAL_KNOCKBACK;
    660             o->oForwardVel = 50.0f;
    661             o->oVelY = 30.0f;
    662             break;
    663     }
    664 
    665     o->oFlags &= ~OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW;
    666     o->oMoveAngleYaw = obj_angle_to_object(gMarioObject, o);
    667 }
    668 
    669 static void obj_set_squished_action(void) {
    670     cur_obj_play_sound_2(SOUND_OBJ_STOMPED);
    671     o->oAction = OBJ_ACT_SQUISHED;
    672 }
    673 
    674 static s32 obj_die_if_above_lava_and_health_non_positive(void) {
    675     if (o->oMoveFlags & OBJ_MOVE_UNDERWATER_ON_GROUND) {
    676         if (o->oGravity + o->oBuoyancy > 0.0f
    677             || find_water_level(o->oPosX, o->oPosZ) - o->oPosY < 150.0f) {
    678             return FALSE;
    679         }
    680     } else if (!(o->oMoveFlags & OBJ_MOVE_ABOVE_LAVA)) {
    681         if (o->oMoveFlags & OBJ_MOVE_ENTERED_WATER) {
    682             if (o->oWallHitboxRadius < 200.0f) {
    683                 cur_obj_play_sound_2(SOUND_OBJ_DIVING_INTO_WATER);
    684             } else {
    685                 cur_obj_play_sound_2(SOUND_OBJ_DIVING_IN_WATER);
    686             }
    687         }
    688         return FALSE;
    689     }
    690 
    691     obj_die_if_health_non_positive();
    692     return TRUE;
    693 }
    694 
    695 static s32 obj_handle_attacks(struct ObjectHitbox *hitbox, s32 attackedMarioAction,
    696                               u8 *attackHandlers) {
    697     s32 attackType;
    698 
    699     obj_set_hitbox(o, hitbox);
    700 
    701     //! Die immediately if above lava
    702     if (obj_die_if_above_lava_and_health_non_positive()) {
    703         return 1;
    704     } else if (o->oInteractStatus & INT_STATUS_INTERACTED) {
    705         if (o->oInteractStatus & INT_STATUS_ATTACKED_MARIO) {
    706             if (o->oAction != attackedMarioAction) {
    707                 o->oAction = attackedMarioAction;
    708                 o->oTimer = 0;
    709             }
    710         } else {
    711             attackType = o->oInteractStatus & INT_STATUS_ATTACK_MASK;
    712 
    713             switch (attackHandlers[attackType - 1]) {
    714                 case ATTACK_HANDLER_NOP:
    715                     break;
    716 
    717                 case ATTACK_HANDLER_DIE_IF_HEALTH_NON_POSITIVE:
    718                     obj_die_if_health_non_positive();
    719                     break;
    720 
    721                 case ATTACK_HANDLER_KNOCKBACK:
    722                     obj_set_knockback_action(attackType);
    723                     break;
    724 
    725                 case ATTACK_HANDLER_SQUISHED:
    726                     obj_set_squished_action();
    727                     break;
    728 
    729                 case ATTACK_HANDLER_SPECIAL_KOOPA_LOSE_SHELL:
    730                     shelled_koopa_attack_handler(attackType);
    731                     break;
    732 
    733                 case ATTACK_HANDLER_SET_SPEED_TO_ZERO:
    734                     obj_set_speed_to_zero();
    735                     break;
    736 
    737                 case ATTACK_HANDLER_SPECIAL_WIGGLER_JUMPED_ON:
    738                     wiggler_jumped_on_attack_handler();
    739                     break;
    740 
    741                 case ATTACK_HANDLER_SPECIAL_HUGE_GOOMBA_WEAKLY_ATTACKED:
    742                     huge_goomba_weakly_attacked();
    743                     break;
    744 
    745                 case ATTACK_HANDLER_SQUISHED_WITH_BLUE_COIN:
    746                     o->oNumLootCoins = -1;
    747                     obj_set_squished_action();
    748                     break;
    749             }
    750 
    751             o->oInteractStatus = 0;
    752             return attackType;
    753         }
    754     }
    755 
    756     o->oInteractStatus = 0;
    757     return 0;
    758 }
    759 
    760 static void obj_act_knockback(UNUSED f32 baseScale) {
    761     cur_obj_update_floor_and_walls();
    762 
    763     if (o->header.gfx.animInfo.curAnim != NULL) {
    764         cur_obj_extend_animation_if_at_end();
    765     }
    766 
    767     //! Dies immediately if above lava
    768     if ((o->oMoveFlags
    769          & (OBJ_MOVE_MASK_ON_GROUND | OBJ_MOVE_MASK_IN_WATER | OBJ_MOVE_HIT_WALL | OBJ_MOVE_ABOVE_LAVA))
    770         || (o->oAction == OBJ_ACT_VERTICAL_KNOCKBACK && o->oTimer >= 9)) {
    771         obj_die_if_health_non_positive();
    772     }
    773 
    774     cur_obj_move_standard(-78);
    775 }
    776 
    777 static void obj_act_squished(f32 baseScale) {
    778     f32 targetScaleY = baseScale * 0.3f;
    779 
    780     cur_obj_update_floor_and_walls();
    781 
    782     if (o->header.gfx.animInfo.curAnim != NULL) {
    783         cur_obj_extend_animation_if_at_end();
    784     }
    785 
    786     if (approach_f32_ptr(&o->header.gfx.scale[1], targetScaleY, baseScale * 0.14f)) {
    787         o->header.gfx.scale[0] = o->header.gfx.scale[2] = baseScale * 2.0f - o->header.gfx.scale[1];
    788 
    789         if (o->oTimer > 15) {
    790             obj_die_if_health_non_positive();
    791         }
    792     }
    793 
    794     o->oForwardVel = 0.0f;
    795     cur_obj_move_standard(-78);
    796 }
    797 
    798 static s32 obj_update_standard_actions(f32 scale) {
    799     if (o->oAction < 100) {
    800         return TRUE;
    801     } else {
    802         cur_obj_become_intangible();
    803 
    804         switch (o->oAction) {
    805             case OBJ_ACT_HORIZONTAL_KNOCKBACK:
    806             case OBJ_ACT_VERTICAL_KNOCKBACK:
    807                 obj_act_knockback(scale);
    808                 break;
    809 
    810             case OBJ_ACT_SQUISHED:
    811                 obj_act_squished(scale);
    812                 break;
    813         }
    814 
    815         return FALSE;
    816     }
    817 }
    818 
    819 static s32 obj_check_attacks(struct ObjectHitbox *hitbox, s32 attackedMarioAction) {
    820     s32 attackType;
    821 
    822     obj_set_hitbox(o, hitbox);
    823 
    824     //! Dies immediately if above lava
    825     if (obj_die_if_above_lava_and_health_non_positive()) {
    826         return 1;
    827     } else if (o->oInteractStatus & INT_STATUS_INTERACTED) {
    828         if (o->oInteractStatus & INT_STATUS_ATTACKED_MARIO) {
    829             if (o->oAction != attackedMarioAction) {
    830                 o->oAction = attackedMarioAction;
    831                 o->oTimer = 0;
    832             }
    833         } else {
    834             attackType = o->oInteractStatus & INT_STATUS_ATTACK_MASK;
    835             obj_die_if_health_non_positive();
    836             o->oInteractStatus = 0;
    837             return attackType;
    838         }
    839     }
    840 
    841     o->oInteractStatus = 0;
    842     return 0;
    843 }
    844 
    845 static s32 obj_move_for_one_second(s32 endAction) {
    846     cur_obj_update_floor_and_walls();
    847     cur_obj_extend_animation_if_at_end();
    848 
    849     if (o->oTimer > 30) {
    850         o->oAction = endAction;
    851         return TRUE;
    852     }
    853 
    854     cur_obj_move_standard(-78);
    855     return FALSE;
    856 }
    857 
    858 /**
    859  * If we are far from home (> threshold away), then set oAngleToMario to the
    860  * angle to home and oDistanceToMario to 25000.
    861  * If we are close to home, but Mario is far from us (> threshold away), then
    862  * keep oAngleToMario the same and set oDistanceToMario to 20000.
    863  * If we are close to both home and Mario, then keep both oAngleToMario and
    864  * oDistanceToMario the same.
    865  *
    866  * The point of this function is to avoid having to write extra code to get
    867  * the object to return to home. When Mario is far away and the object is far
    868  * from home, it could theoretically re-use the "approach Mario" logic to approach
    869  * its home instead.
    870  * However, most objects that use this function handle the far-from-home case
    871  * separately anyway.
    872  * This function causes seemingly erroneous behavior in some objects that try to
    873  * attack Mario (e.g. fly guy shooting fire or lunging), especially when combined
    874  * with partial updates.
    875  */
    876 static void treat_far_home_as_mario(f32 threshold) {
    877     f32 dx = o->oHomeX - o->oPosX;
    878     f32 dy = o->oHomeY - o->oPosY;
    879     f32 dz = o->oHomeZ - o->oPosZ;
    880     f32 distance = sqrtf(dx * dx + dy * dy + dz * dz);
    881 
    882     if (distance > threshold) {
    883         o->oAngleToMario = atan2s(dz, dx);
    884         o->oDistanceToMario = 25000.0f;
    885     } else {
    886         dx = o->oHomeX - gMarioObject->oPosX;
    887         dy = o->oHomeY - gMarioObject->oPosY;
    888         dz = o->oHomeZ - gMarioObject->oPosZ;
    889         distance = sqrtf(dx * dx + dy * dy + dz * dz);
    890 
    891         if (distance > threshold) {
    892             o->oDistanceToMario = 20000.0f;
    893         }
    894     }
    895 }
    896 
    897 #include "behaviors/koopa.inc.c" // TODO: Text arg field name
    898 #include "behaviors/pokey.inc.c"
    899 #include "behaviors/swoop.inc.c"
    900 #include "behaviors/fly_guy.inc.c"
    901 #include "behaviors/goomba.inc.c"
    902 #include "behaviors/chain_chomp.inc.c" // TODO: chain_chomp_sub_act_lunge documentation
    903 #include "behaviors/wiggler.inc.c"     // TODO
    904 #include "behaviors/spiny.inc.c"
    905 #include "behaviors/enemy_lakitu.inc.c" // TODO
    906 #include "behaviors/cloud.inc.c"
    907 #include "behaviors/camera_lakitu.inc.c" // TODO: 104 label, follow cam documentation
    908 #include "behaviors/monty_mole.inc.c"    // TODO
    909 #include "behaviors/platform_on_track.inc.c"
    910 #include "behaviors/seesaw_platform.inc.c"
    911 #include "behaviors/ferris_wheel.inc.c"
    912 #include "behaviors/water_bomb.inc.c" // TODO: Shadow position
    913 #include "behaviors/ttc_rotating_solid.inc.c"
    914 #include "behaviors/ttc_pendulum.inc.c"
    915 #include "behaviors/ttc_treadmill.inc.c" // TODO
    916 #include "behaviors/ttc_moving_bar.inc.c"
    917 #include "behaviors/ttc_cog.inc.c"
    918 #include "behaviors/ttc_pit_block.inc.c"
    919 #include "behaviors/ttc_elevator.inc.c"
    920 #include "behaviors/ttc_2d_rotator.inc.c"
    921 #include "behaviors/ttc_spinner.inc.c"
    922 #include "behaviors/mr_blizzard.inc.c"
    923 #include "behaviors/sliding_platform_2.inc.c"
    924 #include "behaviors/rotating_octagonal_plat.inc.c"
    925 #include "behaviors/animated_floor_switch.inc.c"
    926 #include "behaviors/activated_bf_plat.inc.c"
    927 #include "behaviors/recovery_heart.inc.c"
    928 #include "behaviors/water_bomb_cannon.inc.c"
    929 #include "behaviors/unagi.inc.c"
    930 #include "behaviors/dorrie.inc.c"
    931 #include "behaviors/haunted_chair.inc.c"
    932 #include "behaviors/mad_piano.inc.c"
    933 #include "behaviors/flying_bookend_switch.inc.c"
    934 
    935 /**
    936  * Used by bowser, fly guy, piranha plant, and fire spitters.
    937  */
    938 void obj_spit_fire(s16 relativePosX, s16 relativePosY, s16 relativePosZ, f32 scale, s32 model,
    939                    f32 startSpeed, f32 endSpeed, s16 movePitch) {
    940     struct Object *obj = spawn_object_relative_with_scale(1, relativePosX, relativePosY, relativePosZ,
    941                                                            scale, o, model, bhvSmallPiranhaFlame);
    942 
    943     if (obj != NULL) {
    944         obj->oSmallPiranhaFlameStartSpeed = startSpeed;
    945         obj->oSmallPiranhaFlameEndSpeed = endSpeed;
    946         obj->oSmallPiranhaFlameModel = model;
    947         obj->oMoveAnglePitch = movePitch;
    948     }
    949 }
    950 
    951 #include "behaviors/fire_piranha_plant.inc.c"
    952 #include "behaviors/fire_spitter.inc.c"
    953 #include "behaviors/flame.inc.c"
    954 #include "behaviors/snufit.inc.c"
    955 #include "behaviors/horizontal_grindel.inc.c"
    956 #include "behaviors/eyerok.inc.c"
    957 #include "behaviors/klepto.inc.c"
    958 #include "behaviors/bird.inc.c"
    959 #include "behaviors/racing_penguin.inc.c"
    960 #include "behaviors/coffin.inc.c"
    961 #include "behaviors/clam.inc.c"
    962 #include "behaviors/skeeter.inc.c"
    963 #include "behaviors/swing_platform.inc.c"
    964 #include "behaviors/donut_platform.inc.c"
    965 #include "behaviors/ddd_pole.inc.c"
    966 #include "behaviors/reds_star_marker.inc.c"
    967 #include "behaviors/triplet_butterfly.inc.c"
    968 #include "behaviors/bubba.inc.c"