sm64

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

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 }