monty_mole.inc.c (14828B)
1 2 /** 3 * Behavior for bhvMontyMole, bhvMontyMoleHole, and bhvMontyMoleRock. 4 * The monty mole holes are placed statically. The monty moles repeatedly 5 * search for an available hole to pop out of. 6 */ 7 8 /** 9 * The first hole in the list of monty mole holes. The list is a singly linked 10 * list using the parentObj field. 11 */ 12 struct Object *sMontyMoleHoleList; 13 14 /** 15 * The number of nearby monty moles that have been killed in a row. 16 */ 17 s32 sMontyMoleKillStreak; 18 19 /** 20 * The position of the last killed monty mole, used for determining whether 21 * the next killed monty mole is nearby. 22 */ 23 f32 sMontyMoleLastKilledPosX; 24 f32 sMontyMoleLastKilledPosY; 25 f32 sMontyMoleLastKilledPosZ; 26 27 /** 28 * Link all objects with the given behavior using parentObj. 29 * The result is a singly linked list in reverse processing order. Return the 30 * start of this list. 31 */ 32 static struct Object *link_objects_with_behavior(const BehaviorScript *behavior) { 33 const BehaviorScript *behaviorAddr = segmented_to_virtual(behavior); 34 struct Object *obj; 35 struct Object *lastObject = NULL; 36 struct ObjectNode *listHead = &gObjectLists[get_object_list_from_behavior(behaviorAddr)]; 37 38 obj = (struct Object *) listHead->next; 39 while (obj != (struct Object *) listHead) { 40 if (obj->behavior == behaviorAddr && obj->activeFlags != ACTIVE_FLAG_DEACTIVATED) { 41 obj->parentObj = lastObject; 42 lastObject = obj; 43 } 44 45 obj = (struct Object *) obj->header.next; 46 } 47 48 return lastObject; 49 } 50 51 /** 52 * Select a random hole that is within minDistToMario and 1500 of mario, and 53 * whose cooldown is zero. Return NULL if no hole is available. 54 */ 55 static struct Object *monty_mole_select_available_hole(f32 minDistToMario) { 56 struct Object *hole = sMontyMoleHoleList; 57 s32 numAvailableHoles = 0; 58 59 while (hole != NULL) { 60 if (hole->oMontyMoleHoleCooldown == 0) { 61 if (hole->oDistanceToMario < 1500.0f && hole->oDistanceToMario > minDistToMario) { 62 numAvailableHoles++; 63 } 64 } 65 66 hole = hole->parentObj; 67 } 68 69 if (numAvailableHoles != 0) { 70 s32 selectedHole = (s32)(random_float() * numAvailableHoles); 71 72 hole = sMontyMoleHoleList; 73 numAvailableHoles = 0; 74 75 while (hole != NULL) { 76 if (hole->oMontyMoleHoleCooldown == 0) { 77 if (hole->oDistanceToMario < 1500.0f && hole->oDistanceToMario > minDistToMario) { 78 if (numAvailableHoles == selectedHole) { 79 return hole; 80 } 81 82 numAvailableHoles++; 83 } 84 } 85 86 hole = hole->parentObj; 87 } 88 } 89 90 return NULL; 91 } 92 93 /** 94 * Update function for bhvMontyMoleHole. 95 */ 96 void bhv_monty_mole_hole_update(void) { 97 // If hole list hasn't been constructed yet, construct it 98 if (o->parentObj == o) { 99 sMontyMoleHoleList = link_objects_with_behavior(bhvMontyMoleHole); 100 sMontyMoleKillStreak = 0; 101 } else if (o->oMontyMoleHoleCooldown > 0) { 102 o->oMontyMoleHoleCooldown--; 103 } 104 } 105 106 /** 107 * Spawn dirt particles when rising out of the ground. 108 */ 109 void monty_mole_spawn_dirt_particles(s8 offsetY, s8 velYBase) { 110 static struct SpawnParticlesInfo montyMoleRiseFromGroundParticles = { 111 /* bhvParam: */ 0, 112 /* count: */ 3, 113 /* model: */ MODEL_SAND_DUST, 114 /* offsetY: */ 0, 115 /* forwardVelBase: */ 4, 116 /* forwardVelRange: */ 4, 117 /* velYBase: */ 10, 118 /* velYRange: */ 15, 119 /* gravity: */ -4, 120 /* dragStrength: */ 0, 121 /* sizeBase: */ 10.0f, 122 /* sizeRange: */ 7.0f, 123 }; 124 125 montyMoleRiseFromGroundParticles.offsetY = offsetY; 126 montyMoleRiseFromGroundParticles.velYBase = velYBase; 127 128 cur_obj_spawn_particles(&montyMoleRiseFromGroundParticles); 129 } 130 131 /** 132 * Init function for bhvMontyMole. 133 */ 134 void bhv_monty_mole_init(void) { 135 o->oMontyMoleCurrentHole = NULL; 136 } 137 138 /** 139 * Search for an available hole. Once one is available, claim it and enter 140 * either the rise from hole or jump out of hole action. 141 */ 142 static void monty_mole_act_select_hole(void) { 143 f32 minDistToMario; 144 145 if (o->oBhvParams2ndByte != MONTY_MOLE_BP_NO_ROCK) { 146 minDistToMario = 200.0f; 147 } else if (gMarioStates[0].forwardVel < 8.0f) { 148 minDistToMario = 100.0f; 149 } else { 150 minDistToMario = 500.0f; 151 } 152 153 // Select a hole to pop out of 154 if ((o->oMontyMoleCurrentHole = monty_mole_select_available_hole(minDistToMario)) != NULL) { 155 cur_obj_play_sound_2(SOUND_OBJ2_MONTY_MOLE_APPEAR); 156 157 // Mark hole as unavailable 158 o->oMontyMoleCurrentHole->oMontyMoleHoleCooldown = -1; 159 160 // Position at hole 161 o->oPosX = o->oMontyMoleCurrentHole->oPosX; 162 o->oPosY = o->oFloorHeight = o->oMontyMoleCurrentHole->oPosY; 163 o->oPosZ = o->oMontyMoleCurrentHole->oPosZ; 164 165 o->oFaceAnglePitch = 0; 166 o->oMoveAngleYaw = o->oMontyMoleCurrentHole->oAngleToMario; 167 168 if (o->oDistanceToMario > 500.0f || minDistToMario > 100.0f || random_sign() < 0) { 169 o->oAction = MONTY_MOLE_ACT_RISE_FROM_HOLE; 170 o->oVelY = 3.0f; 171 o->oGravity = 0.0f; 172 monty_mole_spawn_dirt_particles(0, 10); 173 } else { 174 o->oAction = MONTY_MOLE_ACT_JUMP_OUT_OF_HOLE; 175 o->oVelY = 50.0f; 176 o->oGravity = -4.0f; 177 monty_mole_spawn_dirt_particles(0, 20); 178 } 179 180 cur_obj_unhide(); 181 cur_obj_become_tangible(); 182 } 183 } 184 185 /** 186 * Move upward until high enough, then enter the spawn rock action. 187 */ 188 static void monty_mole_act_rise_from_hole(void) { 189 cur_obj_init_animation_with_sound(1); 190 191 if (o->oMontyMoleHeightRelativeToFloor >= 49.0f) { 192 o->oPosY = o->oFloorHeight + 50.0f; 193 o->oVelY = 0.0f; 194 195 if (cur_obj_check_if_near_animation_end()) { 196 o->oAction = MONTY_MOLE_ACT_SPAWN_ROCK; 197 } 198 } 199 } 200 201 /** 202 * If mario is close enough, then spawn a rock and enter the throw rock action. 203 * Otherwise, enter the begin jump into hole action. 204 */ 205 static void monty_mole_act_spawn_rock(void) { 206 struct Object *rock; 207 208 if (cur_obj_init_anim_and_check_if_end(2)) { 209 if (o->oBhvParams2ndByte != MONTY_MOLE_BP_NO_ROCK 210 && abs_angle_diff(o->oAngleToMario, o->oMoveAngleYaw) < 0x4000 211 && (rock = spawn_object(o, MODEL_PEBBLE, bhvMontyMoleRock)) != NULL) { 212 o->prevObj = rock; 213 o->oAction = MONTY_MOLE_ACT_THROW_ROCK; 214 } else { 215 o->oAction = MONTY_MOLE_ACT_BEGIN_JUMP_INTO_HOLE; 216 } 217 } 218 } 219 220 /** 221 * Wait until mario is relatively close, then set vely to 40 and enter the jump 222 * into hole action. 223 */ 224 static void monty_mole_act_begin_jump_into_hole(void) { 225 if (cur_obj_init_anim_and_check_if_end(3) || obj_is_near_to_and_facing_mario(1000.0f, 0x4000)) { 226 o->oAction = MONTY_MOLE_ACT_JUMP_INTO_HOLE; 227 o->oVelY = 40.0f; 228 o->oGravity = -6.0f; 229 } 230 } 231 232 /** 233 * Throw the held rock, then enter the begin jump into hole action. 234 */ 235 static void monty_mole_act_throw_rock(void) { 236 if (cur_obj_init_anim_check_frame(8, 10)) { 237 cur_obj_play_sound_2(SOUND_OBJ_MONTY_MOLE_ATTACK); 238 o->prevObj = NULL; 239 } 240 241 if (cur_obj_check_if_near_animation_end()) { 242 o->oAction = MONTY_MOLE_ACT_BEGIN_JUMP_INTO_HOLE; 243 } 244 } 245 246 /** 247 * Tilt downward and wait until close to landing, then enter the hide action. 248 */ 249 static void monty_mole_act_jump_into_hole(void) { 250 cur_obj_init_anim_extend(0); 251 252 o->oFaceAnglePitch = -atan2s(o->oVelY, -4.0f); 253 254 if (o->oVelY < 0.0f && o->oMontyMoleHeightRelativeToFloor < 120.0f) { 255 o->oAction = MONTY_MOLE_ACT_HIDE; 256 o->oGravity = 0.0f; 257 monty_mole_spawn_dirt_particles(-80, 15); 258 } 259 } 260 261 /** 262 * Become intangible and enter the select hole action. 263 */ 264 static void monty_mole_hide_in_hole(void) { 265 o->oMontyMoleCurrentHole->oMontyMoleHoleCooldown = 30; 266 o->oAction = MONTY_MOLE_ACT_SELECT_HOLE; 267 o->oVelY = 0.0f; 268 269 //! Even though the object becomes intangible here, it is still possible 270 // for a bob-omb to interact with it later in the frame (since bob-ombs 271 // come after monty moles in processing order). 272 // This means that the monty mole can be attacked while in the select hole 273 // action. If no hole is available (e.g. because mario is too far away), 274 // the game will crash because of the line above that accesses 275 // oMontyMoleCurrentHole. 276 cur_obj_become_intangible(); 277 } 278 279 /** 280 * Wait to land on the floor, then hide. 281 */ 282 static void monty_mole_act_hide(void) { 283 cur_obj_init_animation_with_sound(1); 284 285 if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) { 286 cur_obj_hide(); 287 monty_mole_hide_in_hole(); 288 } else { 289 approach_f32_ptr(&o->oVelY, -4.0f, 0.5f); 290 } 291 } 292 293 /** 294 * Jump upward and then fall back down. Then enter the begin jump into hole 295 * action. 296 */ 297 static void monty_mole_act_jump_out_of_hole(void) { 298 if (o->oVelY > 0.0f) { 299 cur_obj_init_animation_with_sound(9); 300 } else { 301 cur_obj_init_anim_extend(4); 302 303 if (o->oMontyMoleHeightRelativeToFloor < 50.0f) { 304 o->oPosY = o->oFloorHeight + 50.0f; 305 o->oAction = MONTY_MOLE_ACT_BEGIN_JUMP_INTO_HOLE; 306 o->oVelY = o->oGravity = 0.0f; 307 } 308 } 309 } 310 311 /** 312 * Hitbox for monty mole. 313 */ 314 static struct ObjectHitbox sMontyMoleHitbox = { 315 /* interactType: */ INTERACT_BOUNCE_TOP, 316 /* downOffset: */ 0, 317 /* damageOrCoinValue: */ 2, 318 /* health: */ -1, 319 /* numLootCoins: */ 0, 320 /* radius: */ 70, 321 /* height: */ 50, 322 /* hurtboxRadius: */ 30, 323 /* hurtboxHeight: */ 40, 324 }; 325 326 /** 327 * Update function for bhvMontyMole. 328 */ 329 void bhv_monty_mole_update(void) { 330 // PARTIAL_UPDATE 331 332 o->oDeathSound = SOUND_OBJ_DYING_ENEMY1; 333 cur_obj_update_floor_and_walls(); 334 335 o->oMontyMoleHeightRelativeToFloor = o->oPosY - o->oFloorHeight; 336 337 switch (o->oAction) { 338 case MONTY_MOLE_ACT_SELECT_HOLE: 339 monty_mole_act_select_hole(); 340 break; 341 case MONTY_MOLE_ACT_RISE_FROM_HOLE: 342 monty_mole_act_rise_from_hole(); 343 break; 344 case MONTY_MOLE_ACT_SPAWN_ROCK: 345 monty_mole_act_spawn_rock(); 346 break; 347 case MONTY_MOLE_ACT_BEGIN_JUMP_INTO_HOLE: 348 monty_mole_act_begin_jump_into_hole(); 349 break; 350 case MONTY_MOLE_ACT_THROW_ROCK: 351 monty_mole_act_throw_rock(); 352 break; 353 case MONTY_MOLE_ACT_JUMP_INTO_HOLE: 354 monty_mole_act_jump_into_hole(); 355 break; 356 case MONTY_MOLE_ACT_HIDE: 357 monty_mole_act_hide(); 358 break; 359 case MONTY_MOLE_ACT_JUMP_OUT_OF_HOLE: 360 monty_mole_act_jump_out_of_hole(); 361 break; 362 } 363 364 // Spawn a 1-up if you kill 8 monty moles 365 if (obj_check_attacks(&sMontyMoleHitbox, o->oAction) != 0) { 366 if (sMontyMoleKillStreak != 0) { 367 f32 dx = o->oPosX - sMontyMoleLastKilledPosX; 368 f32 dy = o->oPosY - sMontyMoleLastKilledPosY; 369 f32 dz = o->oPosZ - sMontyMoleLastKilledPosZ; 370 371 f32 distToLastKill = sqrtf(dx * dx + dy * dy + dz * dz); 372 373 //! The two farthest holes on the bottom level of TTM are more than 374 // 1500 units away from each other, so the counter resets if you 375 // attack moles in these holes consecutively. 376 if (distToLastKill < 1500.0f) { 377 if (sMontyMoleKillStreak == 7) { 378 play_puzzle_jingle(); 379 spawn_object(o, MODEL_1UP, bhv1UpWalking); 380 } 381 } else { 382 sMontyMoleKillStreak = 0; 383 } 384 } 385 386 //! No overflow check 387 sMontyMoleKillStreak++; 388 389 sMontyMoleLastKilledPosX = o->oPosX; 390 sMontyMoleLastKilledPosY = o->oPosY; 391 sMontyMoleLastKilledPosZ = o->oPosZ; 392 393 monty_mole_hide_in_hole(); 394 395 // Throw rock if holding one 396 o->prevObj = NULL; 397 } 398 399 cur_obj_move_standard(78); 400 } 401 402 /** 403 * Wait for monty mole to throw the rock, then enter the move action. 404 */ 405 static void monty_mole_rock_act_held(void) { 406 // The position is offset since the monty mole is throwing it with its hand 407 o->oParentRelativePosX = 80.0f; 408 o->oParentRelativePosY = -50.0f; 409 o->oParentRelativePosZ = 0.0f; 410 411 if (o->parentObj->prevObj == NULL) { 412 f32 distToMario = o->oDistanceToMario; 413 if (distToMario > 600.0f) { 414 distToMario = 600.0f; 415 } 416 417 o->oAction = MONTY_MOLE_ROCK_ACT_MOVE; 418 419 // The angle is adjusted to compensate for the start position offset 420 o->oMoveAngleYaw = (s32)(o->parentObj->oMoveAngleYaw + 500 - distToMario * 0.1f); 421 422 o->oForwardVel = 40.0f; 423 o->oVelY = distToMario * 0.08f + 8.0f; 424 425 o->oMoveFlags = 0; 426 } 427 } 428 429 /** 430 * Hitbox for monty mole rock. 431 */ 432 static struct ObjectHitbox sMontyMoleRockHitbox = { 433 /* interactType: */ INTERACT_MR_BLIZZARD, 434 /* downOffset: */ 15, 435 /* damageOrCoinValue: */ 1, 436 /* health: */ 99, 437 /* numLootCoins: */ 0, 438 /* radius: */ 30, 439 /* height: */ 15, 440 /* hurtboxRadius: */ 30, 441 /* hurtboxHeight: */ 15, 442 }; 443 444 /** 445 * The particles that spawn when a monty mole rock breaks. 446 */ 447 static struct SpawnParticlesInfo sMontyMoleRockBreakParticles = { 448 /* bhvParam: */ 0, 449 /* count: */ 2, 450 /* model: */ MODEL_PEBBLE, 451 /* offsetY: */ 10, 452 /* forwardVelBase: */ 4, 453 /* forwardVelRange: */ 4, 454 /* velYBase: */ 10, 455 /* velYRange: */ 15, 456 /* gravity: */ -4, 457 /* dragStrength: */ 0, 458 /* sizeBase: */ 8.0f, 459 /* sizeRange: */ 4.0f, 460 }; 461 462 /** 463 * Move, then despawn after hitting the ground or water. 464 */ 465 static void monty_mole_rock_act_move(void) { 466 cur_obj_update_floor_and_walls(); 467 468 if (o->oMoveFlags & (OBJ_MOVE_MASK_ON_GROUND | OBJ_MOVE_ENTERED_WATER)) { 469 cur_obj_spawn_particles(&sMontyMoleRockBreakParticles); 470 obj_mark_for_deletion(o); 471 } 472 473 cur_obj_move_standard(78); 474 } 475 476 /** 477 * Update function for bhvMontyMoleRock. 478 */ 479 void bhv_monty_mole_rock_update(void) { 480 // PARTIAL_UPDATE 481 //! Since we can prevent them from despawning using partial updates, we 482 // can fill up object slots to crash the game. 483 484 obj_check_attacks(&sMontyMoleRockHitbox, o->oAction); 485 486 switch (o->oAction) { 487 case MONTY_MOLE_ROCK_ACT_HELD: 488 monty_mole_rock_act_held(); 489 break; 490 case MONTY_MOLE_ROCK_ACT_MOVE: 491 monty_mole_rock_act_move(); 492 break; 493 } 494 }