#include #include "sm64.h" #include "audio/external.h" #include "game/game_init.h" #include "game/memory.h" #include "game/area.h" #include "game/save_file.h" #include "game/object_helpers.h" #include "game/ingame_menu.h" #include "game/level_update.h" #include "game/segment2.h" #include "game/segment7.h" #include "game/object_list_processor.h" #include "engine/behavior_script.h" #include "engine/graph_node.h" #include "behavior_data.h" #include "text_strings.h" #include "star_select.h" #include "eu_translation.h" #include /** * @file star_select.c * This file implements how the star select screen (act selector) function. * That includes handles what stars can be selected, star selector types, * strings, act values, and star selector model rendering if a star is collected or not. */ // Star Selector count models printed in the act selector menu. static struct Object *sStarSelectorModels[8]; // The act the course is loaded as, affects whether some objects spawn. static s8 sLoadedActNum; // Number of obtained stars, excluding the coin star. static u8 sObtainedStars; // Total number of stars that appear in the act selector menu. static s8 sVisibleStars; // Act selected when the act menu is first opened. static u8 sInitSelectedActNum; // Index value of the act selected in the act menu. static s8 sSelectedActIndex = 0; // Index value of the star that is selectable in the act menu. // Excluding the next star, it doesn't count other transparent stars. static s8 sSelectableStarIndex = 0; // Act Selector menu timer that keeps counting until you choose an act. static s32 sActSelectorMenuTimer = 0; /** * Act Selector Star Type Loop Action * Defines a select type for a star in the act selector. */ void bhv_act_selector_star_type_loop(void) { switch (gCurrentObject->oStarSelectorType) { // If a star is not selected, don't rotate or change size case STAR_SELECTOR_NOT_SELECTED: gCurrentObject->oStarSelectorSize -= 0.1; if (gCurrentObject->oStarSelectorSize < 1.0) { gCurrentObject->oStarSelectorSize = 1.0; } gCurrentObject->oFaceAngleYaw = 0; break; // If a star is selected, rotate and slightly increase size case STAR_SELECTOR_SELECTED: gCurrentObject->oStarSelectorSize += 0.1; if (gCurrentObject->oStarSelectorSize > 1.3) { gCurrentObject->oStarSelectorSize = 1.3; } gCurrentObject->oFaceAngleYaw += 0x800; break; // If the 100 coin star is selected, rotate case STAR_SELECTOR_100_COINS: gCurrentObject->oFaceAngleYaw += 0x800; break; } // Scale act selector stars depending of the type selected cur_obj_scale(gCurrentObject->oStarSelectorSize); // Unused timer, only referenced here. Probably replaced by sActSelectorMenuTimer gCurrentObject->oStarSelectorTimer++; } /** * Renders the 100 coin star with an special star selector type. */ void render_100_coin_star(u8 stars) { if (stars & (1 << 6)) { // If the 100 coin star has been collected, create a new star selector next to the coin score. sStarSelectorModels[6] = spawn_object_abs_with_rot(gCurrentObject, 0, MODEL_STAR, bhvActSelectorStarType, 370, 24, -300, 0, 0, 0); sStarSelectorModels[6]->oStarSelectorSize = 0.8; sStarSelectorModels[6]->oStarSelectorType = STAR_SELECTOR_100_COINS; } } /** * Act Selector Init Action * Checks how many stars has been obtained in a course, to render * the correct star models, the 100 coin star and also handles * checks of what star should be next in sInitSelectedActNum. */ void bhv_act_selector_init(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 (sInitSelectedActNum == 0) { sInitSelectedActNum = sVisibleStars + 1; sSelectableStarIndex = 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; sInitSelectedActNum = sVisibleStars + 1; sSelectableStarIndex = sVisibleStars; sVisibleStars++; } // If all stars have been collected, set the default selection to the last star. if (sObtainedStars == 6) { sInitSelectedActNum = sVisibleStars; } //! Useless, since sInitSelectedActNum has already been set in this //! scenario by the code that shows the next uncollected star. if (sObtainedStars == 0) { sInitSelectedActNum = 1; } // Render star selector objects for (i = 0; i < sVisibleStars; i++) { sStarSelectorModels[i] = spawn_object_abs_with_rot(gCurrentObject, 0, selectorModelIDs[i], bhvActSelectorStarType, 75 + sVisibleStars * -75 + i * 152, 248, -300, 0, 0, 0); sStarSelectorModels[i]->oStarSelectorSize = 1.0f; } render_100_coin_star(stars); } /** * Act Selector Loop Action * Handles star selector scrolling depending of what stars are * selectable, whenever all 6 stars are obtained or not. * Also handles 2 star selector types whenever the star is selected * or not, the types are defined in bhv_act_selector_star_type_loop. */ void bhv_act_selector_loop(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. sSelectedActIndex = 0; handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sObtainedStars); starIndexCounter = sSelectableStarIndex; 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 == sInitSelectedActNum) { if (starIndexCounter == 0) { // We have reached the sSelectableStarIndex-th selectable star. sSelectedActIndex = i; break; } starIndexCounter--; } } } else { // If all stars are collected then they are all selectable. handle_menu_scrolling(MENU_SCROLL_HORIZONTAL, &sSelectableStarIndex, 0, sVisibleStars - 1); sSelectedActIndex = sSelectableStarIndex; } // Star selector type handler for (i = 0; i < sVisibleStars; i++) { if (sSelectedActIndex == i) { sStarSelectorModels[i]->oStarSelectorType = STAR_SELECTOR_SELECTED; } else { sStarSelectorModels[i]->oStarSelectorType = STAR_SELECTOR_NOT_SELECTED; } } } /** * Print the course number selected with the wood rgba16 course texture. */ #ifdef VERSION_EU void print_course_number(s16 language) { #else void print_course_number(void) { #endif u8 courseNum[4]; create_dl_translation_matrix(MENU_MTX_PUSH, 158.0f, 81.0f, 0.0f); // Full wood texture in JP & US, lower part of it on EU gSPDisplayList(gDisplayListHead++, dl_menu_rgba16_wood_course); #ifdef VERSION_EU // Change upper part of the wood texture depending of the language defined switch (language) { case LANGUAGE_ENGLISH: gSPDisplayList(gDisplayListHead++, dl_menu_texture_course_upper); break; case LANGUAGE_FRENCH: gSPDisplayList(gDisplayListHead++, dl_menu_texture_niveau_upper); break; case LANGUAGE_GERMAN: gSPDisplayList(gDisplayListHead++, dl_menu_texture_kurs_upper); break; } gSPDisplayList(gDisplayListHead++, dl_menu_rgba16_wood_course_end); #endif gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); int_to_str(gCurrCourseNum, courseNum); if (gCurrCourseNum < 10) { // 1 digit number print_hud_lut_string(HUD_LUT_GLOBAL, 152, 158, courseNum); } else { // 2 digit number print_hud_lut_string(HUD_LUT_GLOBAL, 143, 158, courseNum); } gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); } #if defined(VERSION_JP) || defined(VERSION_SH) #define ACT_NAME_X 158 #else #define ACT_NAME_X 163 #endif /** * Print act selector strings, some with special checks. */ void print_act_selector_strings(void) { #ifdef VERSION_EU unsigned char myScore[][10] = { {TEXT_MYSCORE}, {TEXT_MY_SCORE_FR}, {TEXT_MY_SCORE_DE} }; #else unsigned char myScore[] = { TEXT_MYSCORE }; #endif unsigned char starNumbers[] = { TEXT_ZERO }; #ifdef VERSION_EU u8 **levelNameTbl; u8 *currLevelName; u8 **actNameTbl; #else u8 **levelNameTbl = segmented_to_virtual(seg2_course_name_table); u8 *currLevelName = segmented_to_virtual(levelNameTbl[gCurrCourseNum - 1]); u8 **actNameTbl = segmented_to_virtual(seg2_act_name_table); #endif u8 *selectedActName; #ifndef VERSION_EU s16 lvlNameX; s16 actNameX; #endif s8 i; #ifdef VERSION_EU s16 language = eu_get_language(); #endif create_dl_ortho_matrix(); #ifdef VERSION_EU switch (language) { case LANGUAGE_ENGLISH: actNameTbl = segmented_to_virtual(act_name_table_eu_en); levelNameTbl = segmented_to_virtual(course_name_table_eu_en); break; case LANGUAGE_FRENCH: actNameTbl = segmented_to_virtual(act_name_table_eu_fr); levelNameTbl = segmented_to_virtual(course_name_table_eu_fr); break; case LANGUAGE_GERMAN: actNameTbl = segmented_to_virtual(act_name_table_eu_de); levelNameTbl = segmented_to_virtual(course_name_table_eu_de); break; } currLevelName = segmented_to_virtual(levelNameTbl[gCurrCourseNum - 1]); #endif // Print the coin highscore. gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); print_hud_my_score_coins(1, gCurrSaveFileNum - 1, gCurrCourseNum - 1, 155, 106); gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); // Print the "MY SCORE" text if the coin score is more than 0 if (save_file_get_course_coin_score(gCurrSaveFileNum - 1, gCurrCourseNum - 1) != 0) { #ifdef VERSION_EU print_generic_string(95, 118, myScore[language]); #else print_generic_string(102, 118, myScore); #endif } #ifdef VERSION_EU print_generic_string(get_str_x_pos_from_center(160, currLevelName + 3, 10.0f), 33, currLevelName + 3); #elif defined(VERSION_SH) lvlNameX = get_str_x_pos_from_center_scale(160, currLevelName + 3, 10.0f); print_generic_string(lvlNameX, 33, currLevelName + 3); #else lvlNameX = get_str_x_pos_from_center(160, currLevelName + 3, 10.0f); print_generic_string(lvlNameX, 33, currLevelName + 3); #endif gSPDisplayList(gDisplayListHead++, dl_ia_text_end); #ifdef VERSION_EU print_course_number((u32)language); #else print_course_number(); #endif gSPDisplayList(gDisplayListHead++, dl_menu_ia8_text_begin); gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); // Print the name of the selected act. if (sVisibleStars != 0) { selectedActName = segmented_to_virtual(actNameTbl[(gCurrCourseNum - 1) * 6 + sSelectedActIndex]); #ifdef VERSION_EU print_menu_generic_string(get_str_x_pos_from_center(ACT_NAME_X, selectedActName, 8.0f), 81, selectedActName); #elif defined(VERSION_SH) actNameX = get_str_x_pos_from_center_scale(ACT_NAME_X, selectedActName, 8.0f); print_menu_generic_string(actNameX, 81, selectedActName); #else actNameX = get_str_x_pos_from_center(ACT_NAME_X, selectedActName, 8.0f); print_menu_generic_string(actNameX, 81, selectedActName); #endif } // Print the numbers above each star. for (i = 1; i <= sVisibleStars; i++) { starNumbers[0] = i; #ifdef VERSION_EU print_menu_generic_string(143 - sVisibleStars * 15 + i * 30 , 38, starNumbers); #else print_menu_generic_string(i * 34 - sVisibleStars * 17 + 139, 38, starNumbers); #endif } gSPDisplayList(gDisplayListHead++, dl_menu_ia8_text_end); } /** * Geo function that Print act selector strings. *!@bug: This geo function is missing the third param. Harmless in practice due to o32 convention. */ #ifdef AVOID_UB Gfx *geo_act_selector_strings(s16 callContext, UNUSED struct GraphNode *node, UNUSED void *context) { #else Gfx *geo_act_selector_strings(s16 callContext, UNUSED struct GraphNode *node) { #endif if (callContext == GEO_CONTEXT_RENDER) { print_act_selector_strings(); } return NULL; } /** * Initiates act selector values before entering a main course. * Also load how much stars a course has, without counting the 100 coin star. */ s32 lvl_init_act_selector_values_and_stars(UNUSED s32 arg, UNUSED s32 unused) { u8 stars = save_file_get_star_flags(gCurrSaveFileNum - 1, gCurrCourseNum - 1); sLoadedActNum = 0; sInitSelectedActNum = 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--; } //! no return value #ifdef AVOID_UB return 0; #endif } /** * Loads act selector button actions with selected act value checks. * Also updates objects and returns act number selected after is choosen. */ s32 lvl_update_obj_and_load_act_button_actions(UNUSED s32 arg, UNUSED s32 unused) { if (sActSelectorMenuTimer >= 11) { // If any of these buttons are pressed, play sound and go to course act #ifndef VERSION_EU if ((gPlayer3Controller->buttonPressed & A_BUTTON) || (gPlayer3Controller->buttonPressed & START_BUTTON) || (gPlayer3Controller->buttonPressed & B_BUTTON)) { #else if ((gPlayer3Controller->buttonPressed & (A_BUTTON | START_BUTTON | B_BUTTON | Z_TRIG))) { #endif #if defined(VERSION_JP) || defined(VERSION_SH) play_sound(SOUND_MENU_STAR_SOUND, gDefaultSoundArgs); #else play_sound(SOUND_MENU_STAR_SOUND_LETS_A_GO, gDefaultSoundArgs); #endif if (sInitSelectedActNum >= sSelectedActIndex + 1) { sLoadedActNum = sSelectedActIndex + 1; } else { sLoadedActNum = sInitSelectedActNum; } gDialogCourseActNum = sSelectedActIndex + 1; } } area_update_objects(); sActSelectorMenuTimer++; return sLoadedActNum; }