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