#include #include "sm64.h" #include "game.h" #include "memory.h" #include "area.h" #include "save_file.h" #include "object_helpers.h" #include "engine/behavior_script.h" #include "ingame_menu.h" #include "text_strings.h" #include "audio/external.h" #include "level_update.h" #include "segment2.h" #include "segment7.h" #include "star_select.h" #include "model_ids.h" #include "object_list_processor.h" static struct Object *sStarSelectIcons[8]; static s8 sCurrentMission; // The mission the course is loaded as, affects whether some objects spawn. static u8 sObtainedStars; // Number of obtained stars, excluding the coin star. static s8 sVisibleStars; // Total number of stars that appear in the act selector menu static u8 sDefaultSelectedAct; // Act selected when the menu is first opened. extern u8 bhvStarInActSelector[]; static s8 sSelectedAct = 0; static s8 sSelectedStarIndex = 0; static s32 sActSelectorMenuTimer = 0; // Delays the time until you can select an act. void BehStarActSelectorLoop(void) { switch (gCurrentObject->oStarSelectorType) { case STAR_SELECTOR_NOT_SELECTED: gCurrentObject->oStarSelectorSize -= 0.1; if (gCurrentObject->oStarSelectorSize < 1.0) { gCurrentObject->oStarSelectorSize = 1.0; } gCurrentObject->oFaceAngleYaw = 0; break; case STAR_SELECTOR_SELECTED: gCurrentObject->oStarSelectorSize += 0.1; if (gCurrentObject->oStarSelectorSize > 1.3) { gCurrentObject->oStarSelectorSize = 1.3; } gCurrentObject->oFaceAngleYaw += 0x800; break; case STAR_SELECTOR_100_COINS: gCurrentObject->oFaceAngleYaw += 0x800; break; } obj_scale(gCurrentObject->oStarSelectorSize); gCurrentObject->oStarSelectorTimer++; // unused timer field? only ever referenced here } void Show100CoinStar(u8 stars) { if (stars & (1 << 6)) { // If the 100 coin star has been collected, create a new star selector next to the coin score. sStarSelectIcons[6] = spawn_object_abs_with_rot(gCurrentObject, 0, MODEL_STAR, bhvStarInActSelector, 370, 24, -300, 0, 0, 0); sStarSelectIcons[6]->oStarSelectorSize = 0.8; sStarSelectIcons[6]->oStarSelectorType = STAR_SELECTOR_100_COINS; } } void BehActSelectorInit(void) { s16 i = 0; s32 selectorModelIDs[10]; u8 stars = save_file_get_star_flags(gCurrSaveFileNum - 1, gCurrCourseNum - 1); sVisibleStars = 0; while (i != sObtainedStars) { if (stars & (1 << sVisibleStars)) { // Star has been collected selectorModelIDs[sVisibleStars] = MODEL_STAR; i++; } else { // Star has not been collected selectorModelIDs[sVisibleStars] = MODEL_TRANSPARENT_STAR; // If this is the first star that has not been collected, set // the default selection to this star. if (sDefaultSelectedAct == 0) { sDefaultSelectedAct = sVisibleStars + 1; sSelectedStarIndex = sVisibleStars; } } sVisibleStars++; } // If the stars have been collected in order so far, show the next star. if (sVisibleStars == sObtainedStars && sVisibleStars != 6) { selectorModelIDs[sVisibleStars] = MODEL_TRANSPARENT_STAR; sDefaultSelectedAct = sVisibleStars + 1; sSelectedStarIndex = sVisibleStars; sVisibleStars++; } // If all stars have been collected, set the default selection to the last star. if (sObtainedStars == 6) { sDefaultSelectedAct = sVisibleStars; } //! Useless, since sDefaultSelectedAct has already been set in this //! scenario by the code that shows the next uncollected star. if (sObtainedStars == 0) { sDefaultSelectedAct = 1; } for (i = 0; i < sVisibleStars; i++) { // Spawn star selector objects sStarSelectIcons[i] = spawn_object_abs_with_rot(gCurrentObject, 0, selectorModelIDs[i], bhvStarInActSelector, 75 + sVisibleStars * -75 + i * 152, 248, -300, 0, 0, 0); sStarSelectIcons[i]->oStarSelectorSize = 1.0f; } Show100CoinStar(stars); } void BehActSelectorLoop(void) { s8 i; u8 starIndexCounter; u8 stars = save_file_get_star_flags(gCurrSaveFileNum - 1, gCurrCourseNum - 1); if (sObtainedStars != 6) { // Sometimes, stars are not selectable even if they appear on the screen. // This code filters selectable and non-selectable stars. sSelectedAct = 0; handleMenuScrolling(MENU_SCROLL_HORIZONTAL, &sSelectedStarIndex, 0, sObtainedStars); starIndexCounter = sSelectedStarIndex; for (i = 0; i < sVisibleStars; i++) { // Can the star be selected (is it either already completed or the first non-completed mission) if ((stars & (1 << i)) || i + 1 == sDefaultSelectedAct) { if (starIndexCounter == 0) { // We have reached the sSelectedStarIndex-th selectable star. sSelectedAct = i; break; } starIndexCounter--; } } } else { // If all stars are collected then they are all selectable. handleMenuScrolling(MENU_SCROLL_HORIZONTAL, &sSelectedStarIndex, 0, sVisibleStars - 1); sSelectedAct = sSelectedStarIndex; } for (i = 0; i < sVisibleStars; i++) { if (sSelectedAct == i) { sStarSelectIcons[i]->oStarSelectorType = STAR_SELECTOR_SELECTED; } else { sStarSelectIcons[i]->oStarSelectorType = STAR_SELECTOR_NOT_SELECTED; } } } static void ShowCourseNumber(void) { u8 courseNum[4]; dl_add_new_translation_matrix(1, 158.0f, 81.0f, 0.0f); gSPDisplayList(gDisplayListHead++, main_menu_seg7_dl_0700F228); gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); Int2Str(gCurrCourseNum, courseNum); if (gCurrCourseNum < 10) { PutString(2, 152, 158, courseNum); } else { PutString(2, 143, 158, courseNum); } gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); } static void ShowActSelectorMenu(void) { // TODO: EU relocates level and act name tables to translation segment 0x19 #ifndef VERSION_EU #ifdef VERSION_JP unsigned char myScore[] = { TEXT_MY_SCORE }; #else unsigned char myScore[] = { TEXT_MYSCORE }; #endif unsigned char starNumbers[] = { TEXT_0 }; u32 *levelNameTbl = (u32 *) segmented_to_virtual(seg2_level_name_table); u8 *currLevelName = (u8 *) segmented_to_virtual((void *) levelNameTbl[gCurrCourseNum - 1]); u32 *actNameTbl = (u32 *) segmented_to_virtual(seg2_act_name_table); u8 *selectedActName; s16 x; s16 x2; s8 i; dl_add_new_ortho_matrix(); // Display the coin highscore. gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); ShowCoins(1, gCurrSaveFileNum - 1, gCurrCourseNum - 1, 155, 106); gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); gSPDisplayList(gDisplayListHead++, dl_ia8_text_begin); gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); // Display the "MY SCORE" text if (save_file_get_course_coin_score(gCurrSaveFileNum - 1, gCurrCourseNum - 1) != 0) { PrintGenericText(102, 118, myScore); } // Display the level name; add 3 to skip the number and spacing to get to the actual string to center. x = get_str_x_pos_from_center(160, currLevelName + 3, 10.0f); PrintGenericText(x, 33, currLevelName + 3); // Display the course number. gSPDisplayList(gDisplayListHead++, dl_ia8_text_end); ShowCourseNumber(); gSPDisplayList(gDisplayListHead++, main_menu_seg7_dl_0700D108); gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); // Display the name of the selected act. if (sVisibleStars != 0) { selectedActName = (u8 *) segmented_to_virtual((void *) (actNameTbl[(gCurrCourseNum - 1) * 6 + sSelectedAct])); #ifdef VERSION_JP x2 = get_str_x_pos_from_center(158, selectedActName, 8.0f); #else x2 = get_str_x_pos_from_center(163, selectedActName, 8.0f); #endif PrintRegularText(x2, 81, selectedActName); } // Display the numbers above each star. for (i = 1; i <= sVisibleStars; i++) { starNumbers[0] = i; PrintRegularText(i * 34 - sVisibleStars * 17 + 0x8B, 38, starNumbers); } gSPDisplayList(gDisplayListHead++, main_menu_seg7_dl_0700D160); #endif // !VERSION_EU } //! @bug Another geo function missing the third param. Harmless in practice due to o32 convention. Gfx *Geo18_80177518(s16 run, UNUSED struct GraphNode *node) { if (run == TRUE) { ShowActSelectorMenu(); } return NULL; } void LevelProc_80177560(UNUSED s32 a, UNUSED s32 b) { u8 stars = save_file_get_star_flags(gCurrSaveFileNum - 1, gCurrCourseNum - 1); sCurrentMission = 0; sDefaultSelectedAct = 0; sVisibleStars = 0; sActSelectorMenuTimer = 0; sObtainedStars = save_file_get_course_star_count(gCurrSaveFileNum - 1, gCurrCourseNum - 1); // Don't count 100 coin star if (stars & (1 << 6)) { sObtainedStars--; } } int LevelProc_80177610(UNUSED s32 a, UNUSED s32 b) { if (sActSelectorMenuTimer >= 11) { if ((gPlayer3Controller->buttonPressed & A_BUTTON) || (gPlayer3Controller->buttonPressed & START_BUTTON) || (gPlayer3Controller->buttonPressed & B_BUTTON)) { #ifdef VERSION_JP play_sound(SOUND_MENU_STARSOUND, gDefaultSoundArgs); #else play_sound(SOUND_MENU_STARSOUNDLETSAGO, gDefaultSoundArgs); #endif if (sDefaultSelectedAct > sSelectedAct) { sCurrentMission = sSelectedAct + 1; } else { sCurrentMission = sDefaultSelectedAct; } D_80330534 = sSelectedAct + 1; } } area_update_objects(); sActSelectorMenuTimer++; return sCurrentMission; }