sm64

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

object_helpers.c (89695B)


      1 #include <PR/ultratypes.h>
      2 
      3 #include "sm64.h"
      4 #include "area.h"
      5 #include "behavior_actions.h"
      6 #include "behavior_data.h"
      7 #include "camera.h"
      8 #include "debug.h"
      9 #include "dialog_ids.h"
     10 #include "engine/behavior_script.h"
     11 #include "engine/geo_layout.h"
     12 #include "engine/math_util.h"
     13 #include "engine/surface_collision.h"
     14 #include "game_init.h"
     15 #include "helper_macros.h"
     16 #include "ingame_menu.h"
     17 #include "interaction.h"
     18 #include "level_table.h"
     19 #include "level_update.h"
     20 #include "mario.h"
     21 #include "mario_actions_cutscene.h"
     22 #include "memory.h"
     23 #include "obj_behaviors.h"
     24 #include "object_helpers.h"
     25 #include "object_list_processor.h"
     26 #include "rendering_graph_node.h"
     27 #include "spawn_object.h"
     28 #include "spawn_sound.h"
     29 
     30 static s8 sBBHStairJiggleOffsets[] = { -8, 8, -4, 4 };
     31 static s16 sPowersOfTwo[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
     32 static s8 sLevelsWithRooms[] = { LEVEL_BBH, LEVEL_CASTLE, LEVEL_HMC, -1 };
     33 
     34 static s32 clear_move_flag(u32 *, s32);
     35 
     36 #define o gCurrentObject
     37 
     38 Gfx *geo_update_projectile_pos_from_parent(s32 callContext, UNUSED struct GraphNode *node, Mat4 mtx) {
     39     if (callContext == GEO_CONTEXT_RENDER) {
     40         Mat4 sp20;
     41         struct Object *obj = (struct Object *) gCurGraphNodeObject; // TODO: change global type to Object pointer
     42         if (obj->prevObj) {
     43             create_transformation_from_matrices(sp20, mtx, *gCurGraphNodeCamera->matrixPtr);
     44             obj_update_pos_from_parent_transformation(sp20, obj->prevObj);
     45             obj_set_gfx_pos_from_pos(obj->prevObj);
     46         }
     47     }
     48 
     49     return NULL;
     50 }
     51 
     52 Gfx *geo_update_layer_transparency(s32 callContext, struct GraphNode *node, UNUSED void *context) {
     53     Gfx *dlStart, *dlHead;
     54     struct Object *objectGraphNode;
     55     struct GraphNodeGenerated *currentGraphNode;
     56     UNUSED struct GraphNodeGenerated *sp2C;
     57     s32 objectOpacity;
     58 
     59     dlStart = NULL;
     60 
     61     if (callContext == GEO_CONTEXT_RENDER) {
     62         objectGraphNode = (struct Object *) gCurGraphNodeObject; // TODO: change this to object pointer?
     63         currentGraphNode = (struct GraphNodeGenerated *) node;
     64         sp2C = (struct GraphNodeGenerated *) node;
     65 
     66         if (gCurGraphNodeHeldObject != NULL) {
     67             objectGraphNode = gCurGraphNodeHeldObject->objNode;
     68         }
     69 
     70         objectOpacity = objectGraphNode->oOpacity;
     71         dlStart = alloc_display_list(sizeof(Gfx) * 3);
     72 
     73         dlHead = dlStart;
     74 
     75         if (objectOpacity == 0xFF) {
     76             if (currentGraphNode->parameter == 20) {
     77                 currentGraphNode->fnNode.node.flags =
     78                 0x600 | (currentGraphNode->fnNode.node.flags & 0xFF);
     79             } else {
     80                 currentGraphNode->fnNode.node.flags =
     81                 0x100 | (currentGraphNode->fnNode.node.flags & 0xFF);
     82             }
     83 
     84             objectGraphNode->oAnimState = 0;
     85         } else {
     86             if (currentGraphNode->parameter == 20) {
     87                 currentGraphNode->fnNode.node.flags =
     88                 0x600 | (currentGraphNode->fnNode.node.flags & 0xFF);
     89             } else {
     90                 currentGraphNode->fnNode.node.flags =
     91                 0x500 | (currentGraphNode->fnNode.node.flags & 0xFF);
     92             }
     93 
     94             objectGraphNode->oAnimState = 1;
     95 
     96 #ifdef VERSION_JP
     97             if (currentGraphNode->parameter == 10) {
     98                 if (gDebugInfo[DEBUG_PAGE_ENEMYINFO][3]) {
     99                     gDPSetAlphaCompare(dlHead++, G_AC_DITHER);
    100                 }
    101             } else {
    102                 if (objectGraphNode->activeFlags & ACTIVE_FLAG_DITHERED_ALPHA) {
    103                     gDPSetAlphaCompare(dlHead++, G_AC_DITHER);
    104                 }
    105             }
    106 #else // gDebugInfo accesses were removed in all non-JP versions.
    107             if (objectOpacity == 0 && segmented_to_virtual(bhvBowser) == objectGraphNode->behavior) {
    108                 objectGraphNode->oAnimState = 2;
    109             }
    110             // the debug info check was removed in US. so we need to
    111             // perform the only necessary check instead of the debuginfo
    112             // one.
    113             if (currentGraphNode->parameter != 10) {
    114                 if (objectGraphNode->activeFlags & ACTIVE_FLAG_DITHERED_ALPHA) {
    115                     gDPSetAlphaCompare(dlHead++, G_AC_DITHER);
    116                 }
    117             }
    118 #endif
    119         }
    120 
    121         gDPSetEnvColor(dlHead++, 255, 255, 255, objectOpacity);
    122         gSPEndDisplayList(dlHead);
    123     }
    124 
    125     return dlStart;
    126 }
    127 
    128 /**
    129  * @bug Every geo function declares the 3 parameters of callContext, node, and
    130  * the matrix array. This one (see also geo_switch_area) doesn't. When executed,
    131  * the node function executor passes the 3rd argument to a function that doesn't
    132  * declare it. This is undefined behavior, but harmless in practice due to the
    133  * o32 calling convention.
    134  */
    135 #ifdef AVOID_UB
    136 Gfx *geo_switch_anim_state(s32 callContext, struct GraphNode *node, UNUSED void *context)
    137 #else
    138 Gfx *geo_switch_anim_state(s32 callContext, struct GraphNode *node)
    139 #endif
    140 {
    141     struct Object *obj;
    142     struct GraphNodeSwitchCase *switchCase;
    143 
    144     if (callContext == GEO_CONTEXT_RENDER) {
    145         obj = (struct Object *) gCurGraphNodeObject; // TODO: change global type to Object pointer
    146 
    147         // move to a local var because GraphNodes are passed in all geo functions.
    148         // cast the pointer.
    149         switchCase = (struct GraphNodeSwitchCase *) node;
    150 
    151         if (gCurGraphNodeHeldObject != NULL) {
    152             obj = gCurGraphNodeHeldObject->objNode;
    153         }
    154 
    155         // if the case is greater than the number of cases, set to 0 to avoid overflowing
    156         // the switch.
    157         if (obj->oAnimState >= switchCase->numCases) {
    158             obj->oAnimState = 0;
    159         }
    160 
    161         // assign the case number for execution.
    162         switchCase->selectedCase = obj->oAnimState;
    163     }
    164 
    165     return NULL;
    166 }
    167 
    168 //! @bug Same issue as geo_switch_anim_state.
    169 #ifdef AVOID_UB
    170 Gfx *geo_switch_area(s32 callContext, struct GraphNode *node, UNUSED void *context)
    171 #else
    172 Gfx *geo_switch_area(s32 callContext, struct GraphNode *node)
    173 #endif
    174 {
    175     s16 sp26;
    176     struct Surface *sp20;
    177     UNUSED struct Object *sp1C =
    178         (struct Object *) gCurGraphNodeObject; // TODO: change global type to Object pointer
    179     struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node;
    180 
    181     if (callContext == GEO_CONTEXT_RENDER) {
    182         if (gMarioObject == NULL) {
    183             switchCase->selectedCase = 0;
    184         } else {
    185             gFindFloorIncludeSurfaceIntangible = TRUE;
    186 
    187             find_floor(gMarioObject->oPosX, gMarioObject->oPosY, gMarioObject->oPosZ, &sp20);
    188 
    189             if (sp20) {
    190                 gMarioCurrentRoom = sp20->room;
    191                 sp26 = sp20->room - 1;
    192                 print_debug_top_down_objectinfo("areainfo %d", sp20->room);
    193 
    194                 if (sp26 >= 0) {
    195                     switchCase->selectedCase = sp26;
    196                 }
    197             }
    198         }
    199     } else {
    200         switchCase->selectedCase = 0;
    201     }
    202 
    203     return NULL;
    204 }
    205 
    206 void obj_update_pos_from_parent_transformation(Mat4 a0, struct Object *a1) {
    207     f32 spC = a1->oParentRelativePosX;
    208     f32 sp8 = a1->oParentRelativePosY;
    209     f32 sp4 = a1->oParentRelativePosZ;
    210 
    211     a1->oPosX = spC * a0[0][0] + sp8 * a0[1][0] + sp4 * a0[2][0] + a0[3][0];
    212     a1->oPosY = spC * a0[0][1] + sp8 * a0[1][1] + sp4 * a0[2][1] + a0[3][1];
    213     a1->oPosZ = spC * a0[0][2] + sp8 * a0[1][2] + sp4 * a0[2][2] + a0[3][2];
    214 }
    215 
    216 void obj_apply_scale_to_matrix(struct Object *obj, Mat4 dst, Mat4 src) {
    217     dst[0][0] = src[0][0] * obj->header.gfx.scale[0];
    218     dst[1][0] = src[1][0] * obj->header.gfx.scale[1];
    219     dst[2][0] = src[2][0] * obj->header.gfx.scale[2];
    220     dst[3][0] = src[3][0];
    221 
    222     dst[0][1] = src[0][1] * obj->header.gfx.scale[0];
    223     dst[1][1] = src[1][1] * obj->header.gfx.scale[1];
    224     dst[2][1] = src[2][1] * obj->header.gfx.scale[2];
    225     dst[3][1] = src[3][1];
    226 
    227     dst[0][2] = src[0][2] * obj->header.gfx.scale[0];
    228     dst[1][2] = src[1][2] * obj->header.gfx.scale[1];
    229     dst[2][2] = src[2][2] * obj->header.gfx.scale[2];
    230     dst[3][2] = src[3][2];
    231 
    232     dst[0][3] = src[0][3];
    233     dst[1][3] = src[1][3];
    234     dst[2][3] = src[2][3];
    235     dst[3][3] = src[3][3];
    236 }
    237 
    238 void create_transformation_from_matrices(Mat4 a0, Mat4 a1, Mat4 a2) {
    239     f32 spC, sp8, sp4;
    240 
    241     spC = a2[3][0] * a2[0][0] + a2[3][1] * a2[0][1] + a2[3][2] * a2[0][2];
    242     sp8 = a2[3][0] * a2[1][0] + a2[3][1] * a2[1][1] + a2[3][2] * a2[1][2];
    243     sp4 = a2[3][0] * a2[2][0] + a2[3][1] * a2[2][1] + a2[3][2] * a2[2][2];
    244 
    245     a0[0][0] = a1[0][0] * a2[0][0] + a1[0][1] * a2[0][1] + a1[0][2] * a2[0][2];
    246     a0[0][1] = a1[0][0] * a2[1][0] + a1[0][1] * a2[1][1] + a1[0][2] * a2[1][2];
    247     a0[0][2] = a1[0][0] * a2[2][0] + a1[0][1] * a2[2][1] + a1[0][2] * a2[2][2];
    248 
    249     a0[1][0] = a1[1][0] * a2[0][0] + a1[1][1] * a2[0][1] + a1[1][2] * a2[0][2];
    250     a0[1][1] = a1[1][0] * a2[1][0] + a1[1][1] * a2[1][1] + a1[1][2] * a2[1][2];
    251     a0[1][2] = a1[1][0] * a2[2][0] + a1[1][1] * a2[2][1] + a1[1][2] * a2[2][2];
    252 
    253     a0[2][0] = a1[2][0] * a2[0][0] + a1[2][1] * a2[0][1] + a1[2][2] * a2[0][2];
    254     a0[2][1] = a1[2][0] * a2[1][0] + a1[2][1] * a2[1][1] + a1[2][2] * a2[1][2];
    255     a0[2][2] = a1[2][0] * a2[2][0] + a1[2][1] * a2[2][1] + a1[2][2] * a2[2][2];
    256 
    257     a0[3][0] = a1[3][0] * a2[0][0] + a1[3][1] * a2[0][1] + a1[3][2] * a2[0][2] - spC;
    258     a0[3][1] = a1[3][0] * a2[1][0] + a1[3][1] * a2[1][1] + a1[3][2] * a2[1][2] - sp8;
    259     a0[3][2] = a1[3][0] * a2[2][0] + a1[3][1] * a2[2][1] + a1[3][2] * a2[2][2] - sp4;
    260 
    261     a0[0][3] = 0.0f;
    262     a0[1][3] = 0.0f;
    263     a0[2][3] = 0.0f;
    264     a0[3][3] = 1.0f;
    265 }
    266 
    267 void obj_set_held_state(struct Object *obj, const BehaviorScript *heldBehavior) {
    268     obj->parentObj = o;
    269 
    270     if (obj->oFlags & OBJ_FLAG_HOLDABLE) {
    271         if (heldBehavior == bhvCarrySomething3) {
    272             obj->oHeldState = HELD_HELD;
    273         }
    274 
    275         if (heldBehavior == bhvCarrySomething5) {
    276             obj->oHeldState = HELD_THROWN;
    277         }
    278 
    279         if (heldBehavior == bhvCarrySomething4) {
    280             obj->oHeldState = HELD_DROPPED;
    281         }
    282     } else {
    283         obj->curBhvCommand = segmented_to_virtual(heldBehavior);
    284         obj->bhvStackIndex = 0;
    285     }
    286 }
    287 
    288 f32 lateral_dist_between_objects(struct Object *obj1, struct Object *obj2) {
    289     f32 dx = obj1->oPosX - obj2->oPosX;
    290     f32 dz = obj1->oPosZ - obj2->oPosZ;
    291 
    292     return sqrtf(dx * dx + dz * dz);
    293 }
    294 
    295 f32 dist_between_objects(struct Object *obj1, struct Object *obj2) {
    296     f32 dx = obj1->oPosX - obj2->oPosX;
    297     f32 dy = obj1->oPosY - obj2->oPosY;
    298     f32 dz = obj1->oPosZ - obj2->oPosZ;
    299 
    300     return sqrtf(dx * dx + dy * dy + dz * dz);
    301 }
    302 
    303 void cur_obj_forward_vel_approach_upward(f32 target, f32 increment) {
    304     if (o->oForwardVel >= target) {
    305         o->oForwardVel = target;
    306     } else {
    307         o->oForwardVel += increment;
    308     }
    309 }
    310 
    311 s32 approach_f32_signed(f32 *value, f32 target, f32 increment) {
    312     s32 reachedTarget = FALSE;
    313 
    314     *value += increment;
    315 
    316     if (increment >= 0.0f) {
    317         if (*value > target) {
    318             *value = target;
    319             reachedTarget = TRUE;
    320         }
    321     } else {
    322         if (*value < target) {
    323             *value = target;
    324             reachedTarget = TRUE;
    325         }
    326     }
    327 
    328     return reachedTarget;
    329 }
    330 
    331 f32 approach_f32_symmetric(f32 value, f32 target, f32 increment) {
    332     f32 dist;
    333 
    334     if ((dist = target - value) >= 0.0f) {
    335         if (dist > increment) {
    336             value += increment;
    337         } else {
    338             value = target;
    339         }
    340     } else {
    341         if (dist < -increment) {
    342             value -= increment;
    343         } else {
    344             value = target;
    345         }
    346     }
    347 
    348     return value;
    349 }
    350 
    351 s16 approach_s16_symmetric(s16 value, s16 target, s16 increment) {
    352     s16 dist = target - value;
    353 
    354     if (dist >= 0) {
    355         if (dist > increment) {
    356             value += increment;
    357         } else {
    358             value = target;
    359         }
    360     } else {
    361         if (dist < -increment) {
    362             value -= increment;
    363         } else {
    364             value = target;
    365         }
    366     }
    367 
    368     return value;
    369 }
    370 
    371 s32 cur_obj_rotate_yaw_toward(s16 target, s16 increment) {
    372     s16 startYaw;
    373 
    374     startYaw = (s16) o->oMoveAngleYaw;
    375     o->oMoveAngleYaw = approach_s16_symmetric(o->oMoveAngleYaw, target, increment);
    376 
    377     if ((o->oAngleVelYaw = (s16)((s16) o->oMoveAngleYaw - startYaw)) == 0) {
    378         return TRUE;
    379     } else {
    380         return FALSE;
    381     }
    382 }
    383 
    384 s16 obj_angle_to_object(struct Object *obj1, struct Object *obj2) {
    385     f32 z1, x1, z2, x2;
    386     s16 angle;
    387 
    388     z1 = obj1->oPosZ; z2 = obj2->oPosZ; // ordering of instructions..
    389     x1 = obj1->oPosX; x2 = obj2->oPosX;
    390 
    391     angle = atan2s(z2 - z1, x2 - x1);
    392     return angle;
    393 }
    394 
    395 s16 obj_turn_toward_object(struct Object *obj, struct Object *target, s16 angleIndex, s16 turnAmount) {
    396     f32 a, b, c, d;
    397     UNUSED u8 filler[4];
    398     s16 targetAngle, startAngle;
    399 
    400     switch (angleIndex) {
    401         case O_MOVE_ANGLE_PITCH_INDEX:
    402         case O_FACE_ANGLE_PITCH_INDEX:
    403             a = target->oPosX - obj->oPosX;
    404             c = target->oPosZ - obj->oPosZ;
    405             a = sqrtf(a * a + c * c);
    406 
    407             b = -obj->oPosY;
    408             d = -target->oPosY;
    409 
    410             targetAngle = atan2s(a, d - b);
    411             break;
    412 
    413         case O_MOVE_ANGLE_YAW_INDEX:
    414         case O_FACE_ANGLE_YAW_INDEX:
    415             a = obj->oPosZ;
    416             c = target->oPosZ;
    417             b = obj->oPosX;
    418             d = target->oPosX;
    419 
    420             targetAngle = atan2s(c - a, d - b);
    421             break;
    422     }
    423 
    424     startAngle = o->rawData.asU32[angleIndex];
    425     o->rawData.asU32[angleIndex] = approach_s16_symmetric(startAngle, targetAngle, turnAmount);
    426     return targetAngle;
    427 }
    428 
    429 void obj_set_parent_relative_pos(struct Object *obj, s16 relX, s16 relY, s16 relZ) {
    430     obj->oParentRelativePosX = relX;
    431     obj->oParentRelativePosY = relY;
    432     obj->oParentRelativePosZ = relZ;
    433 }
    434 
    435 void obj_set_pos(struct Object *obj, s16 x, s16 y, s16 z) {
    436     obj->oPosX = x;
    437     obj->oPosY = y;
    438     obj->oPosZ = z;
    439 }
    440 
    441 void obj_set_angle(struct Object *obj, s16 pitch, s16 yaw, s16 roll) {
    442     obj->oFaceAnglePitch = pitch;
    443     obj->oFaceAngleYaw = yaw;
    444     obj->oFaceAngleRoll = roll;
    445 
    446     obj->oMoveAnglePitch = pitch;
    447     obj->oMoveAngleYaw = yaw;
    448     obj->oMoveAngleRoll = roll;
    449 }
    450 
    451 /*
    452  * Spawns an object at an absolute location with a specified angle.
    453  */
    454 struct Object *spawn_object_abs_with_rot(struct Object *parent, s16 uselessArg, u32 model,
    455                                          const BehaviorScript *behavior,
    456                                          s16 x, s16 y, s16 z, s16 pitch, s16 yaw, s16 roll) {
    457     // 'uselessArg' is unused in the function spawn_object_at_origin()
    458     struct Object *newObj = spawn_object_at_origin(parent, uselessArg, model, behavior);
    459     obj_set_pos(newObj, x, y, z);
    460     obj_set_angle(newObj, pitch, yaw, roll);
    461 
    462     return newObj;
    463 }
    464 
    465 /*
    466  * Spawns an object relative to the parent with a specified angle... is what it is supposed to do.
    467  * The roll argument is never used, and the z offset is used for z-rotation instead. This is most likely
    468  * a copy-paste typo by one of the programmers.
    469  */
    470 struct Object *spawn_object_rel_with_rot(struct Object *parent, u32 model, const BehaviorScript *behavior,
    471                                          s16 xOff, s16 yOff, s16 zOff, s16 pitch, s16 yaw, UNUSED s16 roll) {
    472     struct Object *newObj = spawn_object_at_origin(parent, 0, model, behavior);
    473     newObj->oFlags |= OBJ_FLAG_TRANSFORM_RELATIVE_TO_PARENT;
    474     obj_set_parent_relative_pos(newObj, xOff, yOff, zOff);
    475     obj_set_angle(newObj, pitch, yaw, zOff); // Nice typo you got there Nintendo.
    476 
    477     return newObj;
    478 }
    479 
    480 struct Object *spawn_obj_with_transform_flags(struct Object *sp20, s32 model, const BehaviorScript *sp28) {
    481     struct Object *sp1C = spawn_object(sp20, model, sp28);
    482     sp1C->oFlags |= OBJ_FLAG_0020 | OBJ_FLAG_SET_THROW_MATRIX_FROM_TRANSFORM;
    483     return sp1C;
    484 }
    485 
    486 struct Object *spawn_water_droplet(struct Object *parent, struct WaterDropletParams *params) {
    487     f32 randomScale;
    488     struct Object *newObj = spawn_object(parent, params->model, params->behavior);
    489 
    490     if (params->flags & WATER_DROPLET_FLAG_RAND_ANGLE) {
    491         newObj->oMoveAngleYaw = random_u16();
    492     }
    493 
    494     if (params->flags & WATER_DROPLET_FLAG_RAND_ANGLE_INCR_PLUS_8000) {
    495         newObj->oMoveAngleYaw = (s16)(newObj->oMoveAngleYaw + 0x8000)
    496                                 + (s16) random_f32_around_zero(params->moveAngleRange);
    497     }
    498 
    499     if (params->flags & WATER_DROPLET_FLAG_RAND_ANGLE_INCR) {
    500         newObj->oMoveAngleYaw =
    501             (s16) newObj->oMoveAngleYaw + (s16) random_f32_around_zero(params->moveAngleRange);
    502     }
    503 
    504     if (params->flags & WATER_DROPLET_FLAG_SET_Y_TO_WATER_LEVEL) {
    505         newObj->oPosY = find_water_level(newObj->oPosX, newObj->oPosZ);
    506     }
    507 
    508     if (params->flags & WATER_DROPLET_FLAG_RAND_OFFSET_XZ) {
    509         obj_translate_xz_random(newObj, params->moveRange);
    510     }
    511 
    512     if (params->flags & WATER_DROPLET_FLAG_RAND_OFFSET_XYZ) {
    513         obj_translate_xyz_random(newObj, params->moveRange);
    514     }
    515 
    516     newObj->oForwardVel = random_float() * params->randForwardVelScale + params->randForwardVelOffset;
    517     newObj->oVelY = random_float() * params->randYVelScale + params->randYVelOffset;
    518 
    519     randomScale = random_float() * params->randSizeScale + params->randSizeOffset;
    520     obj_scale(newObj, randomScale);
    521 
    522     return newObj;
    523 }
    524 
    525 struct Object *spawn_object_at_origin(struct Object *parent, UNUSED s32 unusedArg, u32 model,
    526                                       const BehaviorScript *behavior) {
    527     struct Object *obj;
    528     const BehaviorScript *behaviorAddr;
    529 
    530     behaviorAddr = segmented_to_virtual(behavior);
    531     obj = create_object(behaviorAddr);
    532 
    533     obj->parentObj = parent;
    534     obj->header.gfx.areaIndex = parent->header.gfx.areaIndex;
    535     obj->header.gfx.activeAreaIndex = parent->header.gfx.areaIndex;
    536 
    537     geo_obj_init((struct GraphNodeObject *) &obj->header.gfx, gLoadedGraphNodes[model], gVec3fZero,
    538                  gVec3sZero);
    539 
    540     return obj;
    541 }
    542 
    543 struct Object *spawn_object(struct Object *parent, s32 model, const BehaviorScript *behavior) {
    544     struct Object *obj = spawn_object_at_origin(parent, 0, model, behavior);
    545 
    546     obj_copy_pos_and_angle(obj, parent);
    547 
    548     return obj;
    549 }
    550 
    551 struct Object *try_to_spawn_object(s16 offsetY, f32 scale, struct Object *parent, s32 model,
    552                                    const BehaviorScript *behavior) {
    553     struct Object *obj;
    554 
    555     if (gFreeObjectList.next != NULL) {
    556         obj = spawn_object(parent, model, behavior);
    557         obj->oPosY += offsetY;
    558         obj_scale(obj, scale);
    559         return obj;
    560     } else {
    561         return NULL;
    562     }
    563 }
    564 
    565 struct Object *spawn_object_with_scale(struct Object *parent, s32 model, const BehaviorScript *behavior, f32 scale) {
    566     struct Object *obj = spawn_object_at_origin(parent, 0, model, behavior);
    567 
    568     obj_copy_pos_and_angle(obj, parent);
    569     obj_scale(obj, scale);
    570 
    571     return obj;
    572 }
    573 
    574 static void obj_build_relative_transform(struct Object *obj) {
    575     obj_build_transform_from_pos_and_angle(obj, O_PARENT_RELATIVE_POS_INDEX, O_FACE_ANGLE_INDEX);
    576     obj_translate_local(obj, O_POS_INDEX, O_PARENT_RELATIVE_POS_INDEX);
    577 }
    578 
    579 struct Object *spawn_object_relative(s16 behaviorParam, s16 relativePosX, s16 relativePosY, s16 relativePosZ,
    580                                      struct Object *parent, s32 model, const BehaviorScript *behavior) {
    581     struct Object *obj = spawn_object_at_origin(parent, 0, model, behavior);
    582 
    583     obj_copy_pos_and_angle(obj, parent);
    584     obj_set_parent_relative_pos(obj, relativePosX, relativePosY, relativePosZ);
    585     obj_build_relative_transform(obj);
    586 
    587     obj->oBhvParams2ndByte = behaviorParam;
    588     obj->oBhvParams = (behaviorParam & 0xFF) << 16;
    589 
    590     return obj;
    591 }
    592 
    593 struct Object *spawn_object_relative_with_scale(s16 behaviorParam, s16 relativePosX, s16 relativePosY,
    594                                                 s16 relativePosZ, f32 scale, struct Object *parent,
    595                                                 s32 model, const BehaviorScript *behavior) {
    596     struct Object *obj = spawn_object_relative(behaviorParam, relativePosX, relativePosY, relativePosZ,
    597                                                parent, model, behavior);
    598     obj_scale(obj, scale);
    599 
    600     return obj;
    601 }
    602 
    603 void cur_obj_move_using_vel(void) {
    604     o->oPosX += o->oVelX;
    605     o->oPosY += o->oVelY;
    606     o->oPosZ += o->oVelZ;
    607 }
    608 
    609 void obj_copy_graph_y_offset(struct Object *dst, struct Object *src) {
    610     dst->oGraphYOffset = src->oGraphYOffset;
    611 }
    612 
    613 void obj_copy_pos_and_angle(struct Object *dst, struct Object *src) {
    614     obj_copy_pos(dst, src);
    615     obj_copy_angle(dst, src);
    616 }
    617 
    618 void obj_copy_pos(struct Object *dst, struct Object *src) {
    619     dst->oPosX = src->oPosX;
    620     dst->oPosY = src->oPosY;
    621     dst->oPosZ = src->oPosZ;
    622 }
    623 
    624 void obj_copy_angle(struct Object *dst, struct Object *src) {
    625     dst->oMoveAnglePitch = src->oMoveAnglePitch;
    626     dst->oMoveAngleYaw = src->oMoveAngleYaw;
    627     dst->oMoveAngleRoll = src->oMoveAngleRoll;
    628 
    629     dst->oFaceAnglePitch = src->oFaceAnglePitch;
    630     dst->oFaceAngleYaw = src->oFaceAngleYaw;
    631     dst->oFaceAngleRoll = src->oFaceAngleRoll;
    632 }
    633 
    634 void obj_set_gfx_pos_from_pos(struct Object *obj) {
    635     obj->header.gfx.pos[0] = obj->oPosX;
    636     obj->header.gfx.pos[1] = obj->oPosY;
    637     obj->header.gfx.pos[2] = obj->oPosZ;
    638 }
    639 
    640 void obj_init_animation(struct Object *obj, s32 animIndex) {
    641     struct Animation **anims = o->oAnimations;
    642     geo_obj_init_animation(&obj->header.gfx, &anims[animIndex]);
    643 }
    644 
    645 /**
    646  * Multiply a vector by a matrix of the form
    647  * | ? ? ? 0 |
    648  * | ? ? ? 0 |
    649  * | ? ? ? 0 |
    650  * | 0 0 0 1 |
    651  * i.e. a matrix representing a linear transformation over 3 space.
    652  */
    653 void linear_mtxf_mul_vec3f(Mat4 m, Vec3f dst, Vec3f v) {
    654     s32 i;
    655     for (i = 0; i < 3; i++) {
    656         dst[i] = m[0][i] * v[0] + m[1][i] * v[1] + m[2][i] * v[2];
    657     }
    658 }
    659 
    660 /**
    661  * Multiply a vector by the transpose of a matrix of the form
    662  * | ? ? ? 0 |
    663  * | ? ? ? 0 |
    664  * | ? ? ? 0 |
    665  * | 0 0 0 1 |
    666  * i.e. a matrix representing a linear transformation over 3 space.
    667  */
    668 void linear_mtxf_transpose_mul_vec3f(Mat4 m, Vec3f dst, Vec3f v) {
    669     s32 i;
    670     for (i = 0; i < 3; i++) {
    671         dst[i] = m[i][0] * v[0] + m[i][1] * v[1] + m[i][2] * v[2];
    672     }
    673 }
    674 
    675 void obj_apply_scale_to_transform(struct Object *obj) {
    676     f32 scaleX = obj->header.gfx.scale[0];
    677     f32 scaleY = obj->header.gfx.scale[1];
    678     f32 scaleZ = obj->header.gfx.scale[2];
    679 
    680     obj->transform[0][0] *= scaleX;
    681     obj->transform[0][1] *= scaleX;
    682     obj->transform[0][2] *= scaleX;
    683 
    684     obj->transform[1][0] *= scaleY;
    685     obj->transform[1][1] *= scaleY;
    686     obj->transform[1][2] *= scaleY;
    687 
    688     obj->transform[2][0] *= scaleZ;
    689     obj->transform[2][1] *= scaleZ;
    690     obj->transform[2][2] *= scaleZ;
    691 }
    692 
    693 void obj_copy_scale(struct Object *dst, struct Object *src) {
    694     dst->header.gfx.scale[0] = src->header.gfx.scale[0];
    695     dst->header.gfx.scale[1] = src->header.gfx.scale[1];
    696     dst->header.gfx.scale[2] = src->header.gfx.scale[2];
    697 }
    698 
    699 void obj_scale_xyz(struct Object *obj, f32 xScale, f32 yScale, f32 zScale) {
    700     obj->header.gfx.scale[0] = xScale;
    701     obj->header.gfx.scale[1] = yScale;
    702     obj->header.gfx.scale[2] = zScale;
    703 }
    704 
    705 void obj_scale(struct Object *obj, f32 scale) {
    706     obj->header.gfx.scale[0] = scale;
    707     obj->header.gfx.scale[1] = scale;
    708     obj->header.gfx.scale[2] = scale;
    709 }
    710 
    711 void cur_obj_scale(f32 scale) {
    712     o->header.gfx.scale[0] = scale;
    713     o->header.gfx.scale[1] = scale;
    714     o->header.gfx.scale[2] = scale;
    715 }
    716 
    717 void cur_obj_init_animation(s32 animIndex) {
    718     struct Animation **anims = o->oAnimations;
    719     geo_obj_init_animation(&o->header.gfx, &anims[animIndex]);
    720 }
    721 
    722 void cur_obj_init_animation_with_sound(s32 animIndex) {
    723     struct Animation **anims = o->oAnimations;
    724     geo_obj_init_animation(&o->header.gfx, &anims[animIndex]);
    725     o->oSoundStateID = animIndex;
    726 }
    727 
    728 void cur_obj_init_animation_with_accel_and_sound(s32 animIndex, f32 accel) {
    729     struct Animation **anims = o->oAnimations;
    730     s32 animAccel = (s32)(accel * 65536.0f);
    731     geo_obj_init_animation_accel(&o->header.gfx, &anims[animIndex], animAccel);
    732     o->oSoundStateID = animIndex;
    733 }
    734 
    735 void obj_init_animation_with_sound(struct Object *obj, const struct Animation * const* animations, s32 animIndex) {
    736     struct Animation **anims = (struct Animation **) animations;
    737     obj->oAnimations = (struct Animation **) animations;
    738     geo_obj_init_animation(&obj->header.gfx, &anims[animIndex]);
    739     obj->oSoundStateID = animIndex;
    740 }
    741 
    742 void cur_obj_enable_rendering_and_become_tangible(struct Object *obj) {
    743     obj->header.gfx.node.flags |= GRAPH_RENDER_ACTIVE;
    744     obj->oIntangibleTimer = 0;
    745 }
    746 
    747 void cur_obj_enable_rendering(void) {
    748     o->header.gfx.node.flags |= GRAPH_RENDER_ACTIVE;
    749 }
    750 
    751 void cur_obj_disable_rendering_and_become_intangible(struct Object *obj) {
    752     obj->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE;
    753     obj->oIntangibleTimer = -1;
    754 }
    755 
    756 void cur_obj_disable_rendering(void) {
    757     o->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE;
    758 }
    759 
    760 void cur_obj_unhide(void) {
    761     o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
    762 }
    763 
    764 void cur_obj_hide(void) {
    765     o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
    766 }
    767 
    768 void cur_obj_set_pos_relative(struct Object *other, f32 dleft, f32 dy, f32 dforward) {
    769     f32 facingZ = coss(other->oMoveAngleYaw);
    770     f32 facingX = sins(other->oMoveAngleYaw);
    771 
    772     f32 dz = dforward * facingZ - dleft * facingX;
    773     f32 dx = dforward * facingX + dleft * facingZ;
    774 
    775     o->oMoveAngleYaw = other->oMoveAngleYaw;
    776 
    777     o->oPosX = other->oPosX + dx;
    778     o->oPosY = other->oPosY + dy;
    779     o->oPosZ = other->oPosZ + dz;
    780 }
    781 
    782 void cur_obj_set_pos_relative_to_parent(f32 dleft, f32 dy, f32 dforward) {
    783     cur_obj_set_pos_relative(o->parentObj, dleft, dy, dforward);
    784 }
    785 
    786 void cur_obj_enable_rendering_2(void) {
    787     cur_obj_enable_rendering();
    788 }
    789 
    790 void cur_obj_unused_init_on_floor(void) {
    791     cur_obj_enable_rendering();
    792 
    793     o->oPosY = find_floor_height(o->oPosX, o->oPosY, o->oPosZ);
    794     if (o->oPosY < FLOOR_LOWER_LIMIT_MISC) {
    795         cur_obj_set_pos_relative_to_parent(0, 0, -70);
    796         o->oPosY = find_floor_height(o->oPosX, o->oPosY, o->oPosZ);
    797     }
    798 }
    799 
    800 void obj_set_face_angle_to_move_angle(struct Object *obj) {
    801     obj->oFaceAnglePitch = obj->oMoveAnglePitch;
    802     obj->oFaceAngleYaw = obj->oMoveAngleYaw;
    803     obj->oFaceAngleRoll = obj->oMoveAngleRoll;
    804 }
    805 
    806 u32 get_object_list_from_behavior(const BehaviorScript *behavior) {
    807     u32 objectList;
    808 
    809     // If the first behavior command is "begin", then get the object list header
    810     // from there
    811     if ((behavior[0] >> 24) == 0) {
    812         objectList = (behavior[0] >> 16) & 0xFFFF;
    813     } else {
    814         objectList = OBJ_LIST_DEFAULT;
    815     }
    816 
    817     return objectList;
    818 }
    819 
    820 struct Object *cur_obj_nearest_object_with_behavior(const BehaviorScript *behavior) {
    821     struct Object *obj;
    822     f32 dist;
    823 
    824     obj = cur_obj_find_nearest_object_with_behavior(behavior, &dist);
    825 
    826     return obj;
    827 }
    828 
    829 f32 cur_obj_dist_to_nearest_object_with_behavior(const BehaviorScript *behavior) {
    830     struct Object *obj;
    831     f32 dist;
    832 
    833     obj = cur_obj_find_nearest_object_with_behavior(behavior, &dist);
    834     if (obj == NULL) {
    835         dist = 15000.0f;
    836     }
    837 
    838     return dist;
    839 }
    840 
    841 struct Object *cur_obj_find_nearest_object_with_behavior(const BehaviorScript *behavior, f32 *dist) {
    842     uintptr_t *behaviorAddr = segmented_to_virtual(behavior);
    843     struct Object *closestObj = NULL;
    844     struct Object *obj;
    845     struct ObjectNode *listHead;
    846     f32 minDist = 0x20000;
    847 
    848     listHead = &gObjectLists[get_object_list_from_behavior(behaviorAddr)];
    849     obj = (struct Object *) listHead->next;
    850 
    851     while (obj != (struct Object *) listHead) {
    852         if (obj->behavior == behaviorAddr) {
    853             if (obj->activeFlags != ACTIVE_FLAG_DEACTIVATED && obj != o) {
    854                 f32 objDist = dist_between_objects(o, obj);
    855                 if (objDist < minDist) {
    856                     closestObj = obj;
    857                     minDist = objDist;
    858                 }
    859             }
    860         }
    861         obj = (struct Object *) obj->header.next;
    862     }
    863 
    864     *dist = minDist;
    865     return closestObj;
    866 }
    867 
    868 struct Object *find_unimportant_object(void) {
    869     struct ObjectNode *listHead = &gObjectLists[OBJ_LIST_UNIMPORTANT];
    870     struct ObjectNode *obj = listHead->next;
    871 
    872     if (listHead == obj) {
    873         obj = NULL;
    874     }
    875 
    876     return (struct Object *) obj;
    877 }
    878 
    879 s32 count_unimportant_objects(void) {
    880     struct ObjectNode *listHead = &gObjectLists[OBJ_LIST_UNIMPORTANT];
    881     struct ObjectNode *obj = listHead->next;
    882     s32 count = 0;
    883 
    884     while (listHead != obj) {
    885         count++;
    886         obj = obj->next;
    887     }
    888 
    889     return count;
    890 }
    891 
    892 s32 count_objects_with_behavior(const BehaviorScript *behavior) {
    893     uintptr_t *behaviorAddr = segmented_to_virtual(behavior);
    894     struct ObjectNode *listHead = &gObjectLists[get_object_list_from_behavior(behaviorAddr)];
    895     struct ObjectNode *obj = listHead->next;
    896     s32 count = 0;
    897 
    898     while (listHead != obj) {
    899         if (((struct Object *) obj)->behavior == behaviorAddr) {
    900             count++;
    901         }
    902 
    903         obj = obj->next;
    904     }
    905 
    906     return count;
    907 }
    908 
    909 struct Object *cur_obj_find_nearby_held_actor(const BehaviorScript *behavior, f32 maxDist) {
    910     const BehaviorScript *behaviorAddr = segmented_to_virtual(behavior);
    911     struct ObjectNode *listHead = &gObjectLists[OBJ_LIST_GENACTOR];
    912     struct Object *obj = (struct Object *) listHead->next;
    913     struct Object *foundObj = NULL;
    914 
    915     while ((struct Object *) listHead != obj) {
    916         if (obj->behavior == behaviorAddr) {
    917             if (obj->activeFlags != ACTIVE_FLAG_DEACTIVATED) {
    918                 // This includes the dropped and thrown states. By combining instant
    919                 // release, this allows us to activate mama penguin remotely
    920                 if (obj->oHeldState != HELD_FREE) {
    921                     if (dist_between_objects(o, obj) < maxDist) {
    922                         foundObj = obj;
    923                         break;
    924                     }
    925                 }
    926             }
    927         }
    928 
    929         obj = (struct Object *) obj->header.next;
    930     }
    931 
    932     return foundObj;
    933 }
    934 
    935 static void cur_obj_reset_timer_and_subaction(void) {
    936     o->oTimer = 0;
    937     o->oSubAction = 0;
    938 }
    939 
    940 void cur_obj_change_action(s32 action) {
    941     o->oAction = action;
    942     o->oPrevAction = action;
    943     cur_obj_reset_timer_and_subaction();
    944 }
    945 
    946 void cur_obj_set_vel_from_mario_vel(f32 objBaseForwardVel, f32 multiplier) {
    947     f32 marioForwardVel = gMarioStates[0].forwardVel;
    948     f32 objForwardVel = objBaseForwardVel * multiplier;
    949 
    950     if (marioForwardVel < objForwardVel) {
    951         o->oForwardVel = objForwardVel;
    952     } else {
    953         o->oForwardVel = marioForwardVel * multiplier;
    954     }
    955 }
    956 
    957 BAD_RETURN(s16) cur_obj_reverse_animation(void) {
    958     if (o->header.gfx.animInfo.animFrame >= 0) {
    959         o->header.gfx.animInfo.animFrame--;
    960     }
    961 }
    962 
    963 BAD_RETURN(s32) cur_obj_extend_animation_if_at_end(void) {
    964     s32 animFrame = o->header.gfx.animInfo.animFrame;
    965     s32 sp0 = o->header.gfx.animInfo.curAnim->loopEnd - 2;
    966 
    967     if (animFrame == sp0) {
    968         o->header.gfx.animInfo.animFrame--;
    969     }
    970 }
    971 
    972 s32 cur_obj_check_if_near_animation_end(void) {
    973     u32 animFlags = (s32) o->header.gfx.animInfo.curAnim->flags;
    974     s32 animFrame = o->header.gfx.animInfo.animFrame;
    975     s32 nearLoopEnd = o->header.gfx.animInfo.curAnim->loopEnd - 2;
    976     s32 isNearEnd = FALSE;
    977 
    978     if (animFlags & ANIM_FLAG_NOLOOP && nearLoopEnd + 1 == animFrame) {
    979         isNearEnd = TRUE;
    980     }
    981 
    982     if (animFrame == nearLoopEnd) {
    983         isNearEnd = TRUE;
    984     }
    985 
    986     return isNearEnd;
    987 }
    988 
    989 s32 cur_obj_check_if_at_animation_end(void) {
    990     s32 animFrame = o->header.gfx.animInfo.animFrame;
    991     s32 lastFrame = o->header.gfx.animInfo.curAnim->loopEnd - 1;
    992 
    993     if (animFrame == lastFrame) {
    994         return TRUE;
    995     } else {
    996         return FALSE;
    997     }
    998 }
    999 
   1000 s32 cur_obj_check_anim_frame(s32 frame) {
   1001     s32 animFrame = o->header.gfx.animInfo.animFrame;
   1002 
   1003     if (animFrame == frame) {
   1004         return TRUE;
   1005     } else {
   1006         return FALSE;
   1007     }
   1008 }
   1009 
   1010 s32 cur_obj_check_anim_frame_in_range(s32 startFrame, s32 rangeLength) {
   1011     s32 animFrame = o->header.gfx.animInfo.animFrame;
   1012 
   1013     if (animFrame >= startFrame && animFrame < startFrame + rangeLength) {
   1014         return TRUE;
   1015     } else {
   1016         return FALSE;
   1017     }
   1018 }
   1019 
   1020 s32 cur_obj_check_frame_prior_current_frame(s16 *a0) {
   1021     s16 sp6 = o->header.gfx.animInfo.animFrame;
   1022 
   1023     while (*a0 != -1) {
   1024         if (*a0 == sp6) {
   1025             return TRUE;
   1026         }
   1027 
   1028         a0++;
   1029     }
   1030 
   1031     return FALSE;
   1032 }
   1033 
   1034 s32 mario_is_in_air_action(void) {
   1035     if (gMarioStates[0].action & ACT_FLAG_AIR) {
   1036         return TRUE;
   1037     } else {
   1038         return FALSE;
   1039     }
   1040 }
   1041 
   1042 s32 mario_is_dive_sliding(void) {
   1043     if (gMarioStates[0].action == ACT_DIVE_SLIDE) {
   1044         return TRUE;
   1045     } else {
   1046         return FALSE;
   1047     }
   1048 }
   1049 
   1050 void cur_obj_set_y_vel_and_animation(f32 yVel, s32 animIndex) {
   1051     o->oVelY = yVel;
   1052     cur_obj_init_animation_with_sound(animIndex);
   1053 }
   1054 
   1055 void cur_obj_unrender_set_action_and_anim(s32 animIndex, s32 action) {
   1056     cur_obj_become_intangible();
   1057     cur_obj_disable_rendering();
   1058 
   1059     // only set animation if non-negative value
   1060     if (animIndex >= 0) {
   1061         cur_obj_init_animation_with_sound(animIndex);
   1062     }
   1063 
   1064     o->oAction = action;
   1065 }
   1066 
   1067 static void cur_obj_move_after_thrown_or_dropped(f32 forwardVel, f32 velY) {
   1068     o->oMoveFlags = 0;
   1069     o->oFloorHeight = find_floor_height(o->oPosX, o->oPosY + 160.0f, o->oPosZ);
   1070 
   1071     if (o->oFloorHeight > o->oPosY) {
   1072         o->oPosY = o->oFloorHeight;
   1073     } else if (o->oFloorHeight < FLOOR_LOWER_LIMIT_MISC) {
   1074         //! OoB failsafe
   1075         obj_copy_pos(o, gMarioObject);
   1076         o->oFloorHeight = find_floor_height(o->oPosX, o->oPosY, o->oPosZ);
   1077     }
   1078 
   1079     o->oForwardVel = forwardVel;
   1080     o->oVelY = velY;
   1081 
   1082     if (o->oForwardVel != 0) {
   1083         cur_obj_move_y(/*gravity*/ -4.0f, /*bounciness*/ -0.1f, /*buoyancy*/ 2.0f);
   1084     }
   1085 }
   1086 
   1087 void cur_obj_get_thrown_or_placed(f32 forwardVel, f32 velY, s32 thrownAction) {
   1088     if (o->behavior == segmented_to_virtual(bhvBowser)) {
   1089         // Interestingly, when bowser is thrown, he is offset slightly to
   1090         // Mario's right
   1091         cur_obj_set_pos_relative_to_parent(-41.684f, 85.859f, 321.577f);
   1092     } else {
   1093     }
   1094 
   1095     cur_obj_become_tangible();
   1096     cur_obj_enable_rendering();
   1097 
   1098     o->oHeldState = HELD_FREE;
   1099 
   1100     if ((o->oInteractionSubtype & INT_SUBTYPE_HOLDABLE_NPC) || forwardVel == 0.0f) {
   1101         cur_obj_move_after_thrown_or_dropped(0.0f, 0.0f);
   1102     } else {
   1103         o->oAction = thrownAction;
   1104         cur_obj_move_after_thrown_or_dropped(forwardVel, velY);
   1105     }
   1106 }
   1107 
   1108 void cur_obj_get_dropped(void) {
   1109     cur_obj_become_tangible();
   1110     cur_obj_enable_rendering();
   1111 
   1112     o->oHeldState = HELD_FREE;
   1113     cur_obj_move_after_thrown_or_dropped(0.0f, 0.0f);
   1114 }
   1115 
   1116 void cur_obj_set_model(s32 modelID) {
   1117     o->header.gfx.sharedChild = gLoadedGraphNodes[modelID];
   1118 }
   1119 
   1120 void mario_set_flag(s32 flag) {
   1121     gMarioStates[0].flags |= flag;
   1122 }
   1123 
   1124 s32 cur_obj_clear_interact_status_flag(s32 flag) {
   1125     if (o->oInteractStatus & flag) {
   1126         o->oInteractStatus &= flag ^ 0xFFFFFFFF;
   1127         return TRUE;
   1128     }
   1129     return FALSE;
   1130 }
   1131 
   1132 /**
   1133  * Mark an object to be unloaded at the end of the frame.
   1134  */
   1135 void obj_mark_for_deletion(struct Object *obj) {
   1136     //! This clears all activeFlags. Since some of these flags disable behavior,
   1137     //  setting it to 0 could potentially enable unexpected behavior. After an
   1138     //  object is marked for deletion, it still updates on that frame (I think),
   1139     //  so this is worth looking into.
   1140     obj->activeFlags = ACTIVE_FLAG_DEACTIVATED;
   1141 }
   1142 
   1143 void cur_obj_disable(void) {
   1144     cur_obj_disable_rendering();
   1145     cur_obj_hide();
   1146     cur_obj_become_intangible();
   1147 }
   1148 
   1149 void cur_obj_become_intangible(void) {
   1150     // When the timer is negative, the object is intangible and the timer
   1151     // doesn't count down
   1152     o->oIntangibleTimer = -1;
   1153 }
   1154 
   1155 void cur_obj_become_tangible(void) {
   1156     o->oIntangibleTimer = 0;
   1157 }
   1158 
   1159 void obj_become_tangible(struct Object *obj) {
   1160     obj->oIntangibleTimer = 0;
   1161 }
   1162 
   1163 void cur_obj_update_floor_height(void) {
   1164     struct Surface *floor;
   1165     o->oFloorHeight = find_floor(o->oPosX, o->oPosY, o->oPosZ, &floor);
   1166 }
   1167 
   1168 struct Surface *cur_obj_update_floor_height_and_get_floor(void) {
   1169     struct Surface *floor;
   1170     o->oFloorHeight = find_floor(o->oPosX, o->oPosY, o->oPosZ, &floor);
   1171     return floor;
   1172 }
   1173 
   1174 static void apply_drag_to_value(f32 *value, f32 dragStrength) {
   1175     f32 decel;
   1176 
   1177     if (*value != 0) {
   1178         //! Can overshoot if |*value| > 1/(dragStrength * 0.0001)
   1179         decel = (*value) * (*value) * (dragStrength * 0.0001L);
   1180 
   1181         if (*value > 0) {
   1182             *value -= decel;
   1183             if (*value < 0.001L) {
   1184                 *value = 0;
   1185             }
   1186         } else {
   1187             *value += decel;
   1188             if (*value > -0.001L) {
   1189                 *value = 0;
   1190             }
   1191         }
   1192     }
   1193 }
   1194 
   1195 void cur_obj_apply_drag_xz(f32 dragStrength) {
   1196     apply_drag_to_value(&o->oVelX, dragStrength);
   1197     apply_drag_to_value(&o->oVelZ, dragStrength);
   1198 }
   1199 
   1200 static s32 cur_obj_move_xz(f32 steepSlopeNormalY, s32 careAboutEdgesAndSteepSlopes) {
   1201     struct Surface *intendedFloor;
   1202 
   1203     f32 intendedX = o->oPosX + o->oVelX;
   1204     f32 intendedZ = o->oPosZ + o->oVelZ;
   1205 
   1206     f32 intendedFloorHeight = find_floor(intendedX, o->oPosY, intendedZ, &intendedFloor);
   1207     f32 deltaFloorHeight = intendedFloorHeight - o->oFloorHeight;
   1208 
   1209     UNUSED u8 filler[4];
   1210     UNUSED f32 ny;
   1211 
   1212     o->oMoveFlags &= ~OBJ_MOVE_HIT_EDGE;
   1213 
   1214     if (o->oRoom != -1 && intendedFloor != NULL) {
   1215         if (intendedFloor->room != 0 && o->oRoom != intendedFloor->room && intendedFloor->room != 18) {
   1216             // Don't leave native room
   1217             return FALSE;
   1218         }
   1219     }
   1220 
   1221     if (intendedFloorHeight < FLOOR_LOWER_LIMIT_MISC) {
   1222         // Don't move into OoB
   1223         o->oMoveFlags |= OBJ_MOVE_HIT_EDGE;
   1224         return FALSE;
   1225     } else if (deltaFloorHeight < 5.0f) {
   1226         if (!careAboutEdgesAndSteepSlopes) {
   1227             // If we don't care about edges or steep slopes, okay to move
   1228             o->oPosX = intendedX;
   1229             o->oPosZ = intendedZ;
   1230             return TRUE;
   1231         } else if (deltaFloorHeight < -50.0f && (o->oMoveFlags & OBJ_MOVE_ON_GROUND)) {
   1232             // Don't walk off an edge
   1233             o->oMoveFlags |= OBJ_MOVE_HIT_EDGE;
   1234             return FALSE;
   1235         } else if (intendedFloor->normal.y > steepSlopeNormalY) {
   1236             // Allow movement onto a slope, provided it's not too steep
   1237             o->oPosX = intendedX;
   1238             o->oPosZ = intendedZ;
   1239             return TRUE;
   1240         } else {
   1241             // We are likely trying to move onto a steep downward slope
   1242             o->oMoveFlags |= OBJ_MOVE_HIT_EDGE;
   1243             return FALSE;
   1244         }
   1245     } else if ((ny = intendedFloor->normal.y) > steepSlopeNormalY || o->oPosY > intendedFloorHeight) {
   1246         // Allow movement upward, provided either:
   1247         // - The target floor is flat enough (e.g. walking up stairs)
   1248         // - We are above the target floor (most likely in the air)
   1249         o->oPosX = intendedX;
   1250         o->oPosZ = intendedZ;
   1251         //! Returning FALSE but moving anyway (not exploitable; return value is
   1252         //  never used)
   1253     }
   1254 
   1255     // We are likely trying to move onto a steep upward slope
   1256     return FALSE;
   1257 }
   1258 
   1259 static void cur_obj_move_update_underwater_flags(void) {
   1260     f32 decelY = (f32)(sqrtf(o->oVelY * o->oVelY) * (o->oDragStrength * 7.0f)) / 100.0L;
   1261 
   1262     if (o->oVelY > 0) {
   1263         o->oVelY -= decelY;
   1264     } else {
   1265         o->oVelY += decelY;
   1266     }
   1267 
   1268     if (o->oPosY < o->oFloorHeight) {
   1269         o->oPosY = o->oFloorHeight;
   1270         o->oMoveFlags |= OBJ_MOVE_UNDERWATER_ON_GROUND;
   1271     } else {
   1272         o->oMoveFlags |= OBJ_MOVE_UNDERWATER_OFF_GROUND;
   1273     }
   1274 }
   1275 
   1276 static void cur_obj_move_update_ground_air_flags(UNUSED f32 gravity, f32 bounciness) {
   1277     o->oMoveFlags &= ~OBJ_MOVE_BOUNCE;
   1278 
   1279     if (o->oPosY < o->oFloorHeight) {
   1280         // On the first frame that we touch the ground, set OBJ_MOVE_LANDED.
   1281         // On subsequent frames, set OBJ_MOVE_ON_GROUND
   1282         if (!(o->oMoveFlags & OBJ_MOVE_ON_GROUND)) {
   1283             if (clear_move_flag(&o->oMoveFlags, OBJ_MOVE_LANDED)) {
   1284                 o->oMoveFlags |= OBJ_MOVE_ON_GROUND;
   1285             } else {
   1286                 o->oMoveFlags |= OBJ_MOVE_LANDED;
   1287             }
   1288         }
   1289 
   1290         o->oPosY = o->oFloorHeight;
   1291 
   1292         if (o->oVelY < 0.0f) {
   1293             o->oVelY *= bounciness;
   1294         }
   1295 
   1296         if (o->oVelY > 5.0f) {
   1297             //! This overestimates since velY could be > 5 here
   1298             //! without bounce (e.g. jump into misa).
   1299             o->oMoveFlags |= OBJ_MOVE_BOUNCE;
   1300         }
   1301     } else {
   1302         o->oMoveFlags &= ~OBJ_MOVE_LANDED;
   1303         if (clear_move_flag(&o->oMoveFlags, OBJ_MOVE_ON_GROUND)) {
   1304             o->oMoveFlags |= OBJ_MOVE_LEFT_GROUND;
   1305         }
   1306     }
   1307 
   1308     o->oMoveFlags &= ~OBJ_MOVE_MASK_IN_WATER;
   1309 }
   1310 
   1311 static f32 cur_obj_move_y_and_get_water_level(f32 gravity, f32 buoyancy) {
   1312     f32 waterLevel;
   1313 
   1314     o->oVelY += gravity + buoyancy;
   1315     if (o->oVelY < -78.0f) {
   1316         o->oVelY = -78.0f;
   1317     }
   1318 
   1319     o->oPosY += o->oVelY;
   1320     if (o->activeFlags & ACTIVE_FLAG_UNK10) {
   1321         waterLevel = FLOOR_LOWER_LIMIT;
   1322     } else {
   1323         waterLevel = find_water_level(o->oPosX, o->oPosZ);
   1324     }
   1325 
   1326     return waterLevel;
   1327 }
   1328 
   1329 void cur_obj_move_y(f32 gravity, f32 bounciness, f32 buoyancy) {
   1330     f32 waterLevel;
   1331 
   1332     o->oMoveFlags &= ~OBJ_MOVE_LEFT_GROUND;
   1333 
   1334     if (o->oMoveFlags & OBJ_MOVE_AT_WATER_SURFACE) {
   1335         if (o->oVelY > 5.0f) {
   1336             o->oMoveFlags &= ~OBJ_MOVE_MASK_IN_WATER;
   1337             o->oMoveFlags |= OBJ_MOVE_LEAVING_WATER;
   1338         }
   1339     }
   1340 
   1341     if (!(o->oMoveFlags & OBJ_MOVE_MASK_IN_WATER)) {
   1342         waterLevel = cur_obj_move_y_and_get_water_level(gravity, 0.0f);
   1343         if (o->oPosY > waterLevel) {
   1344             //! We only handle floor collision if the object does not enter
   1345             //  water. This allows e.g. coins to clip through floors if they
   1346             //  enter water on the same frame.
   1347             cur_obj_move_update_ground_air_flags(gravity, bounciness);
   1348         } else {
   1349             o->oMoveFlags |= OBJ_MOVE_ENTERED_WATER;
   1350             o->oMoveFlags &= ~OBJ_MOVE_MASK_ON_GROUND;
   1351         }
   1352     } else {
   1353         o->oMoveFlags &= ~OBJ_MOVE_ENTERED_WATER;
   1354 
   1355         waterLevel = cur_obj_move_y_and_get_water_level(gravity, buoyancy);
   1356         if (o->oPosY < waterLevel) {
   1357             cur_obj_move_update_underwater_flags();
   1358         } else {
   1359             if (o->oPosY < o->oFloorHeight) {
   1360                 o->oPosY = o->oFloorHeight;
   1361                 o->oMoveFlags &= ~OBJ_MOVE_MASK_IN_WATER;
   1362             } else {
   1363                 o->oPosY = waterLevel;
   1364                 o->oVelY = 0.0f;
   1365                 o->oMoveFlags &= ~(OBJ_MOVE_UNDERWATER_OFF_GROUND | OBJ_MOVE_UNDERWATER_ON_GROUND);
   1366                 o->oMoveFlags |= OBJ_MOVE_AT_WATER_SURFACE;
   1367             }
   1368         }
   1369     }
   1370 
   1371     if (o->oMoveFlags & (OBJ_MOVE_MASK_ON_GROUND | OBJ_MOVE_AT_WATER_SURFACE
   1372         | OBJ_MOVE_UNDERWATER_OFF_GROUND)) {
   1373         o->oMoveFlags &= ~OBJ_MOVE_IN_AIR;
   1374     } else {
   1375         o->oMoveFlags |= OBJ_MOVE_IN_AIR;
   1376     }
   1377 }
   1378 
   1379 UNUSED static void stub_obj_helpers_1(void) {
   1380 }
   1381 
   1382 static s32 clear_move_flag(u32 *bitSet, s32 flag) {
   1383     if (*bitSet & flag) {
   1384         *bitSet &= flag ^ 0xFFFFFFFF;
   1385         return TRUE;
   1386     } else {
   1387         return FALSE;
   1388     }
   1389 }
   1390 
   1391 void cur_obj_unused_resolve_wall_collisions(f32 offsetY, f32 radius) {
   1392     if (radius > 0.1L) {
   1393         f32_find_wall_collision(&o->oPosX, &o->oPosY, &o->oPosZ, offsetY, radius);
   1394     }
   1395 }
   1396 
   1397 s16 abs_angle_diff(s16 x0, s16 x1) {
   1398     s16 diff = x1 - x0;
   1399 
   1400     if (diff == -0x8000) {
   1401         diff = -0x7FFF;
   1402     }
   1403 
   1404     if (diff < 0) {
   1405         diff = -diff;
   1406     }
   1407 
   1408     return diff;
   1409 }
   1410 
   1411 void cur_obj_move_xz_using_fvel_and_yaw(void) {
   1412     o->oVelX = o->oForwardVel * sins(o->oMoveAngleYaw);
   1413     o->oVelZ = o->oForwardVel * coss(o->oMoveAngleYaw);
   1414 
   1415     o->oPosX += o->oVelX;
   1416     o->oPosZ += o->oVelZ;
   1417 }
   1418 
   1419 void cur_obj_move_y_with_terminal_vel(void) {
   1420     if (o->oVelY < -70.0f) {
   1421         o->oVelY = -70.0f;
   1422     }
   1423 
   1424     o->oPosY += o->oVelY;
   1425 }
   1426 
   1427 void cur_obj_compute_vel_xz(void) {
   1428     o->oVelX = o->oForwardVel * sins(o->oMoveAngleYaw);
   1429     o->oVelZ = o->oForwardVel * coss(o->oMoveAngleYaw);
   1430 }
   1431 
   1432 f32 increment_velocity_toward_range(f32 value, f32 center, f32 zeroThreshold, f32 increment) {
   1433     f32 relative;
   1434     if ((relative = value - center) > 0) {
   1435         if (relative < zeroThreshold) {
   1436             return 0.0f;
   1437         } else {
   1438             return -increment;
   1439         }
   1440     } else {
   1441         if (relative > -zeroThreshold) {
   1442             return 0.0f;
   1443         } else {
   1444             return increment;
   1445         }
   1446     }
   1447 }
   1448 
   1449 s32 obj_check_if_collided_with_object(struct Object *obj1, struct Object *obj2) {
   1450     s32 i;
   1451     for (i = 0; i < obj1->numCollidedObjs; i++) {
   1452         if (obj1->collidedObjs[i] == obj2) {
   1453             return TRUE;
   1454         }
   1455     }
   1456 
   1457     return FALSE;
   1458 }
   1459 
   1460 void cur_obj_set_behavior(const BehaviorScript *behavior) {
   1461     o->behavior = segmented_to_virtual(behavior);
   1462 }
   1463 
   1464 void obj_set_behavior(struct Object *obj, const BehaviorScript *behavior) {
   1465     obj->behavior = segmented_to_virtual(behavior);
   1466 }
   1467 
   1468 s32 cur_obj_has_behavior(const BehaviorScript *behavior) {
   1469     if (o->behavior == segmented_to_virtual(behavior)) {
   1470         return TRUE;
   1471     } else {
   1472         return FALSE;
   1473     }
   1474 }
   1475 
   1476 s32 obj_has_behavior(struct Object *obj, const BehaviorScript *behavior) {
   1477     if (obj->behavior == segmented_to_virtual(behavior)) {
   1478         return TRUE;
   1479     } else {
   1480         return FALSE;
   1481     }
   1482 }
   1483 
   1484 f32 cur_obj_lateral_dist_from_mario_to_home(void) {
   1485     f32 dist;
   1486     f32 dx = o->oHomeX - gMarioObject->oPosX;
   1487     f32 dz = o->oHomeZ - gMarioObject->oPosZ;
   1488 
   1489     dist = sqrtf(dx * dx + dz * dz);
   1490     return dist;
   1491 }
   1492 
   1493 f32 cur_obj_lateral_dist_to_home(void) {
   1494     f32 dist;
   1495     f32 dx = o->oHomeX - o->oPosX;
   1496     f32 dz = o->oHomeZ - o->oPosZ;
   1497 
   1498     dist = sqrtf(dx * dx + dz * dz);
   1499     return dist;
   1500 }
   1501 
   1502 s32 cur_obj_outside_home_square(f32 halfLength) {
   1503     if (o->oHomeX - halfLength > o->oPosX) {
   1504         return TRUE;
   1505     }
   1506 
   1507     if (o->oHomeX + halfLength < o->oPosX) {
   1508         return TRUE;
   1509     }
   1510 
   1511     if (o->oHomeZ - halfLength > o->oPosZ) {
   1512         return TRUE;
   1513     }
   1514 
   1515     if (o->oHomeZ + halfLength < o->oPosZ) {
   1516         return TRUE;
   1517     }
   1518 
   1519     return FALSE;
   1520 }
   1521 
   1522 s32 cur_obj_outside_home_rectangle(f32 minX, f32 maxX, f32 minZ, f32 maxZ) {
   1523     if (o->oHomeX + minX > o->oPosX) {
   1524         return TRUE;
   1525     }
   1526 
   1527     if (o->oHomeX + maxX < o->oPosX) {
   1528         return TRUE;
   1529     }
   1530 
   1531     if (o->oHomeZ + minZ > o->oPosZ) {
   1532         return TRUE;
   1533     }
   1534 
   1535     if (o->oHomeZ + maxZ < o->oPosZ) {
   1536         return TRUE;
   1537     }
   1538 
   1539     return FALSE;
   1540 }
   1541 
   1542 void cur_obj_set_pos_to_home(void) {
   1543     o->oPosX = o->oHomeX;
   1544     o->oPosY = o->oHomeY;
   1545     o->oPosZ = o->oHomeZ;
   1546 }
   1547 
   1548 void cur_obj_set_pos_to_home_and_stop(void) {
   1549     cur_obj_set_pos_to_home();
   1550 
   1551     o->oForwardVel = 0.0f;
   1552     o->oVelY = 0.0f;
   1553 }
   1554 
   1555 void cur_obj_shake_y(f32 amount) {
   1556     //! Technically could cause a bit of drift, but not much
   1557     if (o->oTimer % 2 == 0) {
   1558         o->oPosY += amount;
   1559     } else {
   1560         o->oPosY -= amount;
   1561     }
   1562 }
   1563 
   1564 void cur_obj_start_cam_event(UNUSED struct Object *obj, s32 cameraEvent) {
   1565     gPlayerCameraState->cameraEvent = (s16) cameraEvent;
   1566     gSecondCameraFocus = o;
   1567 }
   1568 
   1569 // unused, self explanatory, maybe oInteractStatus originally had TRUE/FALSE statements
   1570 void set_mario_interact_true_if_in_range(UNUSED s32 arg0, UNUSED s32 arg1, f32 range) {
   1571     if (o->oDistanceToMario < range) {
   1572         gMarioObject->oInteractStatus = TRUE;
   1573     }
   1574 }
   1575 
   1576 void obj_set_billboard(struct Object *obj) {
   1577     obj->header.gfx.node.flags |= GRAPH_RENDER_BILLBOARD;
   1578 }
   1579 
   1580 void cur_obj_set_hitbox_radius_and_height(f32 radius, f32 height) {
   1581     o->hitboxRadius = radius;
   1582     o->hitboxHeight = height;
   1583 }
   1584 
   1585 void cur_obj_set_hurtbox_radius_and_height(f32 radius, f32 height) {
   1586     o->hurtboxRadius = radius;
   1587     o->hurtboxHeight = height;
   1588 }
   1589 
   1590 static void obj_spawn_loot_coins(struct Object *obj, s32 numCoins, f32 baseVelY,
   1591                                     const BehaviorScript *coinBehavior,
   1592                                     s16 posJitter, s16 model) {
   1593     s32 i;
   1594     f32 spawnHeight;
   1595     struct Surface *floor;
   1596     struct Object *coin;
   1597 
   1598     spawnHeight = find_floor(obj->oPosX, obj->oPosY, obj->oPosZ, &floor);
   1599     if (obj->oPosY - spawnHeight > 100.0f) {
   1600         spawnHeight = obj->oPosY;
   1601     }
   1602 
   1603     for (i = 0; i < numCoins; i++) {
   1604         if (obj->oNumLootCoins <= 0) {
   1605             break;
   1606         }
   1607 
   1608         obj->oNumLootCoins--;
   1609 
   1610         coin = spawn_object(obj, model, coinBehavior);
   1611         obj_translate_xz_random(coin, posJitter);
   1612         coin->oPosY = spawnHeight;
   1613         coin->oCoinBaseVelY = baseVelY;
   1614     }
   1615 }
   1616 
   1617 void obj_spawn_loot_blue_coins(struct Object *obj, s32 numCoins, f32 baseVelY, s16 posJitter) {
   1618     obj_spawn_loot_coins(obj, numCoins, baseVelY, bhvBlueCoinJumping, posJitter, MODEL_BLUE_COIN);
   1619 }
   1620 
   1621 void obj_spawn_loot_yellow_coins(struct Object *obj, s32 numCoins, f32 baseVelY) {
   1622     obj_spawn_loot_coins(obj, numCoins, baseVelY, bhvSingleCoinGetsSpawned, 0, MODEL_YELLOW_COIN);
   1623 }
   1624 
   1625 void cur_obj_spawn_loot_coin_at_mario_pos(void) {
   1626     struct Object *coin;
   1627     if (o->oNumLootCoins <= 0) {
   1628         return;
   1629     }
   1630 
   1631     o->oNumLootCoins--;
   1632 
   1633     coin = spawn_object(o, MODEL_YELLOW_COIN, bhvSingleCoinGetsSpawned);
   1634     coin->oVelY = 30.0f;
   1635 
   1636     obj_copy_pos(coin, gMarioObject);
   1637 }
   1638 
   1639 f32 cur_obj_abs_y_dist_to_home(void) {
   1640     f32 dist = o->oHomeY - o->oPosY;
   1641 
   1642     if (dist < 0) {
   1643         dist = -dist;
   1644     }
   1645 
   1646     return dist;
   1647 }
   1648 
   1649 s32 cur_obj_advance_looping_anim(void) {
   1650     s32 animFrame = o->header.gfx.animInfo.animFrame;
   1651     s32 loopEnd = o->header.gfx.animInfo.curAnim->loopEnd;
   1652     s32 result;
   1653 
   1654     if (animFrame < 0) {
   1655         animFrame = 0;
   1656     } else if (loopEnd - 1 == animFrame) {
   1657         animFrame = 0;
   1658     } else {
   1659         animFrame++;
   1660     }
   1661 
   1662     result = (animFrame << 16) / loopEnd;
   1663 
   1664     return result;
   1665 }
   1666 
   1667 static s32 cur_obj_detect_steep_floor(s16 steepAngleDegrees) {
   1668     struct Surface *intendedFloor;
   1669     f32 intendedX, intendedFloorHeight, intendedZ;
   1670     f32 deltaFloorHeight;
   1671     f32 steepNormalY = coss((s16)(steepAngleDegrees * (0x10000 / 360)));
   1672 
   1673     if (o->oForwardVel != 0.0f) {
   1674         intendedX = o->oPosX + o->oVelX;
   1675         intendedZ = o->oPosZ + o->oVelZ;
   1676         intendedFloorHeight = find_floor(intendedX, o->oPosY, intendedZ, &intendedFloor);
   1677         deltaFloorHeight = intendedFloorHeight - o->oFloorHeight;
   1678 
   1679         if (intendedFloorHeight < FLOOR_LOWER_LIMIT_MISC) {
   1680             o->oWallAngle = o->oMoveAngleYaw + 0x8000;
   1681             return 2;
   1682         } else if (intendedFloor->normal.y < steepNormalY && deltaFloorHeight > 0
   1683                    && intendedFloorHeight > o->oPosY) {
   1684             o->oWallAngle = atan2s(intendedFloor->normal.z, intendedFloor->normal.x);
   1685             return 1;
   1686         } else {
   1687             return 0;
   1688         }
   1689     }
   1690 
   1691     return 0;
   1692 }
   1693 
   1694 s32 cur_obj_resolve_wall_collisions(void) {
   1695     s32 numCollisions;
   1696     struct Surface *wall;
   1697     struct WallCollisionData collisionData;
   1698 
   1699     f32 offsetY = 10.0f;
   1700     f32 radius = o->oWallHitboxRadius;
   1701 
   1702     if (radius > 0.1L) {
   1703         collisionData.offsetY = offsetY;
   1704         collisionData.radius = radius;
   1705         collisionData.x = (s16) o->oPosX;
   1706         collisionData.y = (s16) o->oPosY;
   1707         collisionData.z = (s16) o->oPosZ;
   1708 
   1709         numCollisions = find_wall_collisions(&collisionData);
   1710         if (numCollisions != 0) {
   1711             o->oPosX = collisionData.x;
   1712             o->oPosY = collisionData.y;
   1713             o->oPosZ = collisionData.z;
   1714             wall = collisionData.walls[collisionData.numWalls - 1];
   1715 
   1716             o->oWallAngle = atan2s(wall->normal.z, wall->normal.x);
   1717             if (abs_angle_diff(o->oWallAngle, o->oMoveAngleYaw) > 0x4000) {
   1718                 return TRUE;
   1719             } else {
   1720                 return FALSE;
   1721             }
   1722         }
   1723     }
   1724 
   1725     return FALSE;
   1726 }
   1727 
   1728 static void cur_obj_update_floor(void) {
   1729     struct Surface *floor = cur_obj_update_floor_height_and_get_floor();
   1730     o->oFloor = floor;
   1731 
   1732     if (floor != NULL) {
   1733         if (floor->type == SURFACE_BURNING) {
   1734             o->oMoveFlags |= OBJ_MOVE_ABOVE_LAVA;
   1735         }
   1736 #ifndef VERSION_JP
   1737         else if (floor->type == SURFACE_DEATH_PLANE) {
   1738             //! This misses SURFACE_VERTICAL_WIND (and maybe SURFACE_WARP)
   1739             o->oMoveFlags |= OBJ_MOVE_ABOVE_DEATH_BARRIER;
   1740         }
   1741 #endif
   1742 
   1743         o->oFloorType = floor->type;
   1744         o->oFloorRoom = floor->room;
   1745     } else {
   1746         o->oFloorType = 0;
   1747         o->oFloorRoom = 0;
   1748     }
   1749 }
   1750 
   1751 static void cur_obj_update_floor_and_resolve_wall_collisions(s16 steepSlopeDegrees) {
   1752 #ifdef VERSION_JP
   1753     o->oMoveFlags &= ~OBJ_MOVE_ABOVE_LAVA;
   1754 #else
   1755     o->oMoveFlags &= ~(OBJ_MOVE_ABOVE_LAVA | OBJ_MOVE_ABOVE_DEATH_BARRIER);
   1756 #endif
   1757 
   1758     if (o->activeFlags & (ACTIVE_FLAG_FAR_AWAY | ACTIVE_FLAG_IN_DIFFERENT_ROOM)) {
   1759         cur_obj_update_floor();
   1760         o->oMoveFlags &= ~(OBJ_MOVE_HIT_WALL | OBJ_MOVE_MASK_IN_WATER);
   1761 
   1762         if (o->oPosY > o->oFloorHeight) {
   1763             o->oMoveFlags |= OBJ_MOVE_IN_AIR;
   1764         }
   1765     } else {
   1766         o->oMoveFlags &= ~OBJ_MOVE_HIT_WALL;
   1767         if (cur_obj_resolve_wall_collisions()) {
   1768             o->oMoveFlags |= OBJ_MOVE_HIT_WALL;
   1769         }
   1770 
   1771         cur_obj_update_floor();
   1772 
   1773         if (o->oPosY > o->oFloorHeight) {
   1774             o->oMoveFlags |= OBJ_MOVE_IN_AIR;
   1775         }
   1776 
   1777         if (cur_obj_detect_steep_floor(steepSlopeDegrees)) {
   1778             o->oMoveFlags |= OBJ_MOVE_HIT_WALL;
   1779         }
   1780     }
   1781 }
   1782 
   1783 void cur_obj_update_floor_and_walls(void) {
   1784     cur_obj_update_floor_and_resolve_wall_collisions(60);
   1785 }
   1786 
   1787 void cur_obj_move_standard(s16 steepSlopeAngleDegrees) {
   1788     f32 gravity = o->oGravity;
   1789     f32 bounciness = o->oBounciness;
   1790     f32 buoyancy = o->oBuoyancy;
   1791     f32 dragStrength = o->oDragStrength;
   1792     f32 steepSlopeNormalY;
   1793     s32 careAboutEdgesAndSteepSlopes = FALSE;
   1794     s32 negativeSpeed = FALSE;
   1795 
   1796     //! Because some objects allow these active flags to be set but don't
   1797     //  avoid updating when they are, we end up with "partial" updates, where
   1798     //  an object's internal state will be updated, but it doesn't move.
   1799     //  This allows numerous glitches and is typically referred to as
   1800     //  deactivation (though this term has a different meaning in the code).
   1801     //  Objects that do this will be marked with //PARTIAL_UPDATE.
   1802     if (!(o->activeFlags & (ACTIVE_FLAG_FAR_AWAY | ACTIVE_FLAG_IN_DIFFERENT_ROOM))) {
   1803         if (steepSlopeAngleDegrees < 0) {
   1804             // clang-format off
   1805             careAboutEdgesAndSteepSlopes = TRUE; steepSlopeAngleDegrees = -steepSlopeAngleDegrees;
   1806             // clang-format on
   1807         }
   1808 
   1809         steepSlopeNormalY = coss(steepSlopeAngleDegrees * (0x10000 / 360));
   1810 
   1811         cur_obj_compute_vel_xz();
   1812         cur_obj_apply_drag_xz(dragStrength);
   1813 
   1814         cur_obj_move_xz(steepSlopeNormalY, careAboutEdgesAndSteepSlopes);
   1815         cur_obj_move_y(gravity, bounciness, buoyancy);
   1816 
   1817         if (o->oForwardVel < 0.0f) {
   1818             negativeSpeed = TRUE;
   1819         }
   1820         o->oForwardVel = sqrtf(sqr(o->oVelX) + sqr(o->oVelZ));
   1821         if (negativeSpeed == TRUE) {
   1822             o->oForwardVel = -o->oForwardVel;
   1823         }
   1824     }
   1825 }
   1826 
   1827 static s32 cur_obj_within_12k_bounds(void) {
   1828     if (o->oPosX < -12000.0f || 12000.0f < o->oPosX) {
   1829         return FALSE;
   1830     }
   1831 
   1832     if (o->oPosY < -12000.0f || 12000.0f < o->oPosY) {
   1833         return FALSE;
   1834     }
   1835 
   1836     if (o->oPosZ < -12000.0f || 12000.0f < o->oPosZ) {
   1837         return FALSE;
   1838     }
   1839 
   1840     return TRUE;
   1841 }
   1842 
   1843 void cur_obj_move_using_vel_and_gravity(void) {
   1844     if (cur_obj_within_12k_bounds()) {
   1845         o->oPosX += o->oVelX;
   1846         o->oPosZ += o->oVelZ;
   1847         o->oVelY += o->oGravity; //! No terminal velocity
   1848         o->oPosY += o->oVelY;
   1849     }
   1850 }
   1851 
   1852 void cur_obj_move_using_fvel_and_gravity(void) {
   1853     cur_obj_compute_vel_xz();
   1854     cur_obj_move_using_vel_and_gravity(); //! No terminal velocity
   1855 }
   1856 
   1857 void obj_set_pos_relative(struct Object *obj, struct Object *other, f32 dleft, f32 dy,
   1858                              f32 dforward) {
   1859     f32 facingZ = coss(other->oMoveAngleYaw);
   1860     f32 facingX = sins(other->oMoveAngleYaw);
   1861 
   1862     f32 dz = dforward * facingZ - dleft * facingX;
   1863     f32 dx = dforward * facingX + dleft * facingZ;
   1864 
   1865     obj->oMoveAngleYaw = other->oMoveAngleYaw;
   1866 
   1867     obj->oPosX = other->oPosX + dx;
   1868     obj->oPosY = other->oPosY + dy;
   1869     obj->oPosZ = other->oPosZ + dz;
   1870 }
   1871 
   1872 s16 cur_obj_angle_to_home(void) {
   1873     s16 angle;
   1874     f32 dx = o->oHomeX - o->oPosX;
   1875     f32 dz = o->oHomeZ - o->oPosZ;
   1876 
   1877     angle = atan2s(dz, dx);
   1878     return angle;
   1879 }
   1880 
   1881 void obj_set_gfx_pos_at_obj_pos(struct Object *obj1, struct Object *obj2) {
   1882     obj1->header.gfx.pos[0] = obj2->oPosX;
   1883     obj1->header.gfx.pos[1] = obj2->oPosY + obj2->oGraphYOffset;
   1884     obj1->header.gfx.pos[2] = obj2->oPosZ;
   1885 
   1886     obj1->header.gfx.angle[0] = obj2->oMoveAnglePitch & 0xFFFF;
   1887     obj1->header.gfx.angle[1] = obj2->oMoveAngleYaw & 0xFFFF;
   1888     obj1->header.gfx.angle[2] = obj2->oMoveAngleRoll & 0xFFFF;
   1889 }
   1890 
   1891 /**
   1892  * Transform the vector at localTranslateIndex into the object's local
   1893  * coordinates, and then add it to the vector at posIndex.
   1894  */
   1895 void obj_translate_local(struct Object *obj, s16 posIndex, s16 localTranslateIndex) {
   1896     f32 dx = obj->rawData.asF32[localTranslateIndex + 0];
   1897     f32 dy = obj->rawData.asF32[localTranslateIndex + 1];
   1898     f32 dz = obj->rawData.asF32[localTranslateIndex + 2];
   1899 
   1900     obj->rawData.asF32[posIndex + 0] +=
   1901         obj->transform[0][0] * dx + obj->transform[1][0] * dy + obj->transform[2][0] * dz;
   1902     obj->rawData.asF32[posIndex + 1] +=
   1903         obj->transform[0][1] * dx + obj->transform[1][1] * dy + obj->transform[2][1] * dz;
   1904     obj->rawData.asF32[posIndex + 2] +=
   1905         obj->transform[0][2] * dx + obj->transform[1][2] * dy + obj->transform[2][2] * dz;
   1906 }
   1907 
   1908 void obj_build_transform_from_pos_and_angle(struct Object *obj, s16 posIndex, s16 angleIndex) {
   1909     f32 translate[3];
   1910     s16 rotation[3];
   1911 
   1912     translate[0] = obj->rawData.asF32[posIndex + 0];
   1913     translate[1] = obj->rawData.asF32[posIndex + 1];
   1914     translate[2] = obj->rawData.asF32[posIndex + 2];
   1915 
   1916     rotation[0] = obj->rawData.asS32[angleIndex + 0];
   1917     rotation[1] = obj->rawData.asS32[angleIndex + 1];
   1918     rotation[2] = obj->rawData.asS32[angleIndex + 2];
   1919 
   1920     mtxf_rotate_zxy_and_translate(obj->transform, translate, rotation);
   1921 }
   1922 
   1923 void obj_set_throw_matrix_from_transform(struct Object *obj) {
   1924     if (obj->oFlags & OBJ_FLAG_0020) {
   1925         obj_build_transform_from_pos_and_angle(obj, O_POS_INDEX, O_FACE_ANGLE_INDEX);
   1926         obj_apply_scale_to_transform(obj);
   1927     }
   1928 
   1929     obj->header.gfx.throwMatrix = &obj->transform;
   1930 
   1931     //! Sets scale of gCurrentObject instead of obj. Not exploitable since this
   1932     //  function is only called with obj = gCurrentObject
   1933     cur_obj_scale(1.0f);
   1934 }
   1935 
   1936 void obj_build_transform_relative_to_parent(struct Object *obj) {
   1937     struct Object *parent = obj->parentObj;
   1938 
   1939     obj_build_transform_from_pos_and_angle(obj, O_PARENT_RELATIVE_POS_INDEX, O_FACE_ANGLE_INDEX);
   1940     obj_apply_scale_to_transform(obj);
   1941     mtxf_mul(obj->transform, obj->transform, parent->transform);
   1942 
   1943     obj->oPosX = obj->transform[3][0];
   1944     obj->oPosY = obj->transform[3][1];
   1945     obj->oPosZ = obj->transform[3][2];
   1946 
   1947     obj->header.gfx.throwMatrix = &obj->transform;
   1948 
   1949     //! Sets scale of gCurrentObject instead of obj. Not exploitable since this
   1950     //  function is only called with obj = gCurrentObject
   1951     cur_obj_scale(1.0f);
   1952 }
   1953 
   1954 void obj_create_transform_from_self(struct Object *obj) {
   1955     obj->oFlags &= ~OBJ_FLAG_TRANSFORM_RELATIVE_TO_PARENT;
   1956     obj->oFlags |= OBJ_FLAG_SET_THROW_MATRIX_FROM_TRANSFORM;
   1957 
   1958     obj->transform[3][0] = obj->oPosX;
   1959     obj->transform[3][1] = obj->oPosY;
   1960     obj->transform[3][2] = obj->oPosZ;
   1961 }
   1962 
   1963 void cur_obj_rotate_move_angle_using_vel(void) {
   1964     o->oMoveAnglePitch += o->oAngleVelPitch;
   1965     o->oMoveAngleYaw += o->oAngleVelYaw;
   1966     o->oMoveAngleRoll += o->oAngleVelRoll;
   1967 }
   1968 
   1969 void cur_obj_rotate_face_angle_using_vel(void) {
   1970     o->oFaceAnglePitch += o->oAngleVelPitch;
   1971     o->oFaceAngleYaw += o->oAngleVelYaw;
   1972     o->oFaceAngleRoll += o->oAngleVelRoll;
   1973 }
   1974 
   1975 void cur_obj_set_face_angle_to_move_angle(void) {
   1976     o->oFaceAnglePitch = o->oMoveAnglePitch;
   1977     o->oFaceAngleYaw = o->oMoveAngleYaw;
   1978     o->oFaceAngleRoll = o->oMoveAngleRoll;
   1979 }
   1980 
   1981 s32 cur_obj_follow_path(UNUSED s32 unusedArg) {
   1982     struct Waypoint *startWaypoint;
   1983     struct Waypoint *lastWaypoint;
   1984     struct Waypoint *targetWaypoint;
   1985     f32 prevToNextX, prevToNextY, prevToNextZ;
   1986     UNUSED u8 filler[4];
   1987     f32 objToNextXZ;
   1988     f32 objToNextX, objToNextY, objToNextZ;
   1989 
   1990     if (o->oPathedPrevWaypointFlags == 0) {
   1991         o->oPathedPrevWaypoint = o->oPathedStartWaypoint;
   1992         o->oPathedPrevWaypointFlags = WAYPOINT_FLAGS_INITIALIZED;
   1993     }
   1994 
   1995     startWaypoint = o->oPathedStartWaypoint;
   1996     lastWaypoint = o->oPathedPrevWaypoint;
   1997 
   1998     if ((lastWaypoint + 1)->flags != WAYPOINT_FLAGS_END) {
   1999         targetWaypoint = lastWaypoint + 1;
   2000     } else {
   2001         targetWaypoint = startWaypoint;
   2002     }
   2003 
   2004     o->oPathedPrevWaypointFlags = lastWaypoint->flags | WAYPOINT_FLAGS_INITIALIZED;
   2005 
   2006     prevToNextX = targetWaypoint->pos[0] - lastWaypoint->pos[0];
   2007     prevToNextY = targetWaypoint->pos[1] - lastWaypoint->pos[1];
   2008     prevToNextZ = targetWaypoint->pos[2] - lastWaypoint->pos[2];
   2009 
   2010     objToNextX = targetWaypoint->pos[0] - o->oPosX;
   2011     objToNextY = targetWaypoint->pos[1] - o->oPosY;
   2012     objToNextZ = targetWaypoint->pos[2] - o->oPosZ;
   2013     objToNextXZ = sqrtf(sqr(objToNextX) + sqr(objToNextZ));
   2014 
   2015     o->oPathedTargetYaw = atan2s(objToNextZ, objToNextX);
   2016     o->oPathedTargetPitch = atan2s(objToNextXZ, -objToNextY);
   2017 
   2018     // If dot(prevToNext, objToNext) <= 0 (i.e. reached other side of target waypoint)
   2019     if (prevToNextX * objToNextX + prevToNextY * objToNextY + prevToNextZ * objToNextZ <= 0.0f) {
   2020         o->oPathedPrevWaypoint = targetWaypoint;
   2021         if ((targetWaypoint + 1)->flags == WAYPOINT_FLAGS_END) {
   2022             return PATH_REACHED_END;
   2023         } else {
   2024             return PATH_REACHED_WAYPOINT;
   2025         }
   2026     }
   2027 
   2028     return PATH_NONE;
   2029 }
   2030 
   2031 void chain_segment_init(struct ChainSegment *segment) {
   2032     segment->posX = 0.0f;
   2033     segment->posY = 0.0f;
   2034     segment->posZ = 0.0f;
   2035 
   2036     segment->pitch = 0;
   2037     segment->yaw = 0;
   2038     segment->roll = 0;
   2039 }
   2040 
   2041 f32 random_f32_around_zero(f32 diameter) {
   2042     return random_float() * diameter - diameter / 2;
   2043 }
   2044 
   2045 void obj_scale_random(struct Object *obj, f32 rangeLength, f32 minScale) {
   2046     f32 scale = random_float() * rangeLength + minScale;
   2047     obj_scale_xyz(obj, scale, scale, scale);
   2048 }
   2049 
   2050 void obj_translate_xyz_random(struct Object *obj, f32 rangeLength) {
   2051     obj->oPosX += random_float() * rangeLength - rangeLength * 0.5f;
   2052     obj->oPosY += random_float() * rangeLength - rangeLength * 0.5f;
   2053     obj->oPosZ += random_float() * rangeLength - rangeLength * 0.5f;
   2054 }
   2055 
   2056 void obj_translate_xz_random(struct Object *obj, f32 rangeLength) {
   2057     obj->oPosX += random_float() * rangeLength - rangeLength * 0.5f;
   2058     obj->oPosZ += random_float() * rangeLength - rangeLength * 0.5f;
   2059 }
   2060 
   2061 static void obj_build_vel_from_transform(struct Object *obj) {
   2062     f32 up = obj->oUpVel;
   2063     f32 left = obj->oLeftVel;
   2064     f32 forward = obj->oForwardVel;
   2065 
   2066     //! Typo, up and left should be swapped
   2067     obj->oVelX = obj->transform[0][0] * up + obj->transform[1][0] * left + obj->transform[2][0] * forward;
   2068     obj->oVelY = obj->transform[0][1] * up + obj->transform[1][1] * left + obj->transform[2][1] * forward;
   2069     obj->oVelZ = obj->transform[0][2] * up + obj->transform[1][2] * left + obj->transform[2][2] * forward;
   2070 }
   2071 
   2072 void cur_obj_set_pos_via_transform(void) {
   2073     obj_build_transform_from_pos_and_angle(o, O_PARENT_RELATIVE_POS_INDEX, O_MOVE_ANGLE_INDEX);
   2074     obj_build_vel_from_transform(o);
   2075     o->oPosX += o->oVelX;
   2076     o->oPosY += o->oVelY;
   2077     o->oPosZ += o->oVelZ;
   2078 }
   2079 
   2080 s16 cur_obj_reflect_move_angle_off_wall(void) {
   2081     s16 angle = o->oWallAngle - ((s16) o->oMoveAngleYaw - (s16) o->oWallAngle) + 0x8000;
   2082     return angle;
   2083 }
   2084 
   2085 void cur_obj_spawn_particles(struct SpawnParticlesInfo *info) {
   2086     struct Object *particle;
   2087     s32 i;
   2088     f32 scale;
   2089     s32 numParticles = info->count;
   2090 
   2091     // If there are a lot of objects already, limit the number of particles
   2092     if ((gPrevFrameObjectCount > (OBJECT_POOL_CAPACITY - 90)) && numParticles > 10) {
   2093         numParticles = 10;
   2094     }
   2095 
   2096     // We're close to running out of object slots, so don't spawn particles at
   2097     // all
   2098     if (gPrevFrameObjectCount > (OBJECT_POOL_CAPACITY - 30)) {
   2099         numParticles = 0;
   2100     }
   2101 
   2102     for (i = 0; i < numParticles; i++) {
   2103         scale = random_float() * (info->sizeRange * 0.1f) + info->sizeBase * 0.1f;
   2104 
   2105         particle = spawn_object(o, info->model, bhvWhitePuffExplosion);
   2106 
   2107         particle->oBhvParams2ndByte = info->bhvParam;
   2108         particle->oMoveAngleYaw = random_u16();
   2109         particle->oGravity = info->gravity;
   2110         particle->oDragStrength = info->dragStrength;
   2111 
   2112         particle->oPosY += info->offsetY;
   2113         particle->oForwardVel = random_float() * info->forwardVelRange + info->forwardVelBase;
   2114         particle->oVelY = random_float() * info->velYRange + info->velYBase;
   2115 
   2116         obj_scale_xyz(particle, scale, scale, scale);
   2117     }
   2118 }
   2119 
   2120 void obj_set_hitbox(struct Object *obj, struct ObjectHitbox *hitbox) {
   2121     if (!(obj->oFlags & OBJ_FLAG_30)) {
   2122         obj->oFlags |= OBJ_FLAG_30;
   2123 
   2124         obj->oInteractType = hitbox->interactType;
   2125         obj->oDamageOrCoinValue = hitbox->damageOrCoinValue;
   2126         obj->oHealth = hitbox->health;
   2127         obj->oNumLootCoins = hitbox->numLootCoins;
   2128 
   2129         cur_obj_become_tangible();
   2130     }
   2131 
   2132     obj->hitboxRadius = obj->header.gfx.scale[0] * hitbox->radius;
   2133     obj->hitboxHeight = obj->header.gfx.scale[1] * hitbox->height;
   2134     obj->hurtboxRadius = obj->header.gfx.scale[0] * hitbox->hurtboxRadius;
   2135     obj->hurtboxHeight = obj->header.gfx.scale[1] * hitbox->hurtboxHeight;
   2136     obj->hitboxDownOffset = obj->header.gfx.scale[1] * hitbox->downOffset;
   2137 }
   2138 
   2139 s32 signum_positive(s32 x) {
   2140     if (x >= 0) {
   2141         return 1;
   2142     } else {
   2143         return -1;
   2144     }
   2145 }
   2146 
   2147 f32 absf(f32 x) {
   2148     if (x >= 0) {
   2149         return x;
   2150     } else {
   2151         return -x;
   2152     }
   2153 }
   2154 
   2155 s32 absi(s32 x) {
   2156     if (x >= 0) {
   2157         return x;
   2158     } else {
   2159         return -x;
   2160     }
   2161 }
   2162 
   2163 s32 cur_obj_wait_then_blink(s32 timeUntilBlinking, s32 numBlinks) {
   2164     s32 done = FALSE;
   2165     s32 timeBlinking;
   2166 
   2167     if (o->oTimer >= timeUntilBlinking) {
   2168         if ((timeBlinking = o->oTimer - timeUntilBlinking) % 2 != 0) {
   2169             o->header.gfx.node.flags |= GRAPH_RENDER_INVISIBLE;
   2170             if (timeBlinking / 2 > numBlinks) {
   2171                 done = TRUE;
   2172             }
   2173         } else {
   2174             o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
   2175         }
   2176     }
   2177 
   2178     return done;
   2179 }
   2180 
   2181 s32 cur_obj_is_mario_ground_pounding_platform(void) {
   2182     if (gMarioObject->platform == o) {
   2183         if (gMarioStates[0].action == ACT_GROUND_POUND_LAND) {
   2184             return TRUE;
   2185         }
   2186     }
   2187 
   2188     return FALSE;
   2189 }
   2190 
   2191 void spawn_mist_particles(void) {
   2192     spawn_mist_particles_variable(0, 0, 46.0f);
   2193 }
   2194 
   2195 void spawn_mist_particles_with_sound(u32 soundMagic) {
   2196     spawn_mist_particles_variable(0, 0, 46.0f);
   2197     create_sound_spawner(soundMagic);
   2198 }
   2199 
   2200 void cur_obj_push_mario_away(f32 radius) {
   2201     f32 marioRelX = gMarioObject->oPosX - o->oPosX;
   2202     f32 marioRelZ = gMarioObject->oPosZ - o->oPosZ;
   2203     f32 marioDist = sqrtf(sqr(marioRelX) + sqr(marioRelZ));
   2204 
   2205     if (marioDist < radius) {
   2206         //! If this function pushes Mario out of bounds, it will trigger Mario's
   2207         //  oob failsafe
   2208         gMarioStates[0].pos[0] += (radius - marioDist) / radius * marioRelX;
   2209         gMarioStates[0].pos[2] += (radius - marioDist) / radius * marioRelZ;
   2210     }
   2211 }
   2212 
   2213 void cur_obj_push_mario_away_from_cylinder(f32 radius, f32 extentY) {
   2214     f32 marioRelY = gMarioObject->oPosY - o->oPosY;
   2215 
   2216     if (marioRelY < 0.0f) {
   2217         marioRelY = -marioRelY;
   2218     }
   2219 
   2220     if (marioRelY < extentY) {
   2221         cur_obj_push_mario_away(radius);
   2222     }
   2223 }
   2224 
   2225 void bhv_dust_smoke_loop(void) {
   2226     o->oPosX += o->oVelX;
   2227     o->oPosY += o->oVelY;
   2228     o->oPosZ += o->oVelZ;
   2229 
   2230     if (o->oSmokeTimer == 10) {
   2231         obj_mark_for_deletion(o);
   2232     }
   2233 
   2234     o->oSmokeTimer++;
   2235 }
   2236 
   2237 UNUSED static void stub_obj_helpers_2(void) {
   2238 }
   2239 
   2240 s32 cur_obj_set_action_table(s8 *actionTable) {
   2241     o->oToxBoxActionTable = actionTable;
   2242     o->oToxBoxActionStep = 0;
   2243 
   2244     return *(s8 *) o->oToxBoxActionTable;
   2245 }
   2246 
   2247 s32 cur_obj_progress_action_table(void) {
   2248     s8 nextAction;
   2249     s8 *actionTable = o->oToxBoxActionTable;
   2250     s32 nextActionIndex = o->oToxBoxActionStep + 1;
   2251 
   2252     if (actionTable[nextActionIndex] != TOX_BOX_ACT_TABLE_END) {
   2253         nextAction = actionTable[nextActionIndex];
   2254         o->oToxBoxActionStep++;
   2255     } else {
   2256         nextAction = actionTable[0];
   2257         o->oToxBoxActionStep = 0;
   2258     }
   2259 
   2260     return nextAction;
   2261 }
   2262 
   2263 void stub_obj_helpers_3(UNUSED s32 arg0, UNUSED s32 arg1) {
   2264 }
   2265 
   2266 void cur_obj_scale_over_time(s32 a0, s32 a1, f32 sp10, f32 sp14) {
   2267     f32 sp4 = sp14 - sp10;
   2268     f32 sp0 = (f32) o->oTimer / a1;
   2269 
   2270     if (a0 & 0x01) {
   2271         o->header.gfx.scale[0] = sp4 * sp0 + sp10;
   2272     }
   2273 
   2274     if (a0 & 0x02) {
   2275         o->header.gfx.scale[1] = sp4 * sp0 + sp10;
   2276     }
   2277 
   2278     if (a0 & 0x04) {
   2279         o->header.gfx.scale[2] = sp4 * sp0 + sp10;
   2280     }
   2281 }
   2282 
   2283 void cur_obj_set_pos_to_home_with_debug(void) {
   2284     o->oPosX = o->oHomeX + gDebugInfo[DEBUG_PAGE_ENEMYINFO][0];
   2285     o->oPosY = o->oHomeY + gDebugInfo[DEBUG_PAGE_ENEMYINFO][1];
   2286     o->oPosZ = o->oHomeZ + gDebugInfo[DEBUG_PAGE_ENEMYINFO][2];
   2287     cur_obj_scale(gDebugInfo[DEBUG_PAGE_ENEMYINFO][3] / 100.0f + 1.0l);
   2288 }
   2289 
   2290 void stub_obj_helpers_4(void) {
   2291 }
   2292 
   2293 s32 cur_obj_is_mario_on_platform(void) {
   2294     if (gMarioObject->platform == o) {
   2295         return TRUE;
   2296     } else {
   2297         return FALSE;
   2298     }
   2299 }
   2300 
   2301 s32 cur_obj_shake_y_until(s32 cycles, s32 amount) {
   2302     if (o->oTimer % 2 != 0) {
   2303         o->oPosY -= amount;
   2304     } else {
   2305         o->oPosY += amount;
   2306     }
   2307 
   2308     if (o->oTimer == cycles * 2) {
   2309         return TRUE;
   2310     } else {
   2311         return FALSE;
   2312     }
   2313 }
   2314 
   2315 s32 jiggle_bbh_stair(s32 a0) {
   2316     if (a0 >= 4 || a0 < 0) {
   2317         return TRUE;
   2318     }
   2319 
   2320     o->oPosY += sBBHStairJiggleOffsets[a0];
   2321     return FALSE;
   2322 }
   2323 
   2324 void cur_obj_call_action_function(void (*actionFunctions[])(void)) {
   2325     void (*actionFunction)(void) = actionFunctions[o->oAction];
   2326     actionFunction();
   2327 }
   2328 
   2329 static struct Object *spawn_star_with_no_lvl_exit(s32 sp20, s32 sp24) {
   2330     struct Object *sp1C = spawn_object(o, MODEL_STAR, bhvSpawnedStarNoLevelExit);
   2331     sp1C->oSparkleSpawnUnk1B0 = sp24;
   2332     sp1C->oBhvParams = o->oBhvParams;
   2333     sp1C->oBhvParams2ndByte = sp20;
   2334 
   2335     return sp1C;
   2336 }
   2337 
   2338 // old unused initializer for 2d star spawn behavior.
   2339 // uses behavior parameters not used in the current sparkle code.
   2340 void spawn_base_star_with_no_lvl_exit(void) {
   2341     spawn_star_with_no_lvl_exit(0, 0);
   2342 }
   2343 
   2344 s32 bit_shift_left(s32 a0) {
   2345     return sPowersOfTwo[a0];
   2346 }
   2347 
   2348 s32 cur_obj_mario_far_away(void) {
   2349     f32 dx = o->oHomeX - gMarioObject->oPosX;
   2350     f32 dy = o->oHomeY - gMarioObject->oPosY;
   2351     f32 dz = o->oHomeZ - gMarioObject->oPosZ;
   2352     f32 marioDistToHome = sqrtf(dx * dx + dy * dy + dz * dz);
   2353 
   2354     if (o->oDistanceToMario > 2000.0f && marioDistToHome > 2000.0f) {
   2355         return TRUE;
   2356     } else {
   2357         return FALSE;
   2358     }
   2359 }
   2360 
   2361 s32 is_mario_moving_fast_or_in_air(s32 speedThreshold) {
   2362     if (gMarioStates[0].forwardVel > speedThreshold) {
   2363         return TRUE;
   2364     }
   2365 
   2366     if (gMarioStates[0].action & ACT_FLAG_AIR) {
   2367         return TRUE;
   2368     } else {
   2369         return FALSE;
   2370     }
   2371 }
   2372 
   2373 s32 is_item_in_array(s8 item, s8 *array) {
   2374     while (*array != -1) {
   2375         if (*array == item) {
   2376             return TRUE;
   2377         }
   2378 
   2379         array++;
   2380     }
   2381 
   2382     return FALSE;
   2383 }
   2384 
   2385 UNUSED static void stub_obj_helpers_5(void) {
   2386 }
   2387 
   2388 void bhv_init_room(void) {
   2389     struct Surface *floor;
   2390     f32 floorHeight;
   2391 
   2392     if (is_item_in_array(gCurrLevelNum, sLevelsWithRooms)) {
   2393         floorHeight = find_floor(o->oPosX, o->oPosY, o->oPosZ, &floor);
   2394 
   2395         if (floor != NULL) {
   2396             if (floor->room != 0) {
   2397                 o->oRoom = floor->room;
   2398             } else {
   2399                 // Floor probably belongs to a platform object. Try looking
   2400                 // underneath it
   2401                 find_floor(o->oPosX, floorHeight - 100.0f, o->oPosZ, &floor);
   2402                 if (floor != NULL) {
   2403                     //! Technically possible that the room could still be 0 here
   2404                     o->oRoom = floor->room;
   2405                 }
   2406             }
   2407         }
   2408     } else {
   2409         o->oRoom = -1;
   2410     }
   2411 }
   2412 
   2413 void cur_obj_enable_rendering_if_mario_in_room(void) {
   2414     register s32 marioInRoom;
   2415 
   2416     if (o->oRoom != -1 && gMarioCurrentRoom != 0) {
   2417         if (gMarioCurrentRoom == o->oRoom) {
   2418             marioInRoom = TRUE;
   2419         } else if (gDoorAdjacentRooms[gMarioCurrentRoom][0] == o->oRoom) {
   2420             marioInRoom = TRUE;
   2421         } else if (gDoorAdjacentRooms[gMarioCurrentRoom][1] == o->oRoom) {
   2422             marioInRoom = TRUE;
   2423         } else {
   2424             marioInRoom = FALSE;
   2425         }
   2426 
   2427         if (marioInRoom) {
   2428             cur_obj_enable_rendering();
   2429             o->activeFlags &= ~ACTIVE_FLAG_IN_DIFFERENT_ROOM;
   2430             gNumRoomedObjectsInMarioRoom++;
   2431         } else {
   2432             cur_obj_disable_rendering();
   2433             o->activeFlags |= ACTIVE_FLAG_IN_DIFFERENT_ROOM;
   2434             gNumRoomedObjectsNotInMarioRoom++;
   2435         }
   2436     }
   2437 }
   2438 
   2439 s32 cur_obj_set_hitbox_and_die_if_attacked(struct ObjectHitbox *hitbox, s32 deathSound, s32 noLootCoins) {
   2440     s32 interacted = FALSE;
   2441 
   2442     obj_set_hitbox(o, hitbox);
   2443 
   2444     if (noLootCoins) {
   2445         o->oNumLootCoins = 0;
   2446     }
   2447 
   2448     if (o->oInteractStatus & INT_STATUS_INTERACTED) {
   2449         if (o->oInteractStatus & INT_STATUS_WAS_ATTACKED) {
   2450             spawn_mist_particles();
   2451             obj_spawn_loot_yellow_coins(o, o->oNumLootCoins, 20.0f);
   2452             obj_mark_for_deletion(o);
   2453             create_sound_spawner(deathSound);
   2454         } else {
   2455             interacted = TRUE;
   2456         }
   2457     }
   2458 
   2459     o->oInteractStatus = 0;
   2460     return interacted;
   2461 }
   2462 
   2463 void obj_explode_and_spawn_coins(f32 mistParticleSize, s32 sp1C) {
   2464     spawn_mist_particles_variable(0, 0, mistParticleSize);
   2465     spawn_triangle_break_particles(30, MODEL_DIRT_ANIMATION, 3.0f, 4);
   2466     obj_mark_for_deletion(o);
   2467 
   2468     if (sp1C == 1) {
   2469         obj_spawn_loot_yellow_coins(o, o->oNumLootCoins, 20.0f);
   2470     } else if (sp1C == 2) {
   2471         obj_spawn_loot_blue_coins(o, o->oNumLootCoins, 20.0f, 150);
   2472     }
   2473 }
   2474 
   2475 void obj_set_collision_data(struct Object *obj, const void *segAddr) {
   2476     obj->collisionData = segmented_to_virtual(segAddr);
   2477 }
   2478 
   2479 void cur_obj_if_hit_wall_bounce_away(void) {
   2480     if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
   2481         o->oMoveAngleYaw = o->oWallAngle;
   2482     }
   2483 }
   2484 
   2485 s32 cur_obj_hide_if_mario_far_away_y(f32 distY) {
   2486     if (absf(o->oPosY - gMarioObject->oPosY) < distY) {
   2487         cur_obj_unhide();
   2488         return FALSE;
   2489     } else {
   2490         cur_obj_hide();
   2491         return TRUE;
   2492     }
   2493 }
   2494 
   2495 Gfx *geo_offset_klepto_held_object(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
   2496     if (callContext == GEO_CONTEXT_RENDER) {
   2497         ((struct GraphNodeTranslationRotation *) node->next)->translation[0] = 300;
   2498         ((struct GraphNodeTranslationRotation *) node->next)->translation[1] = 300;
   2499         ((struct GraphNodeTranslationRotation *) node->next)->translation[2] = 0;
   2500     }
   2501 
   2502     return NULL;
   2503 }
   2504 
   2505 Gfx *geo_offset_klepto_debug(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
   2506     if (callContext == GEO_CONTEXT_RENDER) {
   2507         ((struct GraphNodeTranslationRotation *) node->next)->translation[0] = gDebugInfo[DEBUG_PAGE_EFFECTINFO][0];
   2508         ((struct GraphNodeTranslationRotation *) node->next)->translation[1] = gDebugInfo[DEBUG_PAGE_EFFECTINFO][1];
   2509         ((struct GraphNodeTranslationRotation *) node->next)->translation[2] = gDebugInfo[DEBUG_PAGE_EFFECTINFO][2];
   2510         ((struct GraphNodeTranslationRotation *) node->next)->rotation[0]    = gDebugInfo[DEBUG_PAGE_EFFECTINFO][3];
   2511         ((struct GraphNodeTranslationRotation *) node->next)->rotation[1]    = gDebugInfo[DEBUG_PAGE_EFFECTINFO][4];
   2512         ((struct GraphNodeTranslationRotation *) node->next)->rotation[2]    = gDebugInfo[DEBUG_PAGE_EFFECTINFO][5];
   2513     }
   2514 
   2515     return NULL;
   2516 }
   2517 
   2518 s32 obj_is_hidden(struct Object *obj) {
   2519     if (obj->header.gfx.node.flags & GRAPH_RENDER_INVISIBLE) {
   2520         return TRUE;
   2521     } else {
   2522         return FALSE;
   2523     }
   2524 }
   2525 
   2526 void enable_time_stop(void) {
   2527     gTimeStopState |= TIME_STOP_ENABLED;
   2528 }
   2529 
   2530 void disable_time_stop(void) {
   2531     gTimeStopState &= ~TIME_STOP_ENABLED;
   2532 }
   2533 
   2534 void set_time_stop_flags(s32 flags) {
   2535     gTimeStopState |= flags;
   2536 }
   2537 
   2538 void clear_time_stop_flags(s32 flags) {
   2539     gTimeStopState = gTimeStopState & (flags ^ 0xFFFFFFFF);
   2540 }
   2541 
   2542 s32 cur_obj_can_mario_activate_textbox(f32 radius, f32 height, UNUSED s32 unused) {
   2543     if (o->oDistanceToMario < 1500.0f) {
   2544         f32 latDistToMario = lateral_dist_between_objects(o, gMarioObject);
   2545         UNUSED s16 angleFromMario = obj_angle_to_object(gMarioObject, o);
   2546 
   2547         if (latDistToMario < radius && o->oPosY < gMarioObject->oPosY + 160.0f
   2548             && gMarioObject->oPosY < o->oPosY + height && !(gMarioStates[0].action & ACT_FLAG_AIR)
   2549             && mario_ready_to_speak()) {
   2550             return TRUE;
   2551         }
   2552     }
   2553 
   2554     return FALSE;
   2555 }
   2556 
   2557 s32 cur_obj_can_mario_activate_textbox_2(f32 radius, f32 height) {
   2558     // The last argument here is unused. When this function is called directly the argument is always set to 0x7FFF.
   2559     return cur_obj_can_mario_activate_textbox(radius, height, 0x1000);
   2560 }
   2561 
   2562 static void cur_obj_end_dialog(s32 dialogFlags, s32 dialogResult) {
   2563     o->oDialogResponse = dialogResult;
   2564     o->oDialogState++;
   2565 
   2566     if (!(dialogFlags & DIALOG_FLAG_TIME_STOP_ENABLED)) {
   2567         set_mario_npc_dialog(MARIO_DIALOG_STOP);
   2568     }
   2569 }
   2570 
   2571 s32 cur_obj_update_dialog(s32 actionArg, s32 dialogFlags, s32 dialogID, UNUSED s32 unused) {
   2572     s32 dialogResponse = DIALOG_RESPONSE_NONE;
   2573     UNUSED s32 doneTurning = TRUE;
   2574 
   2575     switch (o->oDialogState) {
   2576 #if BUGFIX_DIALOG_TIME_STOP
   2577         case DIALOG_STATUS_ENABLE_TIME_STOP:
   2578             // Patched :(
   2579             // Wait for Mario to be ready to speak, and then enable time stop
   2580             if (mario_ready_to_speak() || gMarioState->action == ACT_READING_NPC_DIALOG) {
   2581                 gTimeStopState |= TIME_STOP_ENABLED;
   2582                 o->activeFlags |= ACTIVE_FLAG_INITIATED_TIME_STOP;
   2583                 o->oDialogState++;
   2584             } else {
   2585                 break;
   2586             }
   2587             // Fall through so that Mario's action is interrupted immediately
   2588             // after time is stopped
   2589 #else
   2590         case DIALOG_STATUS_ENABLE_TIME_STOP:
   2591             //! We enable time stop even if Mario is not ready to speak. This
   2592             //  allows us to move during time stop as long as Mario never enters
   2593             //  an action that can be interrupted with text.
   2594             if (gMarioState->health >= 0x100) {
   2595                 gTimeStopState |= TIME_STOP_ENABLED;
   2596                 o->activeFlags |= ACTIVE_FLAG_INITIATED_TIME_STOP;
   2597                 o->oDialogState++;
   2598             }
   2599             break;
   2600 #endif
   2601         case DIALOG_STATUS_INTERRUPT:
   2602             // Interrupt until Mario is actually speaking with the NPC
   2603             if (set_mario_npc_dialog(actionArg) == MARIO_DIALOG_STATUS_SPEAK) {
   2604                 o->oDialogState++;
   2605             }
   2606             break;
   2607 
   2608         case DIALOG_STATUS_START_DIALOG:
   2609             // Starts dialog, depending of the flag defined, it calls
   2610             // a default dialog or a dialog with response.
   2611             if (dialogFlags & DIALOG_FLAG_TEXT_RESPONSE) {
   2612                 create_dialog_box_with_response(dialogID);
   2613             } else if (dialogFlags & DIALOG_FLAG_TEXT_DEFAULT) {
   2614                 create_dialog_box(dialogID);
   2615             }
   2616             o->oDialogState++;
   2617             break;
   2618 
   2619         case DIALOG_STATUS_STOP_DIALOG:
   2620             // Stops dialog, if the flag dialog response was called
   2621             // then it defines the value to let the object do the rest.
   2622             if (dialogFlags & DIALOG_FLAG_TEXT_RESPONSE) {
   2623                 if (gDialogResponse != DIALOG_RESPONSE_NONE) {
   2624                     cur_obj_end_dialog(dialogFlags, gDialogResponse);
   2625                 }
   2626             } else if (dialogFlags & DIALOG_FLAG_TEXT_DEFAULT) {
   2627                 if (get_dialog_id() == DIALOG_NONE) {
   2628                     cur_obj_end_dialog(dialogFlags, DIALOG_RESPONSE_NOT_DEFINED);
   2629                 }
   2630             } else {
   2631                 cur_obj_end_dialog(dialogFlags, DIALOG_RESPONSE_NOT_DEFINED);
   2632             }
   2633             break;
   2634 
   2635         case DIALOG_STATUS_DISABLE_TIME_STOP:
   2636             // We disable time stop for a few seconds when Mario is no longer
   2637             // speaking or the flag is defined, then we enable it again.
   2638             // Usually, an object disables time stop using a separate function
   2639             // after a certain condition is met.
   2640             if (gMarioState->action != ACT_READING_NPC_DIALOG || (dialogFlags & DIALOG_FLAG_TIME_STOP_ENABLED)) {
   2641                 gTimeStopState &= ~TIME_STOP_ENABLED;
   2642                 o->activeFlags &= ~ACTIVE_FLAG_INITIATED_TIME_STOP;
   2643                 dialogResponse = o->oDialogResponse;
   2644                 o->oDialogState = DIALOG_STATUS_ENABLE_TIME_STOP;
   2645             }
   2646             break;
   2647 
   2648         default:
   2649             o->oDialogState = DIALOG_STATUS_ENABLE_TIME_STOP;
   2650             break;
   2651     }
   2652 
   2653     return dialogResponse;
   2654 }
   2655 
   2656 s32 cur_obj_update_dialog_with_cutscene(s32 actionArg, s32 dialogFlags, s32 cutsceneTable, s32 dialogID) {
   2657     s32 dialogResponse = DIALOG_RESPONSE_NONE;
   2658     s32 doneTurning = TRUE;
   2659 
   2660     switch (o->oDialogState) {
   2661 #if BUGFIX_DIALOG_TIME_STOP
   2662         case DIALOG_STATUS_ENABLE_TIME_STOP:
   2663             // Wait for Mario to be ready to speak, and then enable time stop
   2664             if (mario_ready_to_speak() || gMarioState->action == ACT_READING_NPC_DIALOG) {
   2665                 gTimeStopState |= TIME_STOP_ENABLED;
   2666                 o->activeFlags |= ACTIVE_FLAG_INITIATED_TIME_STOP;
   2667                 o->oDialogState++;
   2668                 o->oDialogResponse = DIALOG_RESPONSE_NONE;
   2669             } else {
   2670                 break;
   2671             }
   2672             // Fall through so that Mario's action is interrupted immediately
   2673             // after time is stopped
   2674 #else
   2675         case DIALOG_STATUS_ENABLE_TIME_STOP:
   2676             //! We enable time stop even if Mario is not ready to speak. This
   2677             //  allows us to move during time stop as long as Mario never enters
   2678             //  an action that can be interrupted with text.
   2679             if (gMarioState->health >= 0x0100) {
   2680                 gTimeStopState |= TIME_STOP_ENABLED;
   2681                 o->activeFlags |= ACTIVE_FLAG_INITIATED_TIME_STOP;
   2682                 o->oDialogState++;
   2683                 o->oDialogResponse = DIALOG_RESPONSE_NONE;
   2684             }
   2685             break;
   2686 #endif
   2687         case DIALOG_STATUS_INTERRUPT:
   2688             // Additional flag that makes the NPC rotate towards to Mario
   2689             if (dialogFlags & DIALOG_FLAG_TURN_TO_MARIO) {
   2690                 doneTurning = cur_obj_rotate_yaw_toward(obj_angle_to_object(o, gMarioObject), 0x800);
   2691                 // Failsafe just in case it takes more than 33 frames somehow
   2692                 if (o->oDialogResponse >= 33) {
   2693                     doneTurning = TRUE;
   2694                 }
   2695             }
   2696             // Interrupt status until Mario is actually speaking with the NPC and if the
   2697             // object is done turning to Mario
   2698             if (set_mario_npc_dialog(actionArg) == MARIO_DIALOG_STATUS_SPEAK && doneTurning) {
   2699                 o->oDialogResponse = 0;
   2700                 o->oDialogState++;
   2701             } else {
   2702                 o->oDialogResponse++; // treated as a timer for the failsafe
   2703             }
   2704             break;
   2705 
   2706         case DIALOG_STATUS_START_DIALOG:
   2707             // Special check for Cap Switch cutscene since the cutscene itself
   2708             // handles what dialog should use
   2709             if (cutsceneTable == CUTSCENE_CAP_SWITCH_PRESS) {
   2710                 if ((o->oDialogResponse = cutscene_object_without_dialog(cutsceneTable, o))) {
   2711                     o->oDialogState++;
   2712                 }
   2713             } else {
   2714                 // General dialog cutscene function, most of the time
   2715                 // the "CUTSCENE_DIALOG" cutscene is called
   2716                 if ((o->oDialogResponse = cutscene_object_with_dialog(cutsceneTable, o, dialogID))) {
   2717                     o->oDialogState++;
   2718                 }
   2719             }
   2720             break;
   2721 
   2722         case DIALOG_STATUS_STOP_DIALOG:
   2723             // If flag defined, keep time stop enabled until the object
   2724             // decided to disable it independently
   2725             if (dialogFlags & DIALOG_FLAG_TIME_STOP_ENABLED) {
   2726                 dialogResponse = o->oDialogResponse;
   2727                 o->oDialogState = DIALOG_STATUS_ENABLE_TIME_STOP;
   2728             } else if (gMarioState->action != ACT_READING_NPC_DIALOG) {
   2729                 // Disable time stop, then enable time stop for a frame
   2730                 // until the set_mario_npc_dialog function disables it
   2731                 gTimeStopState &= ~TIME_STOP_ENABLED;
   2732                 o->activeFlags &= ~ACTIVE_FLAG_INITIATED_TIME_STOP;
   2733                 dialogResponse = o->oDialogResponse;
   2734                 o->oDialogState = DIALOG_STATUS_ENABLE_TIME_STOP;
   2735             } else {
   2736                 // And finally stop Mario dialog status
   2737                 set_mario_npc_dialog(MARIO_DIALOG_STOP);
   2738             }
   2739             break;
   2740     }
   2741 
   2742     return dialogResponse;
   2743 }
   2744 
   2745 s32 cur_obj_has_model(u16 modelID) {
   2746     if (o->header.gfx.sharedChild == gLoadedGraphNodes[modelID]) {
   2747         return TRUE;
   2748     } else {
   2749         return FALSE;
   2750     }
   2751 }
   2752 
   2753 void cur_obj_align_gfx_with_floor(void) {
   2754     struct Surface *floor;
   2755     Vec3f floorNormal;
   2756     Vec3f position;
   2757 
   2758     position[0] = o->oPosX;
   2759     position[1] = o->oPosY;
   2760     position[2] = o->oPosZ;
   2761 
   2762     find_floor(position[0], position[1], position[2], &floor);
   2763     if (floor != NULL) {
   2764         floorNormal[0] = floor->normal.x;
   2765         floorNormal[1] = floor->normal.y;
   2766         floorNormal[2] = floor->normal.z;
   2767 
   2768         mtxf_align_terrain_normal(o->transform, floorNormal, position, o->oFaceAngleYaw);
   2769         o->header.gfx.throwMatrix = &o->transform;
   2770     }
   2771 }
   2772 
   2773 s32 mario_is_within_rectangle(s16 minX, s16 maxX, s16 minZ, s16 maxZ) {
   2774     if (gMarioObject->oPosX < minX || maxX < gMarioObject->oPosX) {
   2775         return FALSE;
   2776     }
   2777 
   2778     if (gMarioObject->oPosZ < minZ || maxZ < gMarioObject->oPosZ) {
   2779         return FALSE;
   2780     }
   2781 
   2782     return TRUE;
   2783 }
   2784 
   2785 void cur_obj_shake_screen(s32 shake) {
   2786     set_camera_shake_from_point(shake, o->oPosX, o->oPosY, o->oPosZ);
   2787 }
   2788 
   2789 s32 obj_attack_collided_from_other_object(struct Object *obj) {
   2790     s32 numCollidedObjs;
   2791     struct Object *other;
   2792     s32 touchedOtherObject = FALSE;
   2793 
   2794     numCollidedObjs = obj->numCollidedObjs;
   2795     if (numCollidedObjs != 0) {
   2796         other = obj->collidedObjs[0];
   2797 
   2798         if (other != gMarioObject) {
   2799             other->oInteractStatus |= ATTACK_PUNCH | INT_STATUS_WAS_ATTACKED | INT_STATUS_INTERACTED
   2800                                       | INT_STATUS_TOUCHED_BOB_OMB;
   2801             touchedOtherObject = TRUE;
   2802         }
   2803     }
   2804 
   2805     return touchedOtherObject;
   2806 }
   2807 
   2808 s32 cur_obj_was_attacked_or_ground_pounded(void) {
   2809     s32 attacked = FALSE;
   2810 
   2811     if ((o->oInteractStatus & INT_STATUS_INTERACTED)
   2812         && (o->oInteractStatus & INT_STATUS_WAS_ATTACKED)) {
   2813         attacked = TRUE;
   2814     }
   2815 
   2816     if (cur_obj_is_mario_ground_pounding_platform()) {
   2817         attacked = TRUE;
   2818     }
   2819 
   2820     o->oInteractStatus = 0;
   2821     return attacked;
   2822 }
   2823 
   2824 void obj_copy_behavior_params(struct Object *dst, struct Object *src) {
   2825     dst->oBhvParams = src->oBhvParams;
   2826     dst->oBhvParams2ndByte = src->oBhvParams2ndByte;
   2827 }
   2828 
   2829 void cur_obj_init_animation_and_anim_frame(s32 animIndex, s32 animFrame) {
   2830     cur_obj_init_animation_with_sound(animIndex);
   2831     o->header.gfx.animInfo.animFrame = animFrame;
   2832 }
   2833 
   2834 s32 cur_obj_init_animation_and_check_if_near_end(s32 animIndex) {
   2835     cur_obj_init_animation_with_sound(animIndex);
   2836     return cur_obj_check_if_near_animation_end();
   2837 }
   2838 
   2839 void cur_obj_init_animation_and_extend_if_at_end(s32 animIndex) {
   2840     cur_obj_init_animation_with_sound(animIndex);
   2841     cur_obj_extend_animation_if_at_end();
   2842 }
   2843 
   2844 s32 cur_obj_check_grabbed_mario(void) {
   2845     if (o->oInteractStatus & INT_STATUS_GRABBED_MARIO) {
   2846         o->oKingBobombUnk88 = 1;
   2847         cur_obj_become_intangible();
   2848         return TRUE;
   2849     }
   2850 
   2851     return FALSE;
   2852 }
   2853 
   2854 s32 player_performed_grab_escape_action(void) {
   2855     static s32 grabReleaseState;
   2856     s32 result = FALSE;
   2857 
   2858     if (gPlayer1Controller->stickMag < 30.0f) {
   2859         grabReleaseState = 0;
   2860     }
   2861 
   2862     if (grabReleaseState == 0 && gPlayer1Controller->stickMag > 40.0f) {
   2863         grabReleaseState = 1;
   2864         result = TRUE;
   2865     }
   2866 
   2867     if (gPlayer1Controller->buttonPressed & A_BUTTON) {
   2868         result = TRUE;
   2869     }
   2870 
   2871     return result;
   2872 }
   2873 
   2874 void cur_obj_unused_play_footstep_sound(s32 animFrame1, s32 animFrame2, s32 sound) {
   2875     if (cur_obj_check_anim_frame(animFrame1) || cur_obj_check_anim_frame(animFrame2)) {
   2876         cur_obj_play_sound_2(sound);
   2877     }
   2878 }
   2879 
   2880 void enable_time_stop_including_mario(void) {
   2881     gTimeStopState |= TIME_STOP_ENABLED | TIME_STOP_MARIO_AND_DOORS;
   2882     o->activeFlags |= ACTIVE_FLAG_INITIATED_TIME_STOP;
   2883 }
   2884 
   2885 void disable_time_stop_including_mario(void) {
   2886     gTimeStopState &= ~(TIME_STOP_ENABLED | TIME_STOP_MARIO_AND_DOORS);
   2887     o->activeFlags &= ~ACTIVE_FLAG_INITIATED_TIME_STOP;
   2888 }
   2889 
   2890 s32 cur_obj_check_interacted(void) {
   2891     if (o->oInteractStatus & INT_STATUS_INTERACTED) {
   2892         o->oInteractStatus = 0;
   2893         return TRUE;
   2894     } else {
   2895         return FALSE;
   2896     }
   2897 }
   2898 
   2899 void cur_obj_spawn_loot_blue_coin(void) {
   2900     if (o->oNumLootCoins >= 5) {
   2901         spawn_object(o, MODEL_BLUE_COIN, bhvSpawnedBlueCoin);
   2902         o->oNumLootCoins -= 5;
   2903     }
   2904 }
   2905 
   2906 #ifndef VERSION_JP
   2907 void cur_obj_spawn_star_at_y_offset(f32 targetX, f32 targetY, f32 targetZ, f32 offsetY) {
   2908     f32 objectPosY = o->oPosY;
   2909     o->oPosY += offsetY + gDebugInfo[DEBUG_PAGE_ENEMYINFO][0];
   2910     spawn_default_star(targetX, targetY, targetZ);
   2911     o->oPosY = objectPosY;
   2912 }
   2913 #endif