// ukiki.c.inc /** * @file Contains behavior for the ukiki objects. * * Hat ukiki is the ukiki that steals Mario's hat. * Cage ukiki is the ukiki that triggers the cage star. */ /** * Sets the hat ukiki to its home if Mario is far away * or makes him wait to respawn if in water. */ void handle_hat_ukiki_reset(void) { if (o->oBehParams2ndByte == UKIKI_HAT) { if (cur_obj_mario_far_away()) { cur_obj_set_pos_to_home_and_stop(); o->oAction = UKIKI_ACT_IDLE; } else if (o->oMoveFlags & OBJ_MOVE_MASK_IN_WATER) { o->oAction = UKIKI_ACT_WAIT_TO_RESPAWN; } } } /** * Returns TRUE if Mario has his hat and ukiki is * the hat ukiki. */ s32 is_hat_ukiki_and_mario_has_hat(void) { if (o->oBehParams2ndByte == UKIKI_HAT) { if (does_mario_have_hat(gMarioState)) { return TRUE; } } return FALSE; } /** * Unused copy of geo_update_projectile_pos_from_parent. Perhaps a copy paste mistake. */ Gfx *geo_update_projectile_pos_from_parent_copy(s32 run,UNUSED struct GraphNode *node, Mat4 mtx) { Mat4 mtx2; struct Object* obj; if (run == TRUE) { // TODO: change global type to Object pointer obj = (struct Object*)gCurGraphNodeObject; if (obj->prevObj != NULL) { create_transformation_from_matrices(mtx2, mtx, gCurGraphNodeCamera->matrixPtr); obj_update_pos_from_parent_transformation(mtx2, obj->prevObj); obj_set_gfx_pos_from_pos(obj->prevObj); } } return NULL; } /** * Chooses random idle taunts and loops them a random number of times. */ void idle_ukiki_taunt(void) { o->oForwardVel = 0.0f; if (o->oSubAction == UKIKI_SUB_ACT_TAUNT_NONE) { // Subaction is between 1 and 4. o->oSubAction = (s32)(random_float() * 4.0f + 1.0f); // Counter keeps track of the iterations done, while ToBeDone // is the count of iterations to be done (between 2 and 5). o->oUkikiTauntCounter = 0; o->oUkikiTauntsToBeDone = (s16)(random_float() * 4.0f + 2.0f); } // Switch goes from 1-4. switch(o->oSubAction) { case UKIKI_SUB_ACT_TAUNT_ITCH: cur_obj_init_animation_with_sound(UKIKI_ANIM_ITCH); if (cur_obj_check_if_near_animation_end()) { o->oSubAction = UKIKI_SUB_ACT_TAUNT_NONE; } break; case UKIKI_SUB_ACT_TAUNT_SCREECH: cur_obj_init_animation_with_sound(UKIKI_ANIM_SCREECH); if (cur_obj_check_if_near_animation_end()) { o->oUkikiTauntCounter++; } if (o->oUkikiTauntCounter >= o->oUkikiTauntsToBeDone * 2) { o->oSubAction = UKIKI_SUB_ACT_TAUNT_NONE; } break; case UKIKI_SUB_ACT_TAUNT_JUMP_CLAP: cur_obj_init_animation_with_sound(UKIKI_ANIM_JUMP_CLAP); if (cur_obj_check_if_near_animation_end()) { o->oUkikiTauntCounter++; } if (o->oUkikiTauntCounter >= o->oUkikiTauntsToBeDone) { o->oSubAction = UKIKI_SUB_ACT_TAUNT_NONE; } break; case UKIKI_SUB_ACT_TAUNT_HANDSTAND: cur_obj_init_animation_with_sound(UKIKI_ANIM_HANDSTAND); if (cur_obj_check_if_near_animation_end()) { o->oSubAction = UKIKI_SUB_ACT_TAUNT_NONE; } break; } } /** * Ukiki's general idle action. This is for when ukiki is doing nothing else and * standing around. */ void ukiki_act_idle(void) { idle_ukiki_taunt(); if (is_hat_ukiki_and_mario_has_hat()) { if (o->oDistanceToMario > 700.0f && o->oDistanceToMario < 1000.0f) { o->oAction = UKIKI_ACT_RUN; } else if (o->oDistanceToMario <= 700.0f && 200.0f < o->oDistanceToMario) { if (abs_angle_diff(o->oAngleToMario, o->oMoveAngleYaw) > 0x1000) { o->oAction = UKIKI_ACT_TURN_TO_MARIO; } } } else if (o->oDistanceToMario < 300.0f) { o->oAction = UKIKI_ACT_RUN; } if (o->oUkikiTextState == UKIKI_TEXT_GO_TO_CAGE) { o->oAction = UKIKI_ACT_GO_TO_CAGE; } // Jump away from Mario after stealing his hat. if (o->oUkikiTextState == UKIKI_TEXT_STOLE_HAT) { o->oMoveAngleYaw = gMarioObject->oMoveAngleYaw + 0x8000; if (check_if_moving_over_floor(50.0f, 150.0f)) { o->oAction = UKIKI_ACT_JUMP; } else { o->oMoveAngleYaw = gMarioObject->oMoveAngleYaw + 0x4000; if (check_if_moving_over_floor(50.0f, 150.0f)) { o->oAction = UKIKI_ACT_JUMP; } else { o->oMoveAngleYaw = gMarioObject->oMoveAngleYaw - 0x4000; if (check_if_moving_over_floor(50.0f, 150.0f)) { o->oAction = UKIKI_ACT_JUMP; } } } o->oUkikiTextState = UKIKI_TEXT_HAS_HAT; } if (o->oBehParams2ndByte == UKIKI_HAT) { if (o->oPosY < -1550.0f) { o->oAction = UKIKI_ACT_RETURN_HOME; } } } /** * Ukiki attempts to run home, which is often impossible depending on terrain. * Only used for the hat ukiki. */ void ukiki_act_return_home(void) { UNUSED s32 unused; cur_obj_init_animation_with_sound(UKIKI_ANIM_RUN); o->oMoveAngleYaw = cur_obj_angle_to_home(); o->oForwardVel = 10.0f; // If ukiki somehow walked home, go back to the idle action. if (o->oPosY > -1550.0f) { o->oAction = UKIKI_ACT_IDLE; } } /** * Ukiki has gone somewhere he shouldn't, so wait until Mario * leaves then reset position to his home. */ void ukiki_act_wait_to_respawn(void) { idle_ukiki_taunt(); if (cur_obj_mario_far_away()) { cur_obj_set_pos_to_home_and_stop(); o->oAction = UKIKI_ACT_IDLE; } } /** * A seemgingly stubbed unused action. * * Perhaps an early attempt at the UKIKI_SUB_ACT_CAGE_WAIT_FOR_MARIO * part of the ukiki_act_go_to_cage action. */ void ukiki_act_unused_turn(void) { idle_ukiki_taunt(); if (o->oSubAction == UKIKI_SUB_ACT_TAUNT_JUMP_CLAP) { cur_obj_rotate_yaw_toward(o->oAngleToMario, 0x400); } } /** * Turns ukiki to face towards Mario while moving with slow forward velocity. */ void ukiki_act_turn_to_mario(void) { s32 facingMario; // Initialize the action with a random fVel from 2-5. if (o->oTimer == 0) { o->oForwardVel = random_float() * 3.0f + 2.0f; } cur_obj_init_animation_with_sound(UKIKI_ANIM_TURN); facingMario = cur_obj_rotate_yaw_toward(o->oAngleToMario, 0x800); if (facingMario) { o->oAction = UKIKI_ACT_IDLE; } if (is_hat_ukiki_and_mario_has_hat()){ if (o->oDistanceToMario > 500.0f) { o->oAction = UKIKI_ACT_RUN; } } else if (o->oDistanceToMario < 300.0f) { o->oAction = UKIKI_ACT_RUN; } } /** * Ukiki either runs away away from Mario or towards him if stealing Mario's cap. */ void ukiki_act_run(void) { s32 fleeMario = TRUE; s16 goalYaw = o->oAngleToMario + 0x8000; if (is_hat_ukiki_and_mario_has_hat()) { fleeMario = FALSE; goalYaw = o->oAngleToMario; } if (o->oTimer == 0) { o->oUkikiChaseFleeRange = random_float() * 100.0f + 350.0f; } cur_obj_init_animation_with_sound(UKIKI_ANIM_RUN); cur_obj_rotate_yaw_toward(goalYaw, 0x800); //! @bug (Ukikispeedia) This function sets forward speed to 0.9 * Mario's //! forward speed, which means ukiki can move at hyperspeed rates. cur_obj_set_vel_from_mario_vel(20.0f, 0.9f); if (fleeMario) { if (o->oDistanceToMario > o->oUkikiChaseFleeRange) { o->oAction = UKIKI_ACT_TURN_TO_MARIO; } } else if (o->oDistanceToMario < o->oUkikiChaseFleeRange) { o->oAction = UKIKI_ACT_TURN_TO_MARIO; } if (fleeMario) { if (o->oDistanceToMario < 200.0f) { if((o->oMoveFlags & OBJ_MOVE_HIT_WALL) && is_mario_moving_fast_or_in_air(10)) { o->oAction = UKIKI_ACT_JUMP; o->oMoveAngleYaw = o->oWallAngle; } else if((o->oMoveFlags & OBJ_MOVE_HIT_EDGE)) { if (is_mario_moving_fast_or_in_air(10)) { o->oAction = UKIKI_ACT_JUMP; o->oMoveAngleYaw += 0x8000; } } } } } /** * Jump for a distance, typically used to jump * over Mario when after reaching a wall or edge. */ void ukiki_act_jump(void) { o->oForwardVel = 10.0f; cur_obj_become_intangible(); if (o->oSubAction == 0) { if (o->oTimer == 0) { cur_obj_set_y_vel_and_animation(random_float() * 10.0f + 45.0f, UKIKI_ANIM_JUMP); } else if (o->oMoveFlags & OBJ_MOVE_MASK_NOT_AIR) { o->oSubAction++; o->oVelY = 0.0f; } } else { o->oForwardVel = 0.0f; cur_obj_init_animation_with_sound(UKIKI_ANIM_LAND); cur_obj_become_tangible(); if (cur_obj_check_if_near_animation_end()) { o->oAction = UKIKI_ACT_RUN; } } } /** * Waypoints that lead from the top of the mountain to the cage. * * TODO: Convert to an array of waypoints, perhaps? -1 is tricky. */ s16 sCageUkikiPath[] = { 0, 1011, 2306, -285, 0, 1151, 2304, -510, 0, 1723, 1861, -964, 0, 2082, 1775, -1128, 0, 2489, 1717, -1141, 0, 2662, 1694, -1140, 0, 2902, 1536, -947, 0, 2946, 1536, -467, 0, 2924, 1536, 72, 0, 2908, 1536, 536, 0, 2886, 1536, 783, -1, }; /** * Travel to the cage, wait for Mario, jump to it, and ride it to * our death. Ukiki is a tad suicidal. */ void ukiki_act_go_to_cage(void) { struct Object* obj; f32 latDistToCage = 0.0f; s16 yawToCage = 0; obj = cur_obj_nearest_object_with_behavior(bhvUkikiCageChild); // Ultimately is checking the cage, as it points to the parent // of a dummy child object of the cage. if (obj != NULL) { latDistToCage = lateral_dist_between_objects(o, obj->parentObj); yawToCage = obj_angle_to_object(o, obj->parentObj); } cur_obj_become_intangible(); o->oFlags |= OBJ_FLAG_ACTIVE_FROM_AFAR; // Switch goes from 0-7 in order. switch(o->oSubAction) { case UKIKI_SUB_ACT_CAGE_RUN_TO_CAGE: cur_obj_init_animation_with_sound(UKIKI_ANIM_RUN); o->oPathedWaypointsS16 = sCageUkikiPath; if (cur_obj_follow_path(0) != PATH_REACHED_END) { o->oForwardVel = 10.0f; cur_obj_rotate_yaw_toward(o->oPathedTargetYaw, 0x400); o->oPosY = o->oFloorHeight; } else { o->oForwardVel = 0.0f; o->oSubAction++; } break; case UKIKI_SUB_ACT_CAGE_WAIT_FOR_MARIO: cur_obj_init_animation_with_sound(UKIKI_ANIM_JUMP_CLAP); cur_obj_rotate_yaw_toward(o->oAngleToMario, 0x400); if (cur_obj_can_mario_activate_textbox(200.0f, 30.0f, 0x7FFF)) { o->oSubAction++; // fallthrough } else { break; } case UKIKI_SUB_ACT_CAGE_TALK_TO_MARIO: cur_obj_init_animation_with_sound(UKIKI_ANIM_HANDSTAND); if (cur_obj_update_dialog_with_cutscene(3, 1, CUTSCENE_DIALOG, DIALOG_080)) { o->oSubAction++; } break; case UKIKI_SUB_ACT_CAGE_TURN_TO_CAGE: cur_obj_init_animation_with_sound(UKIKI_ANIM_RUN); if (cur_obj_rotate_yaw_toward(yawToCage, 0x400)) { o->oForwardVel = 10.0f; o->oSubAction++; } break; case UKIKI_SUB_ACT_CAGE_JUMP_TO_CAGE: cur_obj_set_y_vel_and_animation(55.0f, UKIKI_ANIM_JUMP); o->oForwardVel = 36.0f; o->oSubAction++; break; case UKIKI_SUB_ACT_CAGE_LAND_ON_CAGE: if (latDistToCage < 50.0f) { o->oForwardVel = 0.0f; } if (o->oMoveFlags & OBJ_MOVE_LANDED) { play_puzzle_jingle(); cur_obj_init_animation_with_sound(UKIKI_ANIM_JUMP_CLAP); o->oSubAction++; o->oUkikiCageSpinTimer = 32; obj->parentObj->oUkikiCageNextAction = UKIKI_CAGE_ACT_SPIN; o->oForwardVel = 0.0f; } break; case UKIKI_SUB_ACT_CAGE_SPIN_ON_CAGE: o->oMoveAngleYaw += 0x800; o->oUkikiCageSpinTimer--; if (o->oUkikiCageSpinTimer < 0) { o->oSubAction++; obj->parentObj->oUkikiCageNextAction = UKIKI_CAGE_ACT_FALL; } break; case UKIKI_SUB_ACT_CAGE_DESPAWN: if (o->oPosY < -1300.0f) { obj_mark_for_deletion(o); } break; } } /** * A struct of Ukiki's sounds based on his current * SoundState number. */ struct SoundState sUkikiSoundStates[] = { {1, 1, 10, SOUND_OBJ_UKIKI_STEP_DEFAULT}, {0, 0, 0, NO_SOUND}, {0, 0, 0, NO_SOUND}, {0, 0, 0, NO_SOUND}, {1, 0, -1, SOUND_OBJ_UKIKI_CHATTER_SHORT}, {1, 0, -1, SOUND_OBJ_UKIKI_CHATTER_LONG}, {0, 0, 0, NO_SOUND}, {0, 0, 0, NO_SOUND}, {1, 0, -1, SOUND_OBJ_UKIKI_CHATTER_LONG}, {1, 0, -1, SOUND_OBJ_UKIKI_STEP_LEAVES}, {1, 0, -1, SOUND_OBJ_UKIKI_CHATTER_IDLE}, {0, 0, 0, NO_SOUND}, {0, 0, 0, NO_SOUND}, }; /** * An array of each of Ukiki's actions. A function is called * every frame. */ void (*sUkikiActions[])(void) = { ukiki_act_idle, ukiki_act_run, ukiki_act_turn_to_mario, ukiki_act_jump, ukiki_act_go_to_cage, ukiki_act_wait_to_respawn, ukiki_act_unused_turn, ukiki_act_return_home, }; /** * Called via the main behavior function when Ukiki is either nothing * being held, dropped, or thrown. */ void ukiki_free_loop(void) { s32 steepSlopeAngleDegrees; cur_obj_update_floor_and_walls(); cur_obj_call_action_function(sUkikiActions); if (o->oAction == UKIKI_ACT_GO_TO_CAGE || o->oAction == UKIKI_ACT_RETURN_HOME) { steepSlopeAngleDegrees = -88; } else { steepSlopeAngleDegrees = -20; } cur_obj_move_standard(steepSlopeAngleDegrees); handle_hat_ukiki_reset(); if(!(o->oMoveFlags & OBJ_MOVE_MASK_IN_WATER)) { exec_anim_sound_state(sUkikiSoundStates); } } /** * Unused function for timing ukiki's blinking. * Image still present in Ukiki's actor graphics. * * Possibly unused so AnimState could be used for wearing a hat? */ static void ukiki_blink_timer(void) { if (gGlobalTimer % 50 < 7) { o->oAnimState = UKIKI_ANIM_STATE_EYE_CLOSED; } else { o->oAnimState = UKIKI_ANIM_STATE_DEFAULT; } } /** * Called by the main behavior function for the cage ukiki whenever it is held. */ void cage_ukiki_held_loop(void) { if (o->oPosY - o->oHomeY > -100.0f) { switch(o->oUkikiTextState) { case UKIKI_TEXT_DEFAULT: if (set_mario_npc_dialog(2) == 2) { create_dialog_box_with_response(DIALOG_079); o->oUkikiTextState = UKIKI_TEXT_CAGE_TEXTBOX; } break; case UKIKI_TEXT_CAGE_TEXTBOX: if (gDialogResponse != 0) { set_mario_npc_dialog(0); if (gDialogResponse == 1) { o->oInteractionSubtype |= INT_SUBTYPE_DROP_IMMEDIATELY; o->oUkikiTextState = UKIKI_TEXT_GO_TO_CAGE; } else { o->oUkikiTextState = UKIKI_TEXT_DO_NOT_LET_GO; o->oUkikiTextboxTimer = 60; } } break; case UKIKI_TEXT_GO_TO_CAGE: break; // Pester Mario with textboxes to discourage walking far. case UKIKI_TEXT_DO_NOT_LET_GO: if (o->oUkikiTextboxTimer-- < 0) { o->oUkikiTextState = UKIKI_TEXT_DEFAULT; } break; } } else { // If ukiki is far below his home, stop him from trying to // walk to the cage and getting stuck. o->oUkikiTextState = UKIKI_TEXT_DEFAULT; o->oTimer = 0; o->oAction = UKIKI_ACT_WAIT_TO_RESPAWN; } } /** * Called by the main behavior function for the hat ukiki whenever it is held. */ void hat_ukiki_held_loop(void) { switch(o->oUkikiTextState) { case UKIKI_TEXT_DEFAULT: if (mario_lose_cap_to_enemy(2)) { o->oUkikiTextState = UKIKI_TEXT_STEAL_HAT; o->oUkikiHasHat |= UKIKI_HAT_ON; } else {} break; case UKIKI_TEXT_STEAL_HAT: if (cur_obj_update_dialog(2, 2, DIALOG_100, 0)) { o->oInteractionSubtype |= INT_SUBTYPE_DROP_IMMEDIATELY; o->oUkikiTextState = UKIKI_TEXT_STOLE_HAT; } break; case UKIKI_TEXT_STOLE_HAT: break; case UKIKI_TEXT_HAS_HAT: if (cur_obj_update_dialog(2, 18, DIALOG_101, 0)) { mario_retrieve_cap(); set_mario_npc_dialog(0); o->oUkikiHasHat &= ~UKIKI_HAT_ON; o->oUkikiTextState = UKIKI_TEXT_GAVE_HAT_BACK; } break; case UKIKI_TEXT_GAVE_HAT_BACK: o->oUkikiTextState = UKIKI_TEXT_DEFAULT; o->oAction = UKIKI_ACT_IDLE; break; } } /** * Initializatation for ukiki, determines if it has Mario's hat. */ void bhv_ukiki_init(void) { if (o->oBehParams2ndByte == UKIKI_HAT) { if (save_file_get_flags() & SAVE_FLAG_CAP_ON_UKIKI) { o->oUkikiTextState = UKIKI_TEXT_HAS_HAT; o->oUkikiHasHat |= UKIKI_HAT_ON; } } } /** * The main behavior function for ukiki. Chooses which behavior to use * dependent on the held state and whick ukiki it is (cage or hat). */ void bhv_ukiki_loop(void) { switch(o->oHeldState) { case HELD_FREE: //! @bug (PARTIAL_UPDATE) o->oUkikiTextboxTimer = 0; ukiki_free_loop(); break; case HELD_HELD: cur_obj_unrender_and_reset_state(UKIKI_ANIM_HELD, 0); obj_copy_pos(o, gMarioObject); if (o->oBehParams2ndByte == UKIKI_HAT) { hat_ukiki_held_loop(); } else { cage_ukiki_held_loop(); } break; case HELD_THROWN: case HELD_DROPPED: cur_obj_get_dropped(); break; } if (o->oUkikiHasHat & UKIKI_HAT_ON) { o->oAnimState = UKIKI_ANIM_STATE_HAT_ON; } else { o->oAnimState = UKIKI_ANIM_STATE_DEFAULT; } o->oInteractStatus = 0; print_debug_bottom_up("mode %d\n", o->oAction); print_debug_bottom_up("action %d\n", o->oHeldState); }