sm64

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

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 }