wiggler.inc.c (16109B)
1 2 /** 3 * Behavior for bhvWigglerHead and bhvWigglerBody. 4 * The bhvWigglerHead object controls the wiggler's behavior, and physically manifests 5 * as the wiggler's head. The bhvWigglerBody objects represent the 3 tail body 6 * parts, numbered 1 closest to the head, and 3 at the end of the tail. 7 * Processing order is bhvWigglerHead, then bhvWigglerBody 1, 2, then 3. 8 */ 9 10 /** 11 * Hitbox for wiggler's non-head body parts. 12 */ 13 static struct ObjectHitbox sWigglerBodyPartHitbox = { 14 /* interactType: */ INTERACT_BOUNCE_TOP, 15 /* downOffset: */ 0, 16 /* damageOrCoinValue: */ 3, 17 /* health: */ 99, // never decreases 18 /* numLootCoins: */ 0, 19 /* radius: */ 20, 20 /* height: */ 20, 21 /* hurtboxRadius: */ 20, 22 /* hurtboxHeight: */ 10, 23 }; 24 25 /** 26 * Hitbox for wiggler's head. 27 */ 28 static struct ObjectHitbox sWigglerHitbox = { 29 /* interactType: */ INTERACT_BOUNCE_TOP, 30 /* downOffset: */ 0, 31 /* damageOrCoinValue: */ 3, 32 /* health: */ 4, 33 /* numLootCoins: */ 0, 34 /* radius: */ 60, 35 /* height: */ 50, 36 /* hurtboxRadius: */ 30, 37 /* hurtboxHeight: */ 40, 38 }; 39 40 /** 41 * Attack handler for wiggler while in the walking action. 42 */ 43 static u8 sWigglerAttackHandlers[] = { 44 /* ATTACK_PUNCH: */ ATTACK_HANDLER_KNOCKBACK, 45 /* ATTACK_KICK_OR_TRIP: */ ATTACK_HANDLER_KNOCKBACK, 46 /* ATTACK_FROM_ABOVE: */ ATTACK_HANDLER_SPECIAL_WIGGLER_JUMPED_ON, 47 /* ATTACK_GROUND_POUND_OR_TWIRL: */ ATTACK_HANDLER_SPECIAL_WIGGLER_JUMPED_ON, 48 /* ATTACK_FAST_ATTACK: */ ATTACK_HANDLER_KNOCKBACK, 49 /* ATTACK_FROM_BELOW: */ ATTACK_HANDLER_KNOCKBACK, 50 }; 51 52 /** 53 * Target speed while walking when wiggler has health 1, 2, 3, and 4. 54 */ 55 static f32 sWigglerSpeeds[] = { 2.0f, 40.0f, 30.0f, 16.0f }; 56 57 /** 58 * Update function for bhvWigglerBody. 59 * Set object position and angle based on wiggler segment data and avoid falling 60 * through the floor. 61 * Tangible if the wiggler is not in the shrinking action, but does nothing on 62 * attack. 63 */ 64 void bhv_wiggler_body_part_update(void) { 65 f32 dx; 66 f32 dy; 67 f32 dz; 68 f32 dxz; 69 struct ChainSegment *segment = &o->parentObj->oWigglerSegments[o->oBhvParams2ndByte]; 70 f32 posOffset; 71 72 cur_obj_scale(o->parentObj->header.gfx.scale[0]); 73 74 o->oFaceAnglePitch = segment->pitch; 75 o->oFaceAngleYaw = segment->yaw; 76 77 // TODO: What is this for? 78 posOffset = -37.5f * o->header.gfx.scale[0]; 79 dy = posOffset * coss(o->oFaceAnglePitch) - posOffset; 80 dxz = posOffset * sins(o->oFaceAnglePitch); 81 dx = dxz * sins(o->oFaceAngleYaw); 82 dz = dxz * coss(o->oFaceAngleYaw); 83 84 o->oPosX = segment->posX + dx; 85 o->oPosY = segment->posY + dy; 86 o->oPosZ = segment->posZ + dz; 87 88 if (o->oPosY < o->parentObj->oWigglerFallThroughFloorsHeight) { 89 //! Since position is recomputed each frame, tilting the wiggler up 90 // while on the ground could cause the tail segments to clip through 91 // the floor 92 o->oPosY += -30.0f; 93 cur_obj_update_floor_height(); 94 if (o->oFloorHeight > o->oPosY) { 95 o->oPosY = o->oFloorHeight; 96 } 97 } 98 99 segment->posY = o->oPosY; 100 101 // Inherit walking animation speed from wiggler 102 cur_obj_init_animation_with_accel_and_sound(0, o->parentObj->oWigglerWalkAnimSpeed); 103 if (o->parentObj->oWigglerWalkAnimSpeed == 0.0f) { 104 cur_obj_reverse_animation(); 105 } 106 107 if (o->parentObj->oAction == WIGGLER_ACT_SHRINK) { 108 cur_obj_become_intangible(); 109 } else { 110 obj_check_attacks(&sWigglerBodyPartHitbox, o->oAction); 111 } 112 } 113 114 /** 115 * Initialize the segment data and spawn the body part objects. 116 */ 117 void wiggler_init_segments(void) { 118 s32 i; 119 struct ChainSegment *segments = mem_pool_alloc(gObjectMemoryPool, 4 * sizeof(struct ChainSegment)); 120 121 if (segments != NULL) { 122 // Each segment represents the global position and orientation of each 123 // object. Segment 0 represents the wiggler's head, and segment i>0 124 // represents body part i. 125 o->oWigglerSegments = segments; 126 for (i = 0; i <= 3; i++) { 127 chain_segment_init(segments + i); 128 129 (segments + i)->posX = o->oPosX; 130 (segments + i)->posY = o->oPosY; 131 (segments + i)->posZ = o->oPosZ; 132 133 (segments + i)->pitch = o->oFaceAnglePitch; 134 (segments + i)->yaw = o->oFaceAngleYaw; 135 } 136 137 o->header.gfx.animInfo.animFrame = -1; 138 139 // Spawn each body part 140 for (i = 1; i <= 3; i++) { 141 struct Object *bodyPart = 142 spawn_object_relative(i, 0, 0, 0, o, MODEL_WIGGLER_BODY, bhvWigglerBody); 143 if (bodyPart != NULL) { 144 obj_init_animation_with_sound(bodyPart, wiggler_seg5_anims_0500C874, 0); 145 bodyPart->header.gfx.animInfo.animFrame = (23 * i) % 26 - 1; 146 } 147 } 148 149 o->oAction = WIGGLER_ACT_WALK; 150 cur_obj_unhide(); 151 } 152 153 #if defined(VERSION_EU) || defined(AVOID_UB) 154 o->oHealth = 4; // This fixes Wiggler reading UB on his first frame of his acceleration, as his health is not set. 155 #endif 156 } 157 158 /** 159 * Update the tail to reflect changes in the head's yaw and pitch, and ensure 160 * that the distance between parts is exactly the intended distance. 161 * Since these positions are completely recomputed each frame, it is not possible 162 * for a body part to get stuck on geometry and separate from the rest of the 163 * body. 164 */ 165 void wiggler_update_segments(void) { 166 struct ChainSegment *prevBodyPart; 167 struct ChainSegment *bodyPart; 168 f32 dx; 169 f32 dy; 170 f32 dz; 171 s16 dpitch; 172 s16 dyaw; 173 f32 dxz; 174 s32 i; 175 f32 segmentLength = 35.0f * o->header.gfx.scale[0]; 176 177 for (i = 1; i <= 3; i++) { 178 prevBodyPart = &o->oWigglerSegments[i - 1]; 179 bodyPart = &o->oWigglerSegments[i]; 180 181 dx = bodyPart->posX - prevBodyPart->posX; 182 dy = bodyPart->posY - prevBodyPart->posY; 183 dz = bodyPart->posZ - prevBodyPart->posZ; 184 185 // As the head turns, propagate this rotation backward if the difference 186 // is more than 45 degrees 187 dyaw = atan2s(-dz, -dx) - prevBodyPart->yaw; 188 clamp_s16(&dyaw, -0x2000, 0x2000); 189 bodyPart->yaw = prevBodyPart->yaw + dyaw; 190 191 // As the head tilts, propagate the tilt backward 192 dxz = sqrtf(dx * dx + dz * dz); 193 dpitch = atan2s(dxz, dy) - prevBodyPart->pitch; 194 clamp_s16(&dpitch, -0x2000, 0x2000); 195 bodyPart->pitch = prevBodyPart->pitch + dpitch; 196 197 // Set the body part's position relative to the previous body part's 198 // position, using the current body part's angles. This means that the 199 // head can rotate up to 45 degrees without the body moving 200 bodyPart->posY = segmentLength * sins(bodyPart->pitch) + prevBodyPart->posY; 201 dxz = segmentLength * coss(bodyPart->pitch); 202 bodyPart->posX = prevBodyPart->posX - dxz * sins(bodyPart->yaw); 203 bodyPart->posZ = prevBodyPart->posZ - dxz * coss(bodyPart->yaw); 204 } 205 } 206 207 /** 208 * Show text if necessary. Then walk toward mario if not at full health, and 209 * otherwise wander in random directions. 210 * If attacked by mario, enter either the jumped on or knockback action. 211 */ 212 static void wiggler_act_walk(void) { 213 s16 yawTurnSpeed; 214 215 o->oWigglerWalkAnimSpeed = 0.06f * o->oForwardVel; 216 217 // Update text if necessary 218 if (o->oWigglerTextStatus < WIGGLER_TEXT_STATUS_COMPLETED_DIALOG) { 219 if (o->oWigglerTextStatus == WIGGLER_TEXT_STATUS_AWAIT_DIALOG) { 220 seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40); 221 o->oWigglerTextStatus = WIGGLER_TEXT_STATUS_SHOWING_DIALOG; 222 } 223 224 // If Mario is positioned below the wiggler, assume he entered through the 225 // lower cave entrance, so don't display text. 226 if (gMarioObject->oPosY < o->oPosY || cur_obj_update_dialog_with_cutscene( 227 MARIO_DIALOG_LOOK_UP, DIALOG_FLAG_NONE, CUTSCENE_DIALOG, DIALOG_150)) { 228 o->oWigglerTextStatus = WIGGLER_TEXT_STATUS_COMPLETED_DIALOG; 229 } 230 } else { 231 //! Every object's health is initially 2048, and wiggler's doesn't change 232 // to 4 until after this runs the first time. It indexes out of bounds 233 // and uses the value 113762.3 for one frame on US. This is fixed up 234 // in wiggler_init_segments if AVOID_UB is defined. 235 obj_forward_vel_approach(sWigglerSpeeds[o->oHealth - 1], 1.0f); 236 237 if (o->oWigglerWalkAwayFromWallTimer != 0) { 238 o->oWigglerWalkAwayFromWallTimer--; 239 } else { 240 if (o->oDistanceToMario >= 25000.0f) { 241 // If >1200 away from home, turn to home 242 o->oWigglerTargetYaw = o->oAngleToMario; 243 } 244 245 if (obj_bounce_off_walls_edges_objects(&o->oWigglerTargetYaw)) { 246 //! If the wiggler could self-intersect, or intersect a different 247 // non-mario object, this could potentially be used to force 248 // the wiggler to walk straight - past his usual radius 249 o->oWigglerWalkAwayFromWallTimer = random_linear_offset(30, 30); 250 } else { 251 if (o->oHealth < 4) { 252 o->oWigglerTargetYaw = o->oAngleToMario; 253 } else if (o->oWigglerTimeUntilRandomTurn != 0) { 254 o->oWigglerTimeUntilRandomTurn--; 255 } else { 256 o->oWigglerTargetYaw = o->oMoveAngleYaw + 0x4000 * (s16) random_sign(); 257 o->oWigglerTimeUntilRandomTurn = random_linear_offset(30, 50); 258 } 259 } 260 } 261 262 // If moving at high speeds, could overflow. But can't reach such speeds 263 // in practice 264 yawTurnSpeed = (s16)(30.0f * o->oForwardVel); 265 cur_obj_rotate_yaw_toward(o->oWigglerTargetYaw, yawTurnSpeed); 266 obj_face_yaw_approach(o->oMoveAngleYaw, 2 * yawTurnSpeed); 267 268 obj_face_pitch_approach(0, 0x320); 269 270 // For the first two seconds of walking, stay invulnerable 271 if (o->oTimer < 60) { 272 obj_check_attacks(&sWigglerHitbox, o->oAction); 273 } else if (obj_handle_attacks(&sWigglerHitbox, o->oAction, sWigglerAttackHandlers)) { 274 if (o->oAction != WIGGLER_ACT_JUMPED_ON) { 275 o->oAction = WIGGLER_ACT_KNOCKBACK; 276 } 277 278 o->oWigglerWalkAwayFromWallTimer = 0; 279 o->oWigglerWalkAnimSpeed = 0.0f; 280 } 281 } 282 } 283 /** 284 * Squish and unsquish, then show text and enter either the walking or shrinking 285 * action. 286 */ 287 static void wiggler_act_jumped_on(void) { 288 // Text to show on first, second, and third attack. 289 s32 attackText[3] = { DIALOG_152, DIALOG_168, DIALOG_151 }; 290 291 // Shrink until the squish speed becomes 0, then unisquish 292 if (approach_f32_ptr(&o->oWigglerSquishSpeed, 0.0f, 0.05f)) { 293 // Note that 4 is the default scale 294 approach_f32_ptr(&o->header.gfx.scale[1], 4.0f, 0.2f); 295 } else { 296 o->header.gfx.scale[1] -= o->oWigglerSquishSpeed; 297 } 298 299 // Wait for a second after unsquishing, then show text and either shrink (if 300 // defeated) or go back to walking 301 if (o->header.gfx.scale[1] >= 4.0f) { 302 if (o->oTimer > 30) { 303 if (cur_obj_update_dialog_with_cutscene(MARIO_DIALOG_LOOK_UP, 304 DIALOG_FLAG_NONE, CUTSCENE_DIALOG, attackText[o->oHealth - 2])) { 305 // Because we don't want the wiggler to disappear after being 306 // defeated, we leave its health at 1 307 if (--o->oHealth == 1) { 308 o->oAction = WIGGLER_ACT_SHRINK; 309 cur_obj_become_intangible(); 310 } else { 311 o->oAction = WIGGLER_ACT_WALK; 312 o->oMoveAngleYaw = o->oFaceAngleYaw; 313 314 if (o->oHealth == 2) { 315 cur_obj_play_sound_2(SOUND_OBJ_WIGGLER_JUMP); 316 o->oForwardVel = 10.0f; 317 o->oVelY = 70.0f; 318 } 319 } 320 } 321 } 322 } else { 323 o->oTimer = 0; 324 } 325 326 obj_check_attacks(&sWigglerHitbox, o->oAction); 327 } 328 329 /** 330 * Decelerate to a stop and then enter the walk action. 331 */ 332 static void wiggler_act_knockback(void) { 333 if (o->oVelY > 0.0f) { 334 o->oFaceAnglePitch -= o->oVelY * 30.0f; 335 } else { 336 obj_face_pitch_approach(0, 0x190); 337 } 338 339 if (obj_forward_vel_approach(0.0f, 1.0f) && o->oFaceAnglePitch == 0) { 340 o->oAction = WIGGLER_ACT_WALK; 341 o->oMoveAngleYaw = o->oFaceAngleYaw; 342 } 343 344 obj_check_attacks(&sWigglerHitbox, o->oAction); 345 } 346 347 /** 348 * Shrink, then spawn the star and enter the fall through floor action. 349 */ 350 static void wiggler_act_shrink(void) { 351 if (o->oTimer >= 20) { 352 if (o->oTimer == 20) { 353 cur_obj_play_sound_2(SOUND_OBJ_ENEMY_DEFEAT_SHRINK); 354 } 355 356 // 4 is the default scale, so shrink to 1/4 of regular size 357 if (approach_f32_ptr(&o->header.gfx.scale[0], 1.0f, 0.1f)) { 358 spawn_default_star(0.0f, 2048.0f, 0.0f); 359 o->oAction = WIGGLER_ACT_FALL_THROUGH_FLOOR; 360 } 361 362 cur_obj_scale(o->header.gfx.scale[0]); 363 } 364 } 365 366 /** 367 * Fall through floors until y < 1700, then enter the walking action. 368 */ 369 static void wiggler_act_fall_through_floor(void) { 370 if (o->oTimer == 60) { 371 stop_background_music(SEQUENCE_ARGS(4, SEQ_EVENT_BOSS)); 372 o->oWigglerFallThroughFloorsHeight = 1700.0f; 373 } else if (o->oTimer > 60) { 374 if (o->oPosY < o->oWigglerFallThroughFloorsHeight) { 375 o->oAction = WIGGLER_ACT_WALK; 376 } else { 377 o->oFaceAnglePitch = obj_get_pitch_from_vel(); 378 } 379 380 cur_obj_move_using_fvel_and_gravity(); 381 } 382 } 383 384 /** 385 * Attack handler for when wiggler is jumped or ground pounded on. 386 * Stop and enter the jumped on action. 387 */ 388 void wiggler_jumped_on_attack_handler(void) { 389 cur_obj_play_sound_2(SOUND_OBJ_WIGGLER_ATTACKED); 390 o->oAction = WIGGLER_ACT_JUMPED_ON; 391 o->oForwardVel = o->oVelY = 0.0f; 392 o->oWigglerSquishSpeed = 0.4f; 393 } 394 395 /** 396 * Update function for bhvWigglerHead. 397 */ 398 void bhv_wiggler_update(void) { 399 // PARTIAL_UPDATE 400 401 if (o->oAction == WIGGLER_ACT_UNINITIALIZED) { 402 wiggler_init_segments(); 403 } else { 404 if (o->oAction == WIGGLER_ACT_FALL_THROUGH_FLOOR) { 405 wiggler_act_fall_through_floor(); 406 } else { 407 treat_far_home_as_mario(1200.0f); 408 409 // Walking animation and sound 410 cur_obj_init_animation_with_accel_and_sound(0, o->oWigglerWalkAnimSpeed); 411 if (o->oWigglerWalkAnimSpeed != 0.0f) { 412 cur_obj_play_sound_at_anim_range(0, 13, 413 o->oHealth >= 4 ? SOUND_OBJ_WIGGLER_LOW_PITCH : SOUND_OBJ_WIGGLER_HIGH_PITCH); 414 } else { 415 cur_obj_reverse_animation(); 416 } 417 418 cur_obj_update_floor_and_walls(); 419 switch (o->oAction) { 420 case WIGGLER_ACT_WALK: 421 wiggler_act_walk(); 422 break; 423 case WIGGLER_ACT_KNOCKBACK: 424 wiggler_act_knockback(); 425 break; 426 case WIGGLER_ACT_JUMPED_ON: 427 wiggler_act_jumped_on(); 428 break; 429 case WIGGLER_ACT_SHRINK: 430 wiggler_act_shrink(); 431 break; 432 case WIGGLER_ACT_FALL_THROUGH_FLOOR: 433 wiggler_act_fall_through_floor(); 434 break; 435 } 436 437 cur_obj_move_standard(-78); 438 } 439 440 // Update segment 0 with data from the wiggler object 441 o->oWigglerSegments[0].posX = o->oPosX; 442 o->oWigglerSegments[0].posY = o->oPosY; 443 o->oWigglerSegments[0].posZ = o->oPosZ; 444 o->oWigglerSegments[0].pitch = o->oFaceAnglePitch; 445 o->oWigglerSegments[0].yaw = o->oFaceAngleYaw; 446 447 // Update the rest of the segments to follow segment 0 448 wiggler_update_segments(); 449 } 450 }