From 08223110ff9686ecdc22944dc06663548014b30c Mon Sep 17 00:00:00 2001 From: Reonu Date: Sun, 9 May 2021 22:34:22 +0100 Subject: [PATCH] Widescreen support. --- enhancements/wide.diff | 716 +++++++++++ enhancements/widescreenv2_sm64.patch | 198 +++ enhancements/widescreenv2_ultrasm64.patch | 227 ++++ include/config.h.orig | 45 + include/text_strings.h.in | 4 + src/game/ingame_menu.c | 56 +- src/game/level_update.c | 4 + src/game/level_update.c.orig | 1324 +++++++++++++++++++++ src/game/level_update.h | 1 + src/game/rendering_graph_node.c | 12 +- src/game/rendering_graph_node.c.orig | 1092 +++++++++++++++++ src/menu/file_select.c | 2 + src/menu/star_select.c | 33 +- 13 files changed, 3702 insertions(+), 12 deletions(-) create mode 100644 enhancements/wide.diff create mode 100644 enhancements/widescreenv2_sm64.patch create mode 100644 enhancements/widescreenv2_ultrasm64.patch create mode 100644 include/config.h.orig create mode 100644 src/game/level_update.c.orig create mode 100644 src/game/rendering_graph_node.c.orig diff --git a/enhancements/wide.diff b/enhancements/wide.diff new file mode 100644 index 00000000..2fba1ec2 --- /dev/null +++ b/enhancements/wide.diff @@ -0,0 +1,716 @@ + +diff --git a/enhancements/widescreenv2_sm64.patch b/enhancements/widescreenv2_sm64.patch +new file mode 100644 +index 0000000..d7f27ad +--- /dev/null ++++ b/enhancements/widescreenv2_sm64.patch +@@ -0,0 +1,198 @@ ++diff --git a/include/text_strings.h.in b/include/text_strings.h.in ++index 749179b..3b23b41 100644 ++--- a/include/text_strings.h.in +++++ b/include/text_strings.h.in ++@@ -24,6 +24,10 @@ ++ // Ingame Menu ++ #define TEXT_PAUSE _("PAUSE") // Pause text, Castle Courses ++ #define TEXT_HUD_CONGRATULATIONS _("CONGRATULATIONS") // Course Complete Text, Bowser Courses +++#define TEXT_HUD_CURRENT_RATIO_43 _("CURRENT ASPECT RATIO: 4:3. PRESS L TO SWITCH TO 16:9") +++#define TEXT_HUD_CURRENT_RATIO_169 _("CURRENT ASPECT RATIO: 16:9. PRESS L TO SWITCH TO 4:3") +++#define TEXT_HUD_WIDE_INFO _("PLEASE CONFIGURE YOUR DISPLAY OR YOUR EMULATOR TO") +++#define TEXT_HUD_WIDE_INFO2 _("STRETCH THE IMAGE TO 16:9") ++ ++ #if defined(VERSION_JP) || defined(VERSION_SH) ++ ++diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c ++index 8190f88..deb69f5 100644 ++--- a/src/game/ingame_menu.c +++++ b/src/game/ingame_menu.c ++@@ -34,6 +34,11 @@ s16 gDialogY; // D_8032F69C ++ s16 gCutsceneMsgXOffset; ++ s16 gCutsceneMsgYOffset; ++ s8 gRedCoinsCollected; +++u8 textCurrRatio43[] = { TEXT_HUD_CURRENT_RATIO_43 }; +++u8 textCurrRatio169[] = { TEXT_HUD_CURRENT_RATIO_169 }; +++u8 textWideInfo[] = { TEXT_HUD_WIDE_INFO }; +++u8 textWideInfo2[] = { TEXT_HUD_WIDE_INFO2 }; +++extern u8 widescreen = 0; ++ ++ extern u8 gLastCompletedCourseNum; ++ extern u8 gLastCompletedStarNum; ++@@ -2266,6 +2271,14 @@ void render_pause_my_score_coins(void) { ++ print_generic_string(get_string_width(gTextCourseArr[gInGameLanguage]) + 51, 157, strCourseNum); ++ #else ++ print_generic_string(CRS_NUM_X1, 157, strCourseNum); +++ if (widescreen == 0) { +++ print_generic_string(10, 20, textCurrRatio43); +++ } +++ else { +++ print_generic_string(10, 20, textCurrRatio169); +++ print_generic_string(10, 200, textWideInfo); +++ print_generic_string(10, 180, textWideInfo2); +++ } ++ #endif ++ ++ actName = segmented_to_virtual(actNameTbl[(gCurrCourseNum - 1) * 6 + gDialogCourseActNum - 1]); ++@@ -2557,10 +2570,25 @@ void render_pause_castle_main_strings(s16 x, s16 y) { ++ } ++ } ++ } +++ if (gPlayer1Controller->buttonPressed & L_TRIG){ +++ if (widescreen == 0){ +++ widescreen = 1; +++ } +++ else{ +++ widescreen = 0; +++ } +++ } ++ ++ gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); ++ gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); ++- +++ if (widescreen == 0) { +++ print_generic_string(10, 20, textCurrRatio43); +++ } +++ else { +++ print_generic_string(10, 20, textCurrRatio169); +++ print_generic_string(10, 200, textWideInfo); +++ print_generic_string(10, 180, textWideInfo2); +++ } ++ if (gDialogLineNum < COURSE_STAGES_COUNT) { ++ courseName = segmented_to_virtual(courseNameTbl[gDialogLineNum]); ++ render_pause_castle_course_stars(x, y, gCurrSaveFileNum - 1, gDialogLineNum); ++@@ -2626,6 +2654,14 @@ s16 render_pause_courses_and_castle(void) { ++ shade_screen(); ++ render_pause_my_score_coins(); ++ render_pause_red_coins(); +++ if (gPlayer1Controller->buttonPressed & L_TRIG){ +++ if (widescreen == 0){ +++ widescreen = 1; +++ } +++ else{ +++ widescreen = 0; +++ } +++ } ++ ++ if (gMarioStates[0].action & ACT_FLAG_PAUSE_EXIT) { ++ render_pause_course_options(99, 93, &gDialogLineNum, 15); ++diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c ++index 58238e8..aabdb85 100644 ++--- a/src/game/rendering_graph_node.c +++++ b/src/game/rendering_graph_node.c ++@@ -10,6 +10,7 @@ ++ #include "rendering_graph_node.h" ++ #include "shadow.h" ++ #include "sm64.h" +++#define WIDESCREEN ++ ++ /** ++ * This file contains the code that processes the scene graph for rendering. ++@@ -235,6 +236,7 @@ static void geo_process_ortho_projection(struct GraphNodeOrthoProjection *node) ++ /** ++ * Process a perspective projection node. ++ */ +++extern u8 widescreen; ++ static void geo_process_perspective(struct GraphNodePerspective *node) { ++ if (node->fnNode.func != NULL) { ++ node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); ++@@ -246,7 +248,13 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { ++ #ifdef VERSION_EU ++ f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; ++ #else ++- f32 aspect = (f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height; +++ f32 aspect; +++ if (widescreen == 1){ +++ aspect = 1.775f; +++ } +++ else{ +++ aspect = 1.33333f; +++ } ++ #endif ++ ++ guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); ++diff --git a/src/menu/file_select.c b/src/menu/file_select.c ++index c894797..dc759f7 100644 ++--- a/src/menu/file_select.c +++++ b/src/menu/file_select.c ++@@ -2854,10 +2854,12 @@ Gfx *geo_file_select_strings_and_menu_cursor(s32 callContext, UNUSED struct Grap ++ * Relocates cursor position of the last save if the game goes back to the Mario Screen ++ * either completing a course choosing "SAVE & QUIT" or having a game over. ++ */ +++extern u8 widescreen; ++ s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused) { ++ #ifdef VERSION_EU ++ s8 fileNum; ++ #endif +++ widescreen = 0; ++ sSelectedButtonID = MENU_BUTTON_NONE; ++ sCurrentMenuLevel = MENU_LAYER_MAIN; ++ sTextBaseAlpha = 0; ++diff --git a/src/menu/star_select.c b/src/menu/star_select.c ++index 025dbf2..d6aaa7e 100644 ++--- a/src/menu/star_select.c +++++ b/src/menu/star_select.c ++@@ -52,7 +52,7 @@ static s8 sSelectableStarIndex = 0; ++ ++ // Act Selector menu timer that keeps counting until you choose an act. ++ static s32 sActSelectorMenuTimer = 0; ++- +++extern u8 widescreen; ++ /** ++ * Act Selector Star Type Loop Action ++ * Defines a select type for a star in the act selector. ++@@ -92,8 +92,15 @@ void bhv_act_selector_star_type_loop(void) { ++ 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); +++ if (widescreen == 1){ +++ sStarSelectorModels[6] = spawn_object_abs_with_rot(gCurrentObject, 0, MODEL_STAR, +++ bhvActSelectorStarType, ((370*4.0f)/3), 24, -300, 0, 0, 0); +++ } +++ else{ +++ 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; ++ } ++@@ -147,11 +154,21 @@ void bhv_act_selector_init(void) { ++ } ++ ++ // 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; +++ if (widescreen == 1) { +++ for (i = 0; i < sVisibleStars; i++) { +++ sStarSelectorModels[i] = +++ spawn_object_abs_with_rot(gCurrentObject, 0, selectorModelIDs[i], bhvActSelectorStarType, +++ (((75 + sVisibleStars * -75 + i * 152)*4.0f)/3), 248, -300, 0, 0, 0); +++ sStarSelectorModels[i]->oStarSelectorSize = 1.0f; +++ } +++ } +++ else { +++ 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); +diff --git a/enhancements/widescreenv2_ultrasm64.patch b/enhancements/widescreenv2_ultrasm64.patch +new file mode 100644 +index 0000000..667e6e2 +--- /dev/null ++++ b/enhancements/widescreenv2_ultrasm64.patch +@@ -0,0 +1,227 @@ ++diff --git a/include/text_strings.h.in b/include/text_strings.h.in ++index 749179b..3b23b41 100644 ++--- a/include/text_strings.h.in +++++ b/include/text_strings.h.in ++@@ -24,6 +24,10 @@ ++ // Ingame Menu ++ #define TEXT_PAUSE _("PAUSE") // Pause text, Castle Courses ++ #define TEXT_HUD_CONGRATULATIONS _("CONGRATULATIONS") // Course Complete Text, Bowser Courses +++#define TEXT_HUD_CURRENT_RATIO_43 _("CURRENT ASPECT RATIO: 4:3. PRESS L TO SWITCH TO 16:9") +++#define TEXT_HUD_CURRENT_RATIO_169 _("CURRENT ASPECT RATIO: 16:9. PRESS L TO SWITCH TO 4:3") +++#define TEXT_HUD_WIDE_INFO _("PLEASE CONFIGURE YOUR DISPLAY OR YOUR EMULATOR TO") +++#define TEXT_HUD_WIDE_INFO2 _("STRETCH THE IMAGE TO 16:9") ++ ++ #if defined(VERSION_JP) || defined(VERSION_SH) ++ ++diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c ++index 9344738..37bc1d1 100644 ++--- a/src/game/ingame_menu.c +++++ b/src/game/ingame_menu.c ++@@ -34,6 +34,11 @@ s16 gDialogY; // D_8032F69C ++ s16 gCutsceneMsgXOffset; ++ s16 gCutsceneMsgYOffset; ++ s8 gRedCoinsCollected; +++u8 textCurrRatio43[] = { TEXT_HUD_CURRENT_RATIO_43 }; +++u8 textCurrRatio169[] = { TEXT_HUD_CURRENT_RATIO_169 }; +++u8 textWideInfo[] = { TEXT_HUD_WIDE_INFO }; +++u8 textWideInfo2[] = { TEXT_HUD_WIDE_INFO2 }; +++extern s32 widescreen = 0; ++ ++ extern u8 gLastCompletedCourseNum; ++ extern u8 gLastCompletedStarNum; ++@@ -1672,6 +1677,7 @@ s8 gDialogCourseActNum = 1; ++ #define DIAG_VAL2 240 // JP & US ++ #endif ++ +++ ++ void render_dialog_entries(void) { ++ #ifdef VERSION_EU ++ s8 lowerBound; ++@@ -1713,7 +1719,7 @@ void render_dialog_entries(void) { ++ switch (gDialogBoxState) { ++ case DIALOG_STATE_OPENING: ++ if (gDialogBoxOpenTimer == DEFAULT_DIALOG_BOX_ANGLE) { ++- play_dialog_sound(gDialogID); +++ //play_dialog_sound(gDialogID); ++ play_sound(SOUND_MENU_MESSAGE_APPEAR, gGlobalSoundSource); ++ } ++ ++@@ -2199,6 +2205,7 @@ void render_pause_my_score_coins(void) { ++ #else ++ u8 textCourse[] = { TEXT_COURSE }; ++ u8 textMyScore[] = { TEXT_MY_SCORE }; +++ ++ #endif ++ u8 textStar[] = { TEXT_STAR }; ++ u8 textUnfilledStar[] = { TEXT_UNFILLED_STAR }; ++@@ -2266,8 +2273,18 @@ void render_pause_my_score_coins(void) { ++ print_generic_string(get_string_width(gTextCourseArr[gInGameLanguage]) + 51, 157, strCourseNum); ++ #else ++ print_generic_string(CRS_NUM_X1, 157, strCourseNum); +++ if (widescreen == 0) { +++ print_generic_string(10, 20, textCurrRatio43); +++ } +++ else { +++ print_generic_string(10, 20, textCurrRatio169); +++ print_generic_string(10, 200, textWideInfo); +++ print_generic_string(10, 180, textWideInfo2); +++ } +++ ++ #endif ++ +++ ++ actName = segmented_to_virtual(actNameTbl[(gCurrCourseNum - 1) * 6 + gDialogCourseActNum - 1]); ++ ++ if (starFlags & (1 << (gDialogCourseActNum - 1))) { ++@@ -2557,10 +2574,25 @@ void render_pause_castle_main_strings(s16 x, s16 y) { ++ } ++ } ++ } ++- +++ if (gPlayer1Controller->buttonPressed & L_TRIG){ +++ if (widescreen == 0){ +++ widescreen = 1; +++ } +++ else{ +++ widescreen = 0; +++ } +++ } ++ gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); ++ gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); ++ +++ if (widescreen == 0) { +++ print_generic_string(10, 20, textCurrRatio43); +++ } +++ else { +++ print_generic_string(10, 20, textCurrRatio169); +++ print_generic_string(10, 200, textWideInfo); +++ print_generic_string(10, 180, textWideInfo2); +++ } ++ if (gDialogLineNum < COURSE_STAGES_COUNT) { ++ courseName = segmented_to_virtual(courseNameTbl[gDialogLineNum]); ++ render_pause_castle_course_stars(x, y, gCurrSaveFileNum - 1, gDialogLineNum); ++@@ -2626,7 +2658,14 @@ s16 render_pause_courses_and_castle(void) { ++ shade_screen(); ++ render_pause_my_score_coins(); ++ render_pause_red_coins(); ++- +++ if (gPlayer1Controller->buttonPressed & L_TRIG){ +++ if (widescreen == 0){ +++ widescreen = 1; +++ } +++ else{ +++ widescreen = 0; +++ } +++ } ++ if (gMarioStates[0].action & ACT_FLAG_PAUSE_EXIT) { ++ render_pause_course_options(99, 93, &gDialogLineNum, 15); ++ } ++diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c ++index 58238e8..5262fd3 100644 ++--- a/src/game/rendering_graph_node.c +++++ b/src/game/rendering_graph_node.c ++@@ -10,6 +10,7 @@ ++ #include "rendering_graph_node.h" ++ #include "shadow.h" ++ #include "sm64.h" +++#define WIDESCREEN ++ ++ /** ++ * This file contains the code that processes the scene graph for rendering. ++@@ -235,6 +236,8 @@ static void geo_process_ortho_projection(struct GraphNodeOrthoProjection *node) ++ /** ++ * Process a perspective projection node. ++ */ +++ +++extern s32 widescreen; ++ static void geo_process_perspective(struct GraphNodePerspective *node) { ++ if (node->fnNode.func != NULL) { ++ node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); ++@@ -246,7 +249,13 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { ++ #ifdef VERSION_EU ++ f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; ++ #else ++- f32 aspect = (f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height; +++ f32 aspect; +++ if (widescreen == 1){ +++ aspect = 1.775f; +++ } +++ else{ +++ aspect = 1.33333f; +++ } ++ #endif ++ ++ guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); ++diff --git a/src/menu/file_select.c b/src/menu/file_select.c ++index 9437dcc..6ba1845 100644 ++--- a/src/menu/file_select.c +++++ b/src/menu/file_select.c ++@@ -2854,10 +2854,12 @@ Gfx *geo_file_select_strings_and_menu_cursor(s32 callContext, UNUSED struct Grap ++ * Relocates cursor position of the last save if the game goes back to the Mario Screen ++ * either completing a course choosing "SAVE & QUIT" or having a game over. ++ */ +++extern s32 widescreen; ++ s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused) { ++ #ifdef VERSION_EU ++ s8 fileNum; ++ #endif +++ widescreen = 0; ++ sSelectedButtonID = MENU_BUTTON_NONE; ++ sCurrentMenuLevel = MENU_LAYER_MAIN; ++ sTextBaseAlpha = 0; ++diff --git a/src/menu/star_select.c b/src/menu/star_select.c ++index 07610fa..9664cc1 100644 ++--- a/src/menu/star_select.c +++++ b/src/menu/star_select.c ++@@ -52,6 +52,7 @@ static s8 sSelectableStarIndex = 0; ++ ++ // Act Selector menu timer that keeps counting until you choose an act. ++ static s32 sActSelectorMenuTimer = 0; +++extern s32 widescreen; ++ ++ /** ++ * Act Selector Star Type Loop Action ++@@ -92,8 +93,14 @@ void bhv_act_selector_star_type_loop(void) { ++ 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); +++ if (widescreen == 1){ +++ sStarSelectorModels[6] = spawn_object_abs_with_rot(gCurrentObject, 0, MODEL_STAR, +++ bhvActSelectorStarType, ((370*4.0f)/3), 24, -300, 0, 0, 0); +++ } +++ else{ +++ 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; ++ } ++@@ -147,11 +154,21 @@ void bhv_act_selector_init(void) { ++ } ++ ++ // 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; +++ if (widescreen == 1) { +++ for (i = 0; i < sVisibleStars; i++) { +++ sStarSelectorModels[i] = +++ spawn_object_abs_with_rot(gCurrentObject, 0, selectorModelIDs[i], bhvActSelectorStarType, +++ (((75 + sVisibleStars * -75 + i * 152)*4.0f)/3), 248, -300, 0, 0, 0); +++ sStarSelectorModels[i]->oStarSelectorSize = 1.0f; +++ } +++ } +++ else { +++ 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); +diff --git a/include/config.h b/include/config.h +index 211a086..d1eb721 100644 +--- a/include/config.h ++++ b/include/config.h +@@ -30,9 +30,9 @@ + // Border Height Define for NTSC Versions + #ifdef TARGET_N64 + #ifndef VERSION_EU +-#define BORDER_HEIGHT 8 ++#define BORDER_HEIGHT 0 + #else +-#define BORDER_HEIGHT 1 ++#define BORDER_HEIGHT 0 + #endif + #else + // What's the point of having a border? +diff --git a/include/text_strings.h.in b/include/text_strings.h.in +index 749179b..3b23b41 100644 +--- a/include/text_strings.h.in ++++ b/include/text_strings.h.in +@@ -24,6 +24,10 @@ + // Ingame Menu + #define TEXT_PAUSE _("PAUSE") // Pause text, Castle Courses + #define TEXT_HUD_CONGRATULATIONS _("CONGRATULATIONS") // Course Complete Text, Bowser Courses ++#define TEXT_HUD_CURRENT_RATIO_43 _("CURRENT ASPECT RATIO: 4:3. PRESS L TO SWITCH TO 16:9") ++#define TEXT_HUD_CURRENT_RATIO_169 _("CURRENT ASPECT RATIO: 16:9. PRESS L TO SWITCH TO 4:3") ++#define TEXT_HUD_WIDE_INFO _("PLEASE CONFIGURE YOUR DISPLAY OR YOUR EMULATOR TO") ++#define TEXT_HUD_WIDE_INFO2 _("STRETCH THE IMAGE TO 16:9") + + #if defined(VERSION_JP) || defined(VERSION_SH) + +diff --git a/levels/castle_grounds/script.c b/levels/castle_grounds/script.c +index 626bece..b8191ea 100644 +--- a/levels/castle_grounds/script.c ++++ b/levels/castle_grounds/script.c +@@ -132,6 +132,7 @@ const LevelScript level_castle_grounds_entry[] = { + FREE_LEVEL_POOL(), + MARIO_POS(/*area*/ 1, /*yaw*/ 180, /*pos*/ -1328, 260, 4664), + CALL(/*arg*/ 0, /*func*/ lvl_init_or_update), ++ CALL(0, lvl_set_widescreen), + CALL_LOOP(/*arg*/ 1, /*func*/ lvl_init_or_update), + CLEAR_LEVEL(), + SLEEP_BEFORE_EXIT(/*frames*/ 1), +diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c +index 8190f88..e4d0f32 100644 +--- a/src/game/ingame_menu.c ++++ b/src/game/ingame_menu.c +@@ -34,6 +34,11 @@ s16 gDialogY; // D_8032F69C + s16 gCutsceneMsgXOffset; + s16 gCutsceneMsgYOffset; + s8 gRedCoinsCollected; ++u8 textCurrRatio43[] = { TEXT_HUD_CURRENT_RATIO_43 }; ++u8 textCurrRatio169[] = { TEXT_HUD_CURRENT_RATIO_169 }; ++u8 textWideInfo[] = { TEXT_HUD_WIDE_INFO }; ++u8 textWideInfo2[] = { TEXT_HUD_WIDE_INFO2 }; ++u8 widescreen = 1; + + extern u8 gLastCompletedCourseNum; + extern u8 gLastCompletedStarNum; +@@ -2266,6 +2271,24 @@ void render_pause_my_score_coins(void) { + print_generic_string(get_string_width(gTextCourseArr[gInGameLanguage]) + 51, 157, strCourseNum); + #else + print_generic_string(CRS_NUM_X1, 157, strCourseNum); ++ if (widescreen == 0) { ++ if (COURSE_IS_MAIN_COURSE(gCurrCourseNum)) { ++ print_generic_string(10, 40, textCurrRatio43); ++ } else { ++ print_generic_string(10, 20, textCurrRatio43); ++ } ++ ++ } ++ else { ++ if (COURSE_IS_MAIN_COURSE(gCurrCourseNum)) { ++ print_generic_string(10, 40, textCurrRatio169); ++ } else { ++ print_generic_string(10, 20, textCurrRatio169); ++ } ++ ++ print_generic_string(10, 200, textWideInfo); ++ print_generic_string(10, 180, textWideInfo2); ++ } + #endif + + actName = segmented_to_virtual(actNameTbl[(gCurrCourseNum - 1) * 6 + gDialogCourseActNum - 1]); +@@ -2557,10 +2580,25 @@ void render_pause_castle_main_strings(s16 x, s16 y) { + } + } + } ++ if (gPlayer1Controller->buttonPressed & L_TRIG){ ++ if (widescreen == 0){ ++ widescreen = 1; ++ } ++ else{ ++ widescreen = 0; ++ } ++ } + + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); +- ++ if (widescreen == 0) { ++ print_generic_string(10, 20, textCurrRatio43); ++ } ++ else { ++ print_generic_string(10, 20, textCurrRatio169); ++ print_generic_string(10, 200, textWideInfo); ++ print_generic_string(10, 180, textWideInfo2); ++ } + if (gDialogLineNum < COURSE_STAGES_COUNT) { + courseName = segmented_to_virtual(courseNameTbl[gDialogLineNum]); + render_pause_castle_course_stars(x, y, gCurrSaveFileNum - 1, gDialogLineNum); +@@ -2626,6 +2664,14 @@ s16 render_pause_courses_and_castle(void) { + shade_screen(); + render_pause_my_score_coins(); + render_pause_red_coins(); ++ if (gPlayer1Controller->buttonPressed & L_TRIG){ ++ if (widescreen == 0){ ++ widescreen = 1; ++ } ++ else{ ++ widescreen = 0; ++ } ++ } + + if (gMarioStates[0].action & ACT_FLAG_PAUSE_EXIT) { + render_pause_course_options(99, 93, &gDialogLineNum, 15); +diff --git a/src/game/level_update.c b/src/game/level_update.c +index 4ecb902..b2d6e53 100644 +--- a/src/game/level_update.c ++++ b/src/game/level_update.c +@@ -1247,6 +1247,10 @@ s32 lvl_init_or_update(s16 initOrUpdate, UNUSED s32 unused) { + + return result; + } ++extern u8 widescreen; ++void lvl_set_widescreen(void) { ++ widescreen = 1; ++} + + s32 lvl_init_from_save_file(UNUSED s16 arg0, s32 levelNum) { + #ifdef VERSION_EU +diff --git a/src/game/level_update.h b/src/game/level_update.h +index 521b4ef..58d4e75 100644 +--- a/src/game/level_update.h ++++ b/src/game/level_update.h +@@ -128,5 +128,6 @@ s32 lvl_init_from_save_file(UNUSED s16 arg0, s32 levelNum); + s32 lvl_set_current_level(UNUSED s16 arg0, s32 levelNum); + s32 lvl_play_the_end_screen_sound(UNUSED s16 arg0, UNUSED s32 arg1); + void basic_update(UNUSED s16 *arg); ++void lvl_set_widescreen(void); + + #endif // LEVEL_UPDATE_H +diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c +index 58238e8..09a6c87 100644 +--- a/src/game/rendering_graph_node.c ++++ b/src/game/rendering_graph_node.c +@@ -10,6 +10,7 @@ + #include "rendering_graph_node.h" + #include "shadow.h" + #include "sm64.h" ++#define WIDESCREEN + + /** + * This file contains the code that processes the scene graph for rendering. +@@ -39,6 +40,7 @@ + s16 gMatStackIndex; + Mat4 gMatStack[32]; + Mtx *gMatStackFixed[32]; ++f32 aspect; + + /** + * Animation nodes have state in global variables, so this struct captures +@@ -235,6 +237,7 @@ static void geo_process_ortho_projection(struct GraphNodeOrthoProjection *node) + /** + * Process a perspective projection node. + */ ++extern u8 widescreen; + static void geo_process_perspective(struct GraphNodePerspective *node) { + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); +@@ -246,7 +249,12 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { + #ifdef VERSION_EU + f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; + #else +- f32 aspect = (f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height; ++ if (widescreen == 1){ ++ aspect = 1.775f; ++ } ++ else{ ++ aspect = 1.33333f; ++ } + #endif + + guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); +@@ -755,7 +763,7 @@ static s32 obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { + // ! @bug The aspect ratio is not accounted for. When the fov value is 45, + // the horizontal effective fov is actually 60 degrees, so you can see objects + // visibly pop in or out at the edge of the screen. +- halfFov = (gCurGraphNodeCamFrustum->fov / 2.0f + 1.0f) * 32768.0f / 180.0f + 0.5f; ++ halfFov = ((gCurGraphNodeCamFrustum->fov*aspect) / 2.0f + 1.0f) * 32768.0f / 180.0f + 0.5f; + + hScreenEdge = -matrix[3][2] * sins(halfFov) / coss(halfFov); + // -matrix[3][2] is the depth, which gets multiplied by tan(halfFov) to get +diff --git a/src/menu/file_select.c b/src/menu/file_select.c +index c894797..94a3566 100644 +--- a/src/menu/file_select.c ++++ b/src/menu/file_select.c +@@ -2854,10 +2854,12 @@ Gfx *geo_file_select_strings_and_menu_cursor(s32 callContext, UNUSED struct Grap + * Relocates cursor position of the last save if the game goes back to the Mario Screen + * either completing a course choosing "SAVE & QUIT" or having a game over. + */ ++extern u8 widescreen; + s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused) { + #ifdef VERSION_EU + s8 fileNum; + #endif ++ widescreen = 0; + sSelectedButtonID = MENU_BUTTON_NONE; + sCurrentMenuLevel = MENU_LAYER_MAIN; + sTextBaseAlpha = 0; +diff --git a/src/menu/star_select.c b/src/menu/star_select.c +index 025dbf2..4840898 100644 +--- a/src/menu/star_select.c ++++ b/src/menu/star_select.c +@@ -52,7 +52,7 @@ static s8 sSelectableStarIndex = 0; + + // Act Selector menu timer that keeps counting until you choose an act. + static s32 sActSelectorMenuTimer = 0; +- ++extern u8 widescreen; + /** + * Act Selector Star Type Loop Action + * Defines a select type for a star in the act selector. +@@ -92,8 +92,15 @@ void bhv_act_selector_star_type_loop(void) { + 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); ++ if (widescreen == 1){ ++ sStarSelectorModels[6] = spawn_object_abs_with_rot(gCurrentObject, 0, MODEL_STAR, ++ bhvActSelectorStarType, ((370*4.0f)/3), 24, -300, 0, 0, 0); ++ } ++ else{ ++ 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; + } +@@ -147,11 +154,21 @@ void bhv_act_selector_init(void) { + } + + // 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; ++ if (widescreen == 1) { ++ for (i = 0; i < sVisibleStars; i++) { ++ sStarSelectorModels[i] = ++ spawn_object_abs_with_rot(gCurrentObject, 0, selectorModelIDs[i], bhvActSelectorStarType, ++ (((75 + sVisibleStars * -75 + i * 152)*4.0f)/3), 248, -300, 0, 0, 0); ++ sStarSelectorModels[i]->oStarSelectorSize = 1.0f; ++ } ++ } ++ else { ++ 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); diff --git a/enhancements/widescreenv2_sm64.patch b/enhancements/widescreenv2_sm64.patch new file mode 100644 index 00000000..d7f27ade --- /dev/null +++ b/enhancements/widescreenv2_sm64.patch @@ -0,0 +1,198 @@ +diff --git a/include/text_strings.h.in b/include/text_strings.h.in +index 749179b..3b23b41 100644 +--- a/include/text_strings.h.in ++++ b/include/text_strings.h.in +@@ -24,6 +24,10 @@ + // Ingame Menu + #define TEXT_PAUSE _("PAUSE") // Pause text, Castle Courses + #define TEXT_HUD_CONGRATULATIONS _("CONGRATULATIONS") // Course Complete Text, Bowser Courses ++#define TEXT_HUD_CURRENT_RATIO_43 _("CURRENT ASPECT RATIO: 4:3. PRESS L TO SWITCH TO 16:9") ++#define TEXT_HUD_CURRENT_RATIO_169 _("CURRENT ASPECT RATIO: 16:9. PRESS L TO SWITCH TO 4:3") ++#define TEXT_HUD_WIDE_INFO _("PLEASE CONFIGURE YOUR DISPLAY OR YOUR EMULATOR TO") ++#define TEXT_HUD_WIDE_INFO2 _("STRETCH THE IMAGE TO 16:9") + + #if defined(VERSION_JP) || defined(VERSION_SH) + +diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c +index 8190f88..deb69f5 100644 +--- a/src/game/ingame_menu.c ++++ b/src/game/ingame_menu.c +@@ -34,6 +34,11 @@ s16 gDialogY; // D_8032F69C + s16 gCutsceneMsgXOffset; + s16 gCutsceneMsgYOffset; + s8 gRedCoinsCollected; ++u8 textCurrRatio43[] = { TEXT_HUD_CURRENT_RATIO_43 }; ++u8 textCurrRatio169[] = { TEXT_HUD_CURRENT_RATIO_169 }; ++u8 textWideInfo[] = { TEXT_HUD_WIDE_INFO }; ++u8 textWideInfo2[] = { TEXT_HUD_WIDE_INFO2 }; ++extern u8 widescreen = 0; + + extern u8 gLastCompletedCourseNum; + extern u8 gLastCompletedStarNum; +@@ -2266,6 +2271,14 @@ void render_pause_my_score_coins(void) { + print_generic_string(get_string_width(gTextCourseArr[gInGameLanguage]) + 51, 157, strCourseNum); + #else + print_generic_string(CRS_NUM_X1, 157, strCourseNum); ++ if (widescreen == 0) { ++ print_generic_string(10, 20, textCurrRatio43); ++ } ++ else { ++ print_generic_string(10, 20, textCurrRatio169); ++ print_generic_string(10, 200, textWideInfo); ++ print_generic_string(10, 180, textWideInfo2); ++ } + #endif + + actName = segmented_to_virtual(actNameTbl[(gCurrCourseNum - 1) * 6 + gDialogCourseActNum - 1]); +@@ -2557,10 +2570,25 @@ void render_pause_castle_main_strings(s16 x, s16 y) { + } + } + } ++ if (gPlayer1Controller->buttonPressed & L_TRIG){ ++ if (widescreen == 0){ ++ widescreen = 1; ++ } ++ else{ ++ widescreen = 0; ++ } ++ } + + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); +- ++ if (widescreen == 0) { ++ print_generic_string(10, 20, textCurrRatio43); ++ } ++ else { ++ print_generic_string(10, 20, textCurrRatio169); ++ print_generic_string(10, 200, textWideInfo); ++ print_generic_string(10, 180, textWideInfo2); ++ } + if (gDialogLineNum < COURSE_STAGES_COUNT) { + courseName = segmented_to_virtual(courseNameTbl[gDialogLineNum]); + render_pause_castle_course_stars(x, y, gCurrSaveFileNum - 1, gDialogLineNum); +@@ -2626,6 +2654,14 @@ s16 render_pause_courses_and_castle(void) { + shade_screen(); + render_pause_my_score_coins(); + render_pause_red_coins(); ++ if (gPlayer1Controller->buttonPressed & L_TRIG){ ++ if (widescreen == 0){ ++ widescreen = 1; ++ } ++ else{ ++ widescreen = 0; ++ } ++ } + + if (gMarioStates[0].action & ACT_FLAG_PAUSE_EXIT) { + render_pause_course_options(99, 93, &gDialogLineNum, 15); +diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c +index 58238e8..aabdb85 100644 +--- a/src/game/rendering_graph_node.c ++++ b/src/game/rendering_graph_node.c +@@ -10,6 +10,7 @@ + #include "rendering_graph_node.h" + #include "shadow.h" + #include "sm64.h" ++#define WIDESCREEN + + /** + * This file contains the code that processes the scene graph for rendering. +@@ -235,6 +236,7 @@ static void geo_process_ortho_projection(struct GraphNodeOrthoProjection *node) + /** + * Process a perspective projection node. + */ ++extern u8 widescreen; + static void geo_process_perspective(struct GraphNodePerspective *node) { + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); +@@ -246,7 +248,13 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { + #ifdef VERSION_EU + f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; + #else +- f32 aspect = (f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height; ++ f32 aspect; ++ if (widescreen == 1){ ++ aspect = 1.775f; ++ } ++ else{ ++ aspect = 1.33333f; ++ } + #endif + + guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); +diff --git a/src/menu/file_select.c b/src/menu/file_select.c +index c894797..dc759f7 100644 +--- a/src/menu/file_select.c ++++ b/src/menu/file_select.c +@@ -2854,10 +2854,12 @@ Gfx *geo_file_select_strings_and_menu_cursor(s32 callContext, UNUSED struct Grap + * Relocates cursor position of the last save if the game goes back to the Mario Screen + * either completing a course choosing "SAVE & QUIT" or having a game over. + */ ++extern u8 widescreen; + s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused) { + #ifdef VERSION_EU + s8 fileNum; + #endif ++ widescreen = 0; + sSelectedButtonID = MENU_BUTTON_NONE; + sCurrentMenuLevel = MENU_LAYER_MAIN; + sTextBaseAlpha = 0; +diff --git a/src/menu/star_select.c b/src/menu/star_select.c +index 025dbf2..d6aaa7e 100644 +--- a/src/menu/star_select.c ++++ b/src/menu/star_select.c +@@ -52,7 +52,7 @@ static s8 sSelectableStarIndex = 0; + + // Act Selector menu timer that keeps counting until you choose an act. + static s32 sActSelectorMenuTimer = 0; +- ++extern u8 widescreen; + /** + * Act Selector Star Type Loop Action + * Defines a select type for a star in the act selector. +@@ -92,8 +92,15 @@ void bhv_act_selector_star_type_loop(void) { + 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); ++ if (widescreen == 1){ ++ sStarSelectorModels[6] = spawn_object_abs_with_rot(gCurrentObject, 0, MODEL_STAR, ++ bhvActSelectorStarType, ((370*4.0f)/3), 24, -300, 0, 0, 0); ++ } ++ else{ ++ 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; + } +@@ -147,11 +154,21 @@ void bhv_act_selector_init(void) { + } + + // 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; ++ if (widescreen == 1) { ++ for (i = 0; i < sVisibleStars; i++) { ++ sStarSelectorModels[i] = ++ spawn_object_abs_with_rot(gCurrentObject, 0, selectorModelIDs[i], bhvActSelectorStarType, ++ (((75 + sVisibleStars * -75 + i * 152)*4.0f)/3), 248, -300, 0, 0, 0); ++ sStarSelectorModels[i]->oStarSelectorSize = 1.0f; ++ } ++ } ++ else { ++ 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); diff --git a/enhancements/widescreenv2_ultrasm64.patch b/enhancements/widescreenv2_ultrasm64.patch new file mode 100644 index 00000000..667e6e27 --- /dev/null +++ b/enhancements/widescreenv2_ultrasm64.patch @@ -0,0 +1,227 @@ +diff --git a/include/text_strings.h.in b/include/text_strings.h.in +index 749179b..3b23b41 100644 +--- a/include/text_strings.h.in ++++ b/include/text_strings.h.in +@@ -24,6 +24,10 @@ + // Ingame Menu + #define TEXT_PAUSE _("PAUSE") // Pause text, Castle Courses + #define TEXT_HUD_CONGRATULATIONS _("CONGRATULATIONS") // Course Complete Text, Bowser Courses ++#define TEXT_HUD_CURRENT_RATIO_43 _("CURRENT ASPECT RATIO: 4:3. PRESS L TO SWITCH TO 16:9") ++#define TEXT_HUD_CURRENT_RATIO_169 _("CURRENT ASPECT RATIO: 16:9. PRESS L TO SWITCH TO 4:3") ++#define TEXT_HUD_WIDE_INFO _("PLEASE CONFIGURE YOUR DISPLAY OR YOUR EMULATOR TO") ++#define TEXT_HUD_WIDE_INFO2 _("STRETCH THE IMAGE TO 16:9") + + #if defined(VERSION_JP) || defined(VERSION_SH) + +diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c +index 9344738..37bc1d1 100644 +--- a/src/game/ingame_menu.c ++++ b/src/game/ingame_menu.c +@@ -34,6 +34,11 @@ s16 gDialogY; // D_8032F69C + s16 gCutsceneMsgXOffset; + s16 gCutsceneMsgYOffset; + s8 gRedCoinsCollected; ++u8 textCurrRatio43[] = { TEXT_HUD_CURRENT_RATIO_43 }; ++u8 textCurrRatio169[] = { TEXT_HUD_CURRENT_RATIO_169 }; ++u8 textWideInfo[] = { TEXT_HUD_WIDE_INFO }; ++u8 textWideInfo2[] = { TEXT_HUD_WIDE_INFO2 }; ++extern s32 widescreen = 0; + + extern u8 gLastCompletedCourseNum; + extern u8 gLastCompletedStarNum; +@@ -1672,6 +1677,7 @@ s8 gDialogCourseActNum = 1; + #define DIAG_VAL2 240 // JP & US + #endif + ++ + void render_dialog_entries(void) { + #ifdef VERSION_EU + s8 lowerBound; +@@ -1713,7 +1719,7 @@ void render_dialog_entries(void) { + switch (gDialogBoxState) { + case DIALOG_STATE_OPENING: + if (gDialogBoxOpenTimer == DEFAULT_DIALOG_BOX_ANGLE) { +- play_dialog_sound(gDialogID); ++ //play_dialog_sound(gDialogID); + play_sound(SOUND_MENU_MESSAGE_APPEAR, gGlobalSoundSource); + } + +@@ -2199,6 +2205,7 @@ void render_pause_my_score_coins(void) { + #else + u8 textCourse[] = { TEXT_COURSE }; + u8 textMyScore[] = { TEXT_MY_SCORE }; ++ + #endif + u8 textStar[] = { TEXT_STAR }; + u8 textUnfilledStar[] = { TEXT_UNFILLED_STAR }; +@@ -2266,8 +2273,18 @@ void render_pause_my_score_coins(void) { + print_generic_string(get_string_width(gTextCourseArr[gInGameLanguage]) + 51, 157, strCourseNum); + #else + print_generic_string(CRS_NUM_X1, 157, strCourseNum); ++ if (widescreen == 0) { ++ print_generic_string(10, 20, textCurrRatio43); ++ } ++ else { ++ print_generic_string(10, 20, textCurrRatio169); ++ print_generic_string(10, 200, textWideInfo); ++ print_generic_string(10, 180, textWideInfo2); ++ } ++ + #endif + ++ + actName = segmented_to_virtual(actNameTbl[(gCurrCourseNum - 1) * 6 + gDialogCourseActNum - 1]); + + if (starFlags & (1 << (gDialogCourseActNum - 1))) { +@@ -2557,10 +2574,25 @@ void render_pause_castle_main_strings(s16 x, s16 y) { + } + } + } +- ++ if (gPlayer1Controller->buttonPressed & L_TRIG){ ++ if (widescreen == 0){ ++ widescreen = 1; ++ } ++ else{ ++ widescreen = 0; ++ } ++ } + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); + ++ if (widescreen == 0) { ++ print_generic_string(10, 20, textCurrRatio43); ++ } ++ else { ++ print_generic_string(10, 20, textCurrRatio169); ++ print_generic_string(10, 200, textWideInfo); ++ print_generic_string(10, 180, textWideInfo2); ++ } + if (gDialogLineNum < COURSE_STAGES_COUNT) { + courseName = segmented_to_virtual(courseNameTbl[gDialogLineNum]); + render_pause_castle_course_stars(x, y, gCurrSaveFileNum - 1, gDialogLineNum); +@@ -2626,7 +2658,14 @@ s16 render_pause_courses_and_castle(void) { + shade_screen(); + render_pause_my_score_coins(); + render_pause_red_coins(); +- ++ if (gPlayer1Controller->buttonPressed & L_TRIG){ ++ if (widescreen == 0){ ++ widescreen = 1; ++ } ++ else{ ++ widescreen = 0; ++ } ++ } + if (gMarioStates[0].action & ACT_FLAG_PAUSE_EXIT) { + render_pause_course_options(99, 93, &gDialogLineNum, 15); + } +diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c +index 58238e8..5262fd3 100644 +--- a/src/game/rendering_graph_node.c ++++ b/src/game/rendering_graph_node.c +@@ -10,6 +10,7 @@ + #include "rendering_graph_node.h" + #include "shadow.h" + #include "sm64.h" ++#define WIDESCREEN + + /** + * This file contains the code that processes the scene graph for rendering. +@@ -235,6 +236,8 @@ static void geo_process_ortho_projection(struct GraphNodeOrthoProjection *node) + /** + * Process a perspective projection node. + */ ++ ++extern s32 widescreen; + static void geo_process_perspective(struct GraphNodePerspective *node) { + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); +@@ -246,7 +249,13 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { + #ifdef VERSION_EU + f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; + #else +- f32 aspect = (f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height; ++ f32 aspect; ++ if (widescreen == 1){ ++ aspect = 1.775f; ++ } ++ else{ ++ aspect = 1.33333f; ++ } + #endif + + guPerspective(mtx, &perspNorm, node->fov, aspect, node->near, node->far, 1.0f); +diff --git a/src/menu/file_select.c b/src/menu/file_select.c +index 9437dcc..6ba1845 100644 +--- a/src/menu/file_select.c ++++ b/src/menu/file_select.c +@@ -2854,10 +2854,12 @@ Gfx *geo_file_select_strings_and_menu_cursor(s32 callContext, UNUSED struct Grap + * Relocates cursor position of the last save if the game goes back to the Mario Screen + * either completing a course choosing "SAVE & QUIT" or having a game over. + */ ++extern s32 widescreen; + s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused) { + #ifdef VERSION_EU + s8 fileNum; + #endif ++ widescreen = 0; + sSelectedButtonID = MENU_BUTTON_NONE; + sCurrentMenuLevel = MENU_LAYER_MAIN; + sTextBaseAlpha = 0; +diff --git a/src/menu/star_select.c b/src/menu/star_select.c +index 07610fa..9664cc1 100644 +--- a/src/menu/star_select.c ++++ b/src/menu/star_select.c +@@ -52,6 +52,7 @@ static s8 sSelectableStarIndex = 0; + + // Act Selector menu timer that keeps counting until you choose an act. + static s32 sActSelectorMenuTimer = 0; ++extern s32 widescreen; + + /** + * Act Selector Star Type Loop Action +@@ -92,8 +93,14 @@ void bhv_act_selector_star_type_loop(void) { + 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); ++ if (widescreen == 1){ ++ sStarSelectorModels[6] = spawn_object_abs_with_rot(gCurrentObject, 0, MODEL_STAR, ++ bhvActSelectorStarType, ((370*4.0f)/3), 24, -300, 0, 0, 0); ++ } ++ else{ ++ 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; + } +@@ -147,11 +154,21 @@ void bhv_act_selector_init(void) { + } + + // 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; ++ if (widescreen == 1) { ++ for (i = 0; i < sVisibleStars; i++) { ++ sStarSelectorModels[i] = ++ spawn_object_abs_with_rot(gCurrentObject, 0, selectorModelIDs[i], bhvActSelectorStarType, ++ (((75 + sVisibleStars * -75 + i * 152)*4.0f)/3), 248, -300, 0, 0, 0); ++ sStarSelectorModels[i]->oStarSelectorSize = 1.0f; ++ } ++ } ++ else { ++ 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); diff --git a/include/config.h.orig b/include/config.h.orig new file mode 100644 index 00000000..1cd32588 --- /dev/null +++ b/include/config.h.orig @@ -0,0 +1,45 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/** + * @file config.h + * A catch-all file for configuring various bugfixes and other settings + * (maybe eventually) in SM64 + */ + +// Bug Fixes +// --| US Version Nintendo Bug Fixes +/// Fixes bug where obtaining over 999 coins sets the number of lives to 999 (or -25) +#define BUGFIX_MAX_LIVES (0 || VERSION_US || VERSION_EU || VERSION_SH) +/// Fixes bug where the Boss music won't fade out after defeating King Bob-omb +#define BUGFIX_KING_BOB_OMB_FADE_MUSIC (0 || VERSION_US || VERSION_EU || VERSION_SH) +/// Fixes bug in Bob-Omb Battlefield where entering a warp stops the Koopa race music +#define BUGFIX_KOOPA_RACE_MUSIC (0 || VERSION_US || VERSION_EU || VERSION_SH) +/// Fixes bug where Piranha Plants do not reset their action state when the +/// player exits their activation radius. +#define BUGFIX_PIRANHA_PLANT_STATE_RESET (0 || VERSION_US || VERSION_EU || VERSION_SH) +/// Fixes bug where sleeping Piranha Plants damage players that bump into them +#define BUGFIX_PIRANHA_PLANT_SLEEP_DAMAGE (0 || VERSION_US || VERSION_SH) +/// Fixes bug where it shows a star when you grab a key in bowser battle stages +#define BUGFIX_STAR_BOWSER_KEY (0 || VERSION_US || VERSION_EU || VERSION_SH) + +// Support Rumble Pak +#define ENABLE_RUMBLE (1 || VERSION_SH) + +// Screen Size Defines +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 240 + +// Border Height Define for NTSC Versions +#ifdef TARGET_N64 +#ifndef VERSION_EU +#define BORDER_HEIGHT 0 +#else +#define BORDER_HEIGHT 0 +#endif +#else +// What's the point of having a border? +#define BORDER_HEIGHT 0 +#endif + +#endif // CONFIG_H diff --git a/include/text_strings.h.in b/include/text_strings.h.in index 749179b1..3b23b41c 100644 --- a/include/text_strings.h.in +++ b/include/text_strings.h.in @@ -24,6 +24,10 @@ // Ingame Menu #define TEXT_PAUSE _("PAUSE") // Pause text, Castle Courses #define TEXT_HUD_CONGRATULATIONS _("CONGRATULATIONS") // Course Complete Text, Bowser Courses +#define TEXT_HUD_CURRENT_RATIO_43 _("CURRENT ASPECT RATIO: 4:3. PRESS L TO SWITCH TO 16:9") +#define TEXT_HUD_CURRENT_RATIO_169 _("CURRENT ASPECT RATIO: 16:9. PRESS L TO SWITCH TO 4:3") +#define TEXT_HUD_WIDE_INFO _("PLEASE CONFIGURE YOUR DISPLAY OR YOUR EMULATOR TO") +#define TEXT_HUD_WIDE_INFO2 _("STRETCH THE IMAGE TO 16:9") #if defined(VERSION_JP) || defined(VERSION_SH) diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index 9344738d..383381e7 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -22,6 +22,7 @@ #include "sm64.h" #include "text_strings.h" #include "types.h" +#define wide // DELETE THIS DEFINE IF YOU DON'T WANT WIDESCREEN SUPPORT IN YOUR HACK (why tho) u16 gDialogColorFadeTimer; s8 gLastDialogLineNum; @@ -34,6 +35,11 @@ s16 gDialogY; // D_8032F69C s16 gCutsceneMsgXOffset; s16 gCutsceneMsgYOffset; s8 gRedCoinsCollected; +u8 textCurrRatio43[] = { TEXT_HUD_CURRENT_RATIO_43 }; +u8 textCurrRatio169[] = { TEXT_HUD_CURRENT_RATIO_169 }; +u8 textWideInfo[] = { TEXT_HUD_WIDE_INFO }; +u8 textWideInfo2[] = { TEXT_HUD_WIDE_INFO2 }; +u8 widescreen = 0; extern u8 gLastCompletedCourseNum; extern u8 gLastCompletedStarNum; @@ -2266,6 +2272,26 @@ void render_pause_my_score_coins(void) { print_generic_string(get_string_width(gTextCourseArr[gInGameLanguage]) + 51, 157, strCourseNum); #else print_generic_string(CRS_NUM_X1, 157, strCourseNum); +#ifdef wide + if (widescreen == 0) { + if (COURSE_IS_MAIN_COURSE(gCurrCourseNum)) { + print_generic_string(10, 40, textCurrRatio43); + } else { + print_generic_string(10, 20, textCurrRatio43); + } + + } + else { + if (COURSE_IS_MAIN_COURSE(gCurrCourseNum)) { + print_generic_string(10, 40, textCurrRatio169); + } else { + print_generic_string(10, 20, textCurrRatio169); + } + + print_generic_string(10, 200, textWideInfo); + print_generic_string(10, 180, textWideInfo2); + } +#endif #endif actName = segmented_to_virtual(actNameTbl[(gCurrCourseNum - 1) * 6 + gDialogCourseActNum - 1]); @@ -2557,10 +2583,28 @@ void render_pause_castle_main_strings(s16 x, s16 y) { } } } - +#ifdef wide + if (gPlayer1Controller->buttonPressed & L_TRIG){ + if (widescreen == 0){ + widescreen = 1; + } + else{ + widescreen = 0; + } + } +#endif gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); - +#ifdef wide + if (widescreen == 0) { + print_generic_string(10, 20, textCurrRatio43); + } + else { + print_generic_string(10, 20, textCurrRatio169); + print_generic_string(10, 200, textWideInfo); + print_generic_string(10, 180, textWideInfo2); + } +#endif if (gDialogLineNum < COURSE_STAGES_COUNT) { courseName = segmented_to_virtual(courseNameTbl[gDialogLineNum]); render_pause_castle_course_stars(x, y, gCurrSaveFileNum - 1, gDialogLineNum); @@ -2626,6 +2670,14 @@ s16 render_pause_courses_and_castle(void) { shade_screen(); render_pause_my_score_coins(); render_pause_red_coins(); + if (gPlayer1Controller->buttonPressed & L_TRIG){ + if (widescreen == 0){ + widescreen = 1; + } + else{ + widescreen = 0; + } + } if (gMarioStates[0].action & ACT_FLAG_PAUSE_EXIT) { render_pause_course_options(99, 93, &gDialogLineNum, 15); diff --git a/src/game/level_update.c b/src/game/level_update.c index d380d6a2..b9441943 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -1242,6 +1242,10 @@ s32 lvl_init_or_update(s16 initOrUpdate, UNUSED s32 unused) { return result; } +extern u8 widescreen; +void lvl_set_widescreen(void) { + widescreen = 1; +} s32 lvl_init_from_save_file(UNUSED s16 arg0, s32 levelNum) { #ifdef VERSION_EU diff --git a/src/game/level_update.c.orig b/src/game/level_update.c.orig new file mode 100644 index 00000000..d380d6a2 --- /dev/null +++ b/src/game/level_update.c.orig @@ -0,0 +1,1324 @@ +#include + +#include "sm64.h" +#include "seq_ids.h" +#include "dialog_ids.h" +#include "audio/external.h" +#include "level_update.h" +#include "game_init.h" +#include "level_update.h" +#include "main.h" +#include "engine/math_util.h" +#include "engine/graph_node.h" +#include "area.h" +#include "save_file.h" +#include "sound_init.h" +#include "mario.h" +#include "camera.h" +#include "object_list_processor.h" +#include "ingame_menu.h" +#include "obj_behaviors.h" +#include "save_file.h" +#include "debug_course.h" +#ifdef VERSION_EU +#include "memory.h" +#include "eu_translation.h" +#include "segment_symbols.h" +#endif +#include "level_table.h" +#include "course_table.h" +#include "rumble_init.h" + +#define PLAY_MODE_NORMAL 0 +#define PLAY_MODE_PAUSED 2 +#define PLAY_MODE_CHANGE_AREA 3 +#define PLAY_MODE_CHANGE_LEVEL 4 +#define PLAY_MODE_FRAME_ADVANCE 5 + +#define WARP_TYPE_NOT_WARPING 0 +#define WARP_TYPE_CHANGE_LEVEL 1 +#define WARP_TYPE_CHANGE_AREA 2 +#define WARP_TYPE_SAME_AREA 3 + +#define WARP_NODE_F0 0xF0 +#define WARP_NODE_DEATH 0xF1 +#define WARP_NODE_F2 0xF2 +#define WARP_NODE_WARP_FLOOR 0xF3 +#define WARP_NODE_CREDITS_START 0xF8 +#define WARP_NODE_CREDITS_NEXT 0xF9 +#define WARP_NODE_CREDITS_END 0xFA + +#define WARP_NODE_CREDITS_MIN 0xF8 + +// TODO: Make these ifdefs better +const char *credits01[] = { "1GAME DIRECTOR", "SHIGERU MIYAMOTO" }; +const char *credits02[] = { "2ASSISTANT DIRECTORS", "YOSHIAKI KOIZUMI", "TAKASHI TEZUKA" }; +const char *credits03[] = { "2SYSTEM PROGRAMMERS", "YASUNARI NISHIDA", "YOSHINORI TANIMOTO" }; +const char *credits04[] = { "3PROGRAMMERS", "HAJIME YAJIMA", "DAIKI IWAMOTO", "TOSHIO IWAWAKI" }; +#if defined(VERSION_JP) || defined(VERSION_SH) +const char *credits05[] = { "1CAMERA PROGRAMMER", "TAKUMI KAWAGOE" }; +const char *credits06[] = { "1MARIO FACE PROGRAMMER", "GILES GODDARD" }; +const char *credits07[] = { "2COURSE DIRECTORS", "YOICHI YAMADA", "YASUHISA YAMAMURA" }; +const char *credits08[] = { "2COURSE DESIGNERS", "KENTA USUI", "NAOKI MORI" }; +const char *credits09[] = { "3COURSE DESIGNERS", "YOSHIKI HARUHANA", "MAKOTO MIYANAGA", + "KATSUHIKO KANNO" }; +const char *credits10[] = { "1SOUND COMPOSER", "KOJI KONDO" }; + +#ifdef VERSION_SH +const char *credits11[] = { "4SOUND EFFECTS", "SOUND PROGRAMMER", "YOJI INAGAKI", "HIDEAKI SHIMIZU" }; +const char *credits12[] = { "23D ANIMATORS", "YOSHIAKI KOIZUMI", "SATORU TAKIZAWA" }; +const char *credits13[] = { "1CG DESIGNER", "MASANAO ARIMOTO" }; +const char *credits14[] = { "3TECHNICAL SUPPORT", "TAKAO SAWANO", "HIROHITO YOSHIMOTO", "HIROTO YADA" }; +const char *credits15[] = { "1TECHNICAL SUPPORT", "SGI. 64PROJECT STAFF" }; +const char *credits16[] = { "2PROGRESS MANAGEMENT", "KIMIYOSHI FUKUI", "KEIZO KATO" }; +const char *credits17[] = { "4MARIO VOICE", "PEACH VOICE", "CHARLES MARTINET", "LESLIE SWAN" }; +const char *credits18[] = { "3SPECIAL THANKS TO", "JYOHO KAIHATUBU", "ALL NINTENDO", + "MARIO CLUB STAFF" }; +const char *credits19[] = { "1PRODUCER", "SHIGERU MIYAMOTO" }; +const char *credits20[] = { "1EXECUTIVE PRODUCER", "HIROSHI YAMAUCHI" }; +#else // VERSION_JP +const char *credits11[] = { "1SOUND EFFECTS", "YOJI INAGAKI" }; +const char *credits12[] = { "1SOUND PROGRAMMER", "HIDEAKI SHIMIZU" }; +const char *credits13[] = { "23D ANIMATORS", "YOSHIAKI KOIZUMI", "SATORU TAKIZAWA" }; +const char *credits14[] = { "1CG DESIGNER", "MASANAO ARIMOTO" }; +const char *credits15[] = { "3TECHNICAL SUPPORT", "TAKAO SAWANO", "HIROHITO YOSHIMOTO", "HIROTO YADA" }; +const char *credits16[] = { "1TECHNICAL SUPPORT", "SGI. 64PROJECT STAFF" }; +const char *credits17[] = { "2PROGRESS MANAGEMENT", "KIMIYOSHI FUKUI", "KEIZO KATO" }; +const char *credits18[] = { "3SPECIAL THANKS TO", "JYOHO KAIHATUBU", "ALL NINTENDO", + "MARIO CLUB STAFF" }; +const char *credits19[] = { "1PRODUCER", "SHIGERU MIYAMOTO" }; +const char *credits20[] = { "1EXECUTIVE PRODUCER", "HIROSHI YAMAUCHI" }; +#endif +#else // VERSION_US || VERSION_EU +const char *credits05[] = { + "4CAMERA PROGRAMMER", "MARIO FACE PROGRAMMER", "TAKUMI KAWAGOE", "GILES GODDARD" +}; // US combines camera programmer and Mario face programmer +const char *credits06[] = { "2COURSE DIRECTORS", "YOICHI YAMADA", "YASUHISA YAMAMURA" }; +const char *credits07[] = { "2COURSE DESIGNERS", "KENTA USUI", "NAOKI MORI" }; +const char *credits08[] = { "3COURSE DESIGNERS", "YOSHIKI HARUHANA", "MAKOTO MIYANAGA", + "KATSUHIKO KANNO" }; +#ifdef VERSION_US +const char *credits09[] = { "1SOUND COMPOSER", "KOJI KONDO" }; +const char *credits10[] = { "4SOUND EFFECTS", "SOUND PROGRAMMER", "YOJI INAGAKI", + "HIDEAKI SHIMIZU" }; // as well as sound effects and sound programmer +const char *credits11[] = { "23-D ANIMATORS", "YOSHIAKI KOIZUMI", "SATORU TAKIZAWA" }; +const char *credits12[] = { "1ADDITIONAL GRAPHICS", "MASANAO ARIMOTO" }; +const char *credits13[] = { "3TECHNICAL SUPPORT", "TAKAO SAWANO", "HIROHITO YOSHIMOTO", "HIROTO YADA" }; +const char *credits14[] = { "1TECHNICAL SUPPORT", "SGI N64 PROJECT STAFF" }; +const char *credits15[] = { "2PROGRESS MANAGEMENT", "KIMIYOSHI FUKUI", "KEIZO KATO" }; +const char *credits16[] = { "5SCREEN TEXT WRITER", "TRANSLATION", "LESLIE SWAN", "MINA AKINO", + "HIRO YAMADA" }; // ...in order to make room for these 2 new lines +#else // VERSION_EU +const char *credits09[] = { "7SOUND COMPOSER", "SOUND EFFECTS", "SOUND PROGRAMMER", "KOJI KONDO", + "YOJI INAGAKI", "HIDEAKI SHIMIZU" }; +const char *credits10[] = { "63-D ANIMATORS", "ADDITIONAL GRAPHICS", "YOSHIAKI KOIZUMI", "SATORU TAKIZAWA", + "MASANAO ARIMOTO" }; +const char *credits11[] = { "3TECHNICAL SUPPORT", "TAKAO SAWANO", "HIROHITO YOSHIMOTO", "HIROTO YADA" }; +const char *credits12[] = { "1TECHNICAL SUPPORT", "SGI N64 PROJECT STAFF" }; +const char *credits13[] = { "2PROGRESS MANAGEMENT", "KIMIYOSHI FUKUI", "KEIZO KATO" }; +const char *credits14[] = { "5SCREEN TEXT WRITER", "ENGLISH TRANSLATION", "LESLIE SWAN", "MINA AKINO", + "HIRO YAMADA" }; +const char *credits15[] = { "4SCREEN TEXT WRITER", "FRENCH TRANSLATION", "JULIEN BARDAKOFF", + "KENJI HARAGUCHI" }; +const char *credits16[] = { "4SCREEN TEXT WRITER", "GERMAN TRANSLATION", "THOMAS GOERG", + "THOMAS SPINDLER" }; +#endif +const char *credits17[] = { "4MARIO VOICE", "PEACH VOICE", "CHARLES MARTINET", "LESLIE SWAN" }; +const char *credits18[] = { "3SPECIAL THANKS TO", "EAD STAFF", "ALL NINTENDO PERSONNEL", +#ifdef VERSION_US + "MARIO CLUB STAFF" }; +#else // VERSION_EU + "SUPER MARIO CLUB STAFF" }; +#endif +const char *credits19[] = { "1PRODUCER", "SHIGERU MIYAMOTO" }; +const char *credits20[] = { "1EXECUTIVE PRODUCER", "HIROSHI YAMAUCHI" }; +#endif + + +struct CreditsEntry sCreditsSequence[] = { + { LEVEL_CASTLE_GROUNDS, 1, 1, -128, { 0, 8000, 0 }, NULL }, + { LEVEL_BOB, 1, 1, 117, { 713, 3918, -3889 }, credits01 }, + { LEVEL_WF, 1, 50, 46, { 347, 5376, 326 }, credits02 }, + { LEVEL_JRB, 1, 18, 22, { 3800, -4840, 2727 }, credits03 }, + { LEVEL_CCM, 2, 34, 25, { -5464, 6656, -6575 }, credits04 }, + { LEVEL_BBH, 1, 1, 60, { 257, 1922, 2580 }, credits05 }, + { LEVEL_HMC, 1, -15, 123, { -6469, 1616, -6054 }, credits06 }, + { LEVEL_THI, 3, 17, -32, { 508, 1024, 1942 }, credits07 }, + { LEVEL_LLL, 2, 33, 124, { -73, 82, -1467 }, credits08 }, + { LEVEL_SSL, 1, 65, 98, { -5906, 1024, -2576 }, credits09 }, + { LEVEL_DDD, 1, 50, 47, { -4884, -4607, -272 }, credits10 }, + { LEVEL_SL, 1, 17, -34, { 1925, 3328, 563 }, credits11 }, + { LEVEL_WDW, 1, 33, 105, { -537, 1850, 1818 }, credits12 }, + { LEVEL_TTM, 1, 2, -33, { 2613, 313, 1074 }, credits13 }, + { LEVEL_THI, 1, 51, 54, { -2609, 512, 856 }, credits14 }, + { LEVEL_TTC, 1, 17, -72, { -1304, -71, -967 }, credits15 }, + { LEVEL_RR, 1, 33, 64, { 1565, 1024, -148 }, credits16 }, + { LEVEL_SA, 1, 1, 24, { -1050, -1330, -1559 }, credits17 }, + { LEVEL_COTMC, 1, 49, -16, { -254, 415, -6045 }, credits18 }, + { LEVEL_DDD, 2, -111, -64, { 3948, 1185, -104 }, credits19 }, + { LEVEL_CCM, 1, 33, 31, { 3169, -4607, 5240 }, credits20 }, + { LEVEL_CASTLE_GROUNDS, 1, 1, -128, { 0, 906, -1200 }, NULL }, + { LEVEL_NONE, 0, 1, 0, { 0, 0, 0 }, NULL }, +}; + +struct MarioState gMarioStates[1]; +struct HudDisplay gHudDisplay; +s16 sCurrPlayMode; +u16 D_80339ECA; +s16 sTransitionTimer; +void (*sTransitionUpdate)(s16 *); +struct WarpDest sWarpDest; +s16 D_80339EE0; +s16 sDelayedWarpOp; +s16 sDelayedWarpTimer; +s16 sSourceWarpNodeId; +s32 sDelayedWarpArg; +#if defined(VERSION_EU) || defined(VERSION_SH) +s16 unusedEULevelUpdateBss1; +#endif +s8 sTimerRunning; +s8 gNeverEnteredCastle; + +struct MarioState *gMarioState = &gMarioStates[0]; +u8 unused1[4] = { 0 }; +s8 sWarpCheckpointActive = FALSE; +u8 unused3[4]; +u8 unused4[2]; + +u16 level_control_timer(s32 timerOp) { + switch (timerOp) { + case TIMER_CONTROL_SHOW: + gHudDisplay.flags |= HUD_DISPLAY_FLAG_TIMER; + sTimerRunning = FALSE; + gHudDisplay.timer = 0; + break; + + case TIMER_CONTROL_START: + sTimerRunning = TRUE; + break; + + case TIMER_CONTROL_STOP: + sTimerRunning = FALSE; + break; + + case TIMER_CONTROL_HIDE: + gHudDisplay.flags &= ~HUD_DISPLAY_FLAG_TIMER; + sTimerRunning = FALSE; + gHudDisplay.timer = 0; + break; + } + + return gHudDisplay.timer; +} + +u32 pressed_pause(void) { + u32 val4 = get_dialog_id() >= 0; + u32 intangible = (gMarioState->action & ACT_FLAG_INTANGIBLE) != 0; + + if (!intangible && !val4 && !gWarpTransition.isActive && sDelayedWarpOp == WARP_OP_NONE + && (gPlayer1Controller->buttonPressed & START_BUTTON)) { + return TRUE; + } + + return FALSE; +} + +void set_play_mode(s16 playMode) { + sCurrPlayMode = playMode; + D_80339ECA = 0; +} + +void warp_special(s32 arg) { + sCurrPlayMode = PLAY_MODE_CHANGE_LEVEL; + D_80339ECA = 0; + D_80339EE0 = arg; +} + +void fade_into_special_warp(u32 arg, u32 color) { + if (color != 0) { + color = 0xFF; + } + + fadeout_music(190); + play_transition(WARP_TRANSITION_FADE_INTO_COLOR, 0x10, color, color, color); + level_set_transition(30, NULL); + + warp_special(arg); +} + +void stub_level_update_1(void) { +} + +void load_level_init_text(u32 arg) { + s32 gotAchievement; + u32 dialogID = gCurrentArea->dialog[arg]; + + switch (dialogID) { + case DIALOG_129: + gotAchievement = save_file_get_flags() & SAVE_FLAG_HAVE_VANISH_CAP; + break; + + case DIALOG_130: + gotAchievement = save_file_get_flags() & SAVE_FLAG_HAVE_METAL_CAP; + break; + + case DIALOG_131: + gotAchievement = save_file_get_flags() & SAVE_FLAG_HAVE_WING_CAP; + break; + + case 255: + gotAchievement = TRUE; + break; + + default: + gotAchievement = save_file_get_star_flags(gCurrSaveFileNum - 1, gCurrCourseNum - 1); + break; + } + + if (!gotAchievement) { + level_set_transition(-1, NULL); + create_dialog_box(dialogID); + } +} + +void init_door_warp(struct SpawnInfo *spawnInfo, u32 arg1) { + if (arg1 & 0x00000002) { + spawnInfo->startAngle[1] += 0x8000; + } + + spawnInfo->startPos[0] += 300.0f * sins(spawnInfo->startAngle[1]); + spawnInfo->startPos[2] += 300.0f * coss(spawnInfo->startAngle[1]); +} + +void set_mario_initial_cap_powerup(struct MarioState *m) { + u32 capCourseIndex = gCurrCourseNum - COURSE_CAP_COURSES; + + switch (capCourseIndex) { + case COURSE_COTMC - COURSE_CAP_COURSES: + m->flags |= MARIO_METAL_CAP | MARIO_CAP_ON_HEAD; + m->capTimer = 600; + break; + + case COURSE_TOTWC - COURSE_CAP_COURSES: + m->flags |= MARIO_WING_CAP | MARIO_CAP_ON_HEAD; + m->capTimer = 1200; + break; + + case COURSE_VCUTM - COURSE_CAP_COURSES: + m->flags |= MARIO_VANISH_CAP | MARIO_CAP_ON_HEAD; + m->capTimer = 600; + break; + } +} + +void set_mario_initial_action(struct MarioState *m, u32 spawnType, u32 actionArg) { + switch (spawnType) { + case MARIO_SPAWN_DOOR_WARP: + set_mario_action(m, ACT_WARP_DOOR_SPAWN, actionArg); + break; + case MARIO_SPAWN_UNKNOWN_02: + set_mario_action(m, ACT_IDLE, 0); + break; + case MARIO_SPAWN_UNKNOWN_03: + set_mario_action(m, ACT_EMERGE_FROM_PIPE, 0); + break; + case MARIO_SPAWN_TELEPORT: + set_mario_action(m, ACT_TELEPORT_FADE_IN, 0); + break; + case MARIO_SPAWN_INSTANT_ACTIVE: + set_mario_action(m, ACT_IDLE, 0); + break; + case MARIO_SPAWN_AIRBORNE: + set_mario_action(m, ACT_SPAWN_NO_SPIN_AIRBORNE, 0); + break; + case MARIO_SPAWN_HARD_AIR_KNOCKBACK: + set_mario_action(m, ACT_HARD_BACKWARD_AIR_KB, 0); + break; + case MARIO_SPAWN_SPIN_AIRBORNE_CIRCLE: + set_mario_action(m, ACT_SPAWN_SPIN_AIRBORNE, 0); + break; + case MARIO_SPAWN_DEATH: + set_mario_action(m, ACT_FALLING_DEATH_EXIT, 0); + break; + case MARIO_SPAWN_SPIN_AIRBORNE: + set_mario_action(m, ACT_SPAWN_SPIN_AIRBORNE, 0); + break; + case MARIO_SPAWN_FLYING: + set_mario_action(m, ACT_FLYING, 2); + break; + case MARIO_SPAWN_SWIMMING: + set_mario_action(m, ACT_WATER_IDLE, 1); + break; + case MARIO_SPAWN_PAINTING_STAR_COLLECT: + set_mario_action(m, ACT_EXIT_AIRBORNE, 0); + break; + case MARIO_SPAWN_PAINTING_DEATH: + set_mario_action(m, ACT_DEATH_EXIT, 0); + break; + case MARIO_SPAWN_AIRBORNE_STAR_COLLECT: + set_mario_action(m, ACT_FALLING_EXIT_AIRBORNE, 0); + break; + case MARIO_SPAWN_AIRBORNE_DEATH: + set_mario_action(m, ACT_UNUSED_DEATH_EXIT, 0); + break; + case MARIO_SPAWN_LAUNCH_STAR_COLLECT: + set_mario_action(m, ACT_SPECIAL_EXIT_AIRBORNE, 0); + break; + case MARIO_SPAWN_LAUNCH_DEATH: + set_mario_action(m, ACT_SPECIAL_DEATH_EXIT, 0); + break; + } + + set_mario_initial_cap_powerup(m); +} + +void init_mario_after_warp(void) { + struct ObjectWarpNode *spawnNode = area_get_warp_node(sWarpDest.nodeId); + u32 marioSpawnType = get_mario_spawn_type(spawnNode->object); + + if (gMarioState->action != ACT_UNINITIALIZED) { + gPlayerSpawnInfos[0].startPos[0] = (s16) spawnNode->object->oPosX; + gPlayerSpawnInfos[0].startPos[1] = (s16) spawnNode->object->oPosY; + gPlayerSpawnInfos[0].startPos[2] = (s16) spawnNode->object->oPosZ; + + gPlayerSpawnInfos[0].startAngle[0] = 0; + gPlayerSpawnInfos[0].startAngle[1] = spawnNode->object->oMoveAngleYaw; + gPlayerSpawnInfos[0].startAngle[2] = 0; + + if (marioSpawnType == MARIO_SPAWN_DOOR_WARP) { + init_door_warp(&gPlayerSpawnInfos[0], sWarpDest.arg); + } + + if (sWarpDest.type == WARP_TYPE_CHANGE_LEVEL || sWarpDest.type == WARP_TYPE_CHANGE_AREA) { + gPlayerSpawnInfos[0].areaIndex = sWarpDest.areaIdx; + load_mario_area(); + } + + init_mario(); + set_mario_initial_action(gMarioState, marioSpawnType, sWarpDest.arg); + + gMarioState->interactObj = spawnNode->object; + gMarioState->usedObj = spawnNode->object; + } + + reset_camera(gCurrentArea->camera); + sWarpDest.type = WARP_TYPE_NOT_WARPING; + sDelayedWarpOp = WARP_OP_NONE; + + switch (marioSpawnType) { + case MARIO_SPAWN_UNKNOWN_03: + play_transition(WARP_TRANSITION_FADE_FROM_STAR, 0x10, 0x00, 0x00, 0x00); + break; + case MARIO_SPAWN_DOOR_WARP: + play_transition(WARP_TRANSITION_FADE_FROM_CIRCLE, 0x10, 0x00, 0x00, 0x00); + break; + case MARIO_SPAWN_TELEPORT: + play_transition(WARP_TRANSITION_FADE_FROM_COLOR, 0x14, 0xFF, 0xFF, 0xFF); + break; + case MARIO_SPAWN_SPIN_AIRBORNE: + play_transition(WARP_TRANSITION_FADE_FROM_COLOR, 0x1A, 0xFF, 0xFF, 0xFF); + break; + case MARIO_SPAWN_SPIN_AIRBORNE_CIRCLE: + play_transition(WARP_TRANSITION_FADE_FROM_CIRCLE, 0x10, 0x00, 0x00, 0x00); + break; + case MARIO_SPAWN_UNKNOWN_27: + play_transition(WARP_TRANSITION_FADE_FROM_COLOR, 0x10, 0x00, 0x00, 0x00); + break; + default: + play_transition(WARP_TRANSITION_FADE_FROM_STAR, 0x10, 0x00, 0x00, 0x00); + break; + } + + if (gCurrDemoInput == NULL) { + set_background_music(gCurrentArea->musicParam, gCurrentArea->musicParam2, 0); + + if (gMarioState->flags & MARIO_METAL_CAP) { + play_cap_music(SEQUENCE_ARGS(4, SEQ_EVENT_METAL_CAP)); + } + + if (gMarioState->flags & (MARIO_VANISH_CAP | MARIO_WING_CAP)) { + play_cap_music(SEQUENCE_ARGS(4, SEQ_EVENT_POWERUP)); + } + +#ifndef VERSION_JP + if (gCurrLevelNum == LEVEL_BOB + && get_current_background_music() != SEQUENCE_ARGS(4, SEQ_LEVEL_SLIDE) + && sTimerRunning) { + play_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(4, SEQ_LEVEL_SLIDE), 0); + } +#endif + + if (sWarpDest.levelNum == LEVEL_CASTLE && sWarpDest.areaIdx == 1 +#ifndef VERSION_JP + && (sWarpDest.nodeId == 31 || sWarpDest.nodeId == 32) +#else + && sWarpDest.nodeId == 31 +#endif + ) + play_sound(SOUND_MENU_MARIO_CASTLE_WARP, gGlobalSoundSource); +#ifndef VERSION_JP + if (sWarpDest.levelNum == LEVEL_CASTLE_GROUNDS && sWarpDest.areaIdx == 1 + && (sWarpDest.nodeId == 7 || sWarpDest.nodeId == 10 || sWarpDest.nodeId == 20 + || sWarpDest.nodeId == 30)) { + play_sound(SOUND_MENU_MARIO_CASTLE_WARP, gGlobalSoundSource); + } +#endif + } +} + +// used for warps inside one level +void warp_area(void) { + if (sWarpDest.type != WARP_TYPE_NOT_WARPING) { + if (sWarpDest.type == WARP_TYPE_CHANGE_AREA) { + level_control_timer(TIMER_CONTROL_HIDE); + unload_mario_area(); + load_area(sWarpDest.areaIdx); + } + + init_mario_after_warp(); + } +} + +// used for warps between levels +void warp_level(void) { + gCurrLevelNum = sWarpDest.levelNum; + + level_control_timer(TIMER_CONTROL_HIDE); + + load_area(sWarpDest.areaIdx); + init_mario_after_warp(); +} + +void warp_credits(void) { + s32 marioAction; + + switch (sWarpDest.nodeId) { + case WARP_NODE_CREDITS_START: + marioAction = ACT_END_PEACH_CUTSCENE; + break; + + case WARP_NODE_CREDITS_NEXT: + marioAction = ACT_CREDITS_CUTSCENE; + break; + + case WARP_NODE_CREDITS_END: + marioAction = ACT_END_WAVING_CUTSCENE; + break; + } + + gCurrLevelNum = sWarpDest.levelNum; + + load_area(sWarpDest.areaIdx); + + vec3s_set(gPlayerSpawnInfos[0].startPos, gCurrCreditsEntry->marioPos[0], + gCurrCreditsEntry->marioPos[1], gCurrCreditsEntry->marioPos[2]); + + vec3s_set(gPlayerSpawnInfos[0].startAngle, 0, 0x100 * gCurrCreditsEntry->marioAngle, 0); + + gPlayerSpawnInfos[0].areaIndex = sWarpDest.areaIdx; + + load_mario_area(); + init_mario(); + + set_mario_action(gMarioState, marioAction, 0); + + reset_camera(gCurrentArea->camera); + + sWarpDest.type = WARP_TYPE_NOT_WARPING; + sDelayedWarpOp = WARP_OP_NONE; + + play_transition(WARP_TRANSITION_FADE_FROM_COLOR, 0x14, 0x00, 0x00, 0x00); + + if (gCurrCreditsEntry == NULL || gCurrCreditsEntry == sCreditsSequence) { + set_background_music(gCurrentArea->musicParam, gCurrentArea->musicParam2, 0); + } +} + +void check_instant_warp(void) { + s16 cameraAngle; + struct Surface *floor; + + if (gCurrLevelNum == LEVEL_CASTLE + && save_file_get_total_star_count(gCurrSaveFileNum - 1, COURSE_MIN - 1, COURSE_MAX - 1) >= 70) { + return; + } + + if ((floor = gMarioState->floor) != NULL) { + s32 index = floor->type - SURFACE_INSTANT_WARP_1B; + if (index >= INSTANT_WARP_INDEX_START && index < INSTANT_WARP_INDEX_STOP + && gCurrentArea->instantWarps != NULL) { + struct InstantWarp *warp = &gCurrentArea->instantWarps[index]; + + if (warp->id != 0) { + gMarioState->pos[0] += warp->displacement[0]; + gMarioState->pos[1] += warp->displacement[1]; + gMarioState->pos[2] += warp->displacement[2]; + + gMarioState->marioObj->oPosX = gMarioState->pos[0]; + gMarioState->marioObj->oPosY = gMarioState->pos[1]; + gMarioState->marioObj->oPosZ = gMarioState->pos[2]; + + cameraAngle = gMarioState->area->camera->yaw; + + change_area(warp->area); + gMarioState->area = gCurrentArea; + + warp_camera(warp->displacement[0], warp->displacement[1], warp->displacement[2]); + + gMarioState->area->camera->yaw = cameraAngle; + } + } + } +} + +s16 music_changed_through_warp(s16 arg) { + struct ObjectWarpNode *warpNode = area_get_warp_node(arg); + s16 levelNum = warpNode->node.destLevel & 0x7F; + +#if BUGFIX_KOOPA_RACE_MUSIC + + s16 destArea = warpNode->node.destArea; + s16 val4 = TRUE; + s16 sp2C; + + if (levelNum == LEVEL_BOB && levelNum == gCurrLevelNum && destArea == gCurrAreaIndex) { + sp2C = get_current_background_music(); + if (sp2C == SEQUENCE_ARGS(4, SEQ_EVENT_POWERUP | SEQ_VARIATION) + || sp2C == SEQUENCE_ARGS(4, SEQ_EVENT_POWERUP)) { + val4 = 0; + } + } else { + u16 val8 = gAreas[destArea].musicParam; + u16 val6 = gAreas[destArea].musicParam2; + + val4 = levelNum == gCurrLevelNum && val8 == gCurrentArea->musicParam + && val6 == gCurrentArea->musicParam2; + + if (get_current_background_music() != val6) { + val4 = FALSE; + } + } + return val4; + +#else + + u16 val8 = gAreas[warpNode->node.destArea].musicParam; + u16 val6 = gAreas[warpNode->node.destArea].musicParam2; + + s16 val4 = levelNum == gCurrLevelNum && val8 == gCurrentArea->musicParam + && val6 == gCurrentArea->musicParam2; + + if (get_current_background_music() != val6) { + val4 = FALSE; + } + return val4; + +#endif +} + +/** + * Set the current warp type and destination level/area/node. + */ +void initiate_warp(s16 destLevel, s16 destArea, s16 destWarpNode, s32 arg3) { + if (destWarpNode >= WARP_NODE_CREDITS_MIN) { + sWarpDest.type = WARP_TYPE_CHANGE_LEVEL; + } else if (destLevel != gCurrLevelNum) { + sWarpDest.type = WARP_TYPE_CHANGE_LEVEL; + } else if (destArea != gCurrentArea->index) { + sWarpDest.type = WARP_TYPE_CHANGE_AREA; + } else { + sWarpDest.type = WARP_TYPE_SAME_AREA; + } + + sWarpDest.levelNum = destLevel; + sWarpDest.areaIdx = destArea; + sWarpDest.nodeId = destWarpNode; + sWarpDest.arg = arg3; +} + +// From Surface 0xD3 to 0xFC +#define PAINTING_WARP_INDEX_START 0x00 // Value greater than or equal to Surface 0xD3 +#define PAINTING_WARP_INDEX_FA 0x2A // THI Huge Painting index left +#define PAINTING_WARP_INDEX_END 0x2D // Value less than Surface 0xFD + +/** + * Check if Mario is above and close to a painting warp floor, and return the + * corresponding warp node. + */ +struct WarpNode *get_painting_warp_node(void) { + struct WarpNode *warpNode = NULL; + s32 paintingIndex = gMarioState->floor->type - SURFACE_PAINTING_WARP_D3; + + if (paintingIndex >= PAINTING_WARP_INDEX_START && paintingIndex < PAINTING_WARP_INDEX_END) { + if (paintingIndex < PAINTING_WARP_INDEX_FA + || gMarioState->pos[1] - gMarioState->floorHeight < 80.0f) { + warpNode = &gCurrentArea->paintingWarpNodes[paintingIndex]; + } + } + + return warpNode; +} + +/** + * Check is Mario has entered a painting, and if so, initiate a warp. + */ +void initiate_painting_warp(void) { + if (gCurrentArea->paintingWarpNodes != NULL && gMarioState->floor != NULL) { + struct WarpNode warpNode; + struct WarpNode *pWarpNode = get_painting_warp_node(); + + if (pWarpNode != NULL) { + if (gMarioState->action & ACT_FLAG_INTANGIBLE) { + play_painting_eject_sound(); + } else if (pWarpNode->id != 0) { + warpNode = *pWarpNode; + + if (!(warpNode.destLevel & 0x80)) { + sWarpCheckpointActive = check_warp_checkpoint(&warpNode); + } + + initiate_warp(warpNode.destLevel & 0x7F, warpNode.destArea, warpNode.destNode, 0); + check_if_should_set_warp_checkpoint(&warpNode); + + play_transition_after_delay(WARP_TRANSITION_FADE_INTO_COLOR, 30, 255, 255, 255, 45); + level_set_transition(74, basic_update); + + set_mario_action(gMarioState, ACT_DISAPPEARED, 0); + + gMarioState->marioObj->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE; + + play_sound(SOUND_MENU_STAR_SOUND, gGlobalSoundSource); + fadeout_music(398); +#if ENABLE_RUMBLE + queue_rumble_data(80, 70); + func_sh_8024C89C(1); +#endif + } + } + } +} + +/** + * If there is not already a delayed warp, schedule one. The source node is + * based on the warp operation and sometimes Mario's used object. + * Return the time left until the delayed warp is initiated. + */ +s16 level_trigger_warp(struct MarioState *m, s32 warpOp) { + s32 val04 = TRUE; + + if (sDelayedWarpOp == WARP_OP_NONE) { + m->invincTimer = -1; + sDelayedWarpArg = 0; + sDelayedWarpOp = warpOp; + + switch (warpOp) { + case WARP_OP_DEMO_NEXT: + case WARP_OP_DEMO_END: sDelayedWarpTimer = 20; // Must be one line to match on -O2 + sSourceWarpNodeId = WARP_NODE_F0; + gSavedCourseNum = COURSE_NONE; + val04 = FALSE; + play_transition(WARP_TRANSITION_FADE_INTO_STAR, 0x14, 0x00, 0x00, 0x00); + break; + + case WARP_OP_CREDITS_END: + sDelayedWarpTimer = 60; + sSourceWarpNodeId = WARP_NODE_F0; + val04 = FALSE; + gSavedCourseNum = COURSE_NONE; + play_transition(WARP_TRANSITION_FADE_INTO_COLOR, 0x3C, 0x00, 0x00, 0x00); + break; + + case WARP_OP_STAR_EXIT: + sDelayedWarpTimer = 32; + sSourceWarpNodeId = WARP_NODE_F0; + gSavedCourseNum = COURSE_NONE; + play_transition(WARP_TRANSITION_FADE_INTO_MARIO, 0x20, 0x00, 0x00, 0x00); + break; + + case WARP_OP_DEATH: + if (m->numLives == 0) { + sDelayedWarpOp = WARP_OP_GAME_OVER; + } + sDelayedWarpTimer = 48; + sSourceWarpNodeId = WARP_NODE_DEATH; + play_transition(WARP_TRANSITION_FADE_INTO_BOWSER, 0x30, 0x00, 0x00, 0x00); + play_sound(SOUND_MENU_BOWSER_LAUGH, gGlobalSoundSource); + break; + + case WARP_OP_WARP_FLOOR: + sSourceWarpNodeId = WARP_NODE_WARP_FLOOR; + if (area_get_warp_node(sSourceWarpNodeId) == NULL) { + if (m->numLives == 0) { + sDelayedWarpOp = WARP_OP_GAME_OVER; + } else { + sSourceWarpNodeId = WARP_NODE_DEATH; + } + } + sDelayedWarpTimer = 20; + play_transition(WARP_TRANSITION_FADE_INTO_CIRCLE, 0x14, 0x00, 0x00, 0x00); + break; + + case WARP_OP_UNKNOWN_01: // enter totwc + sDelayedWarpTimer = 30; + sSourceWarpNodeId = WARP_NODE_F2; + play_transition(WARP_TRANSITION_FADE_INTO_COLOR, 0x1E, 0xFF, 0xFF, 0xFF); +#ifndef VERSION_JP + play_sound(SOUND_MENU_STAR_SOUND, gGlobalSoundSource); +#endif + break; + + case WARP_OP_UNKNOWN_02: // bbh enter + sDelayedWarpTimer = 30; + sSourceWarpNodeId = (m->usedObj->oBehParams & 0x00FF0000) >> 16; + play_transition(WARP_TRANSITION_FADE_INTO_COLOR, 0x1E, 0xFF, 0xFF, 0xFF); + break; + + case WARP_OP_TELEPORT: + sDelayedWarpTimer = 20; + sSourceWarpNodeId = (m->usedObj->oBehParams & 0x00FF0000) >> 16; + val04 = !music_changed_through_warp(sSourceWarpNodeId); + play_transition(WARP_TRANSITION_FADE_INTO_COLOR, 0x14, 0xFF, 0xFF, 0xFF); + break; + + case WARP_OP_WARP_DOOR: + sDelayedWarpTimer = 20; + sDelayedWarpArg = m->actionArg; + sSourceWarpNodeId = (m->usedObj->oBehParams & 0x00FF0000) >> 16; + val04 = !music_changed_through_warp(sSourceWarpNodeId); + play_transition(WARP_TRANSITION_FADE_INTO_CIRCLE, 0x14, 0x00, 0x00, 0x00); + break; + + case WARP_OP_WARP_OBJECT: + sDelayedWarpTimer = 20; + sSourceWarpNodeId = (m->usedObj->oBehParams & 0x00FF0000) >> 16; + val04 = !music_changed_through_warp(sSourceWarpNodeId); + play_transition(WARP_TRANSITION_FADE_INTO_STAR, 0x14, 0x00, 0x00, 0x00); + break; + + case WARP_OP_CREDITS_START: + sDelayedWarpTimer = 30; + play_transition(WARP_TRANSITION_FADE_INTO_COLOR, 0x1E, 0x00, 0x00, 0x00); + break; + + case WARP_OP_CREDITS_NEXT: + if (gCurrCreditsEntry == &sCreditsSequence[0]) { + sDelayedWarpTimer = 60; + play_transition(WARP_TRANSITION_FADE_INTO_COLOR, 0x3C, 0x00, 0x00, 0x00); + } else { + sDelayedWarpTimer = 20; + play_transition(WARP_TRANSITION_FADE_INTO_COLOR, 0x14, 0x00, 0x00, 0x00); + } + val04 = FALSE; + break; + } + + if (val04 && gCurrDemoInput == NULL) { + fadeout_music((3 * sDelayedWarpTimer / 2) * 8 - 2); + } + } + + return sDelayedWarpTimer; +} + +/** + * If a delayed warp is ready, initiate it. + */ +void initiate_delayed_warp(void) { + struct ObjectWarpNode *warpNode; + s32 destWarpNode; + + if (sDelayedWarpOp != WARP_OP_NONE && --sDelayedWarpTimer == 0) { + reset_dialog_render_state(); + + if (gDebugLevelSelect && (sDelayedWarpOp & WARP_OP_TRIGGERS_LEVEL_SELECT)) { + warp_special(-9); + } else if (gCurrDemoInput != NULL) { + if (sDelayedWarpOp == WARP_OP_DEMO_END) { + warp_special(-8); + } else { + warp_special(-2); + } + } else { + switch (sDelayedWarpOp) { + case WARP_OP_GAME_OVER: + save_file_reload(); + warp_special(-3); + break; + + case WARP_OP_CREDITS_END: + warp_special(-1); + sound_banks_enable(SEQ_PLAYER_SFX, + SOUND_BANKS_ALL & ~SOUND_BANKS_DISABLED_AFTER_CREDITS); + break; + + case WARP_OP_DEMO_NEXT: + warp_special(-2); + break; + + case WARP_OP_CREDITS_START: + gCurrCreditsEntry = &sCreditsSequence[0]; + initiate_warp(gCurrCreditsEntry->levelNum, gCurrCreditsEntry->areaIndex, + WARP_NODE_CREDITS_START, 0); + break; + + case WARP_OP_CREDITS_NEXT: + sound_banks_disable(SEQ_PLAYER_SFX, SOUND_BANKS_ALL); + + gCurrCreditsEntry += 1; + gCurrActNum = gCurrCreditsEntry->unk02 & 0x07; + if ((gCurrCreditsEntry + 1)->levelNum == LEVEL_NONE) { + destWarpNode = WARP_NODE_CREDITS_END; + } else { + destWarpNode = WARP_NODE_CREDITS_NEXT; + } + + initiate_warp(gCurrCreditsEntry->levelNum, gCurrCreditsEntry->areaIndex, + destWarpNode, 0); + break; + + default: + warpNode = area_get_warp_node(sSourceWarpNodeId); + + initiate_warp(warpNode->node.destLevel & 0x7F, warpNode->node.destArea, + warpNode->node.destNode, sDelayedWarpArg); + + check_if_should_set_warp_checkpoint(&warpNode->node); + if (sWarpDest.type != WARP_TYPE_CHANGE_LEVEL) { + level_set_transition(2, NULL); + } + break; + } + } + } +} + +void update_hud_values(void) { + if (gCurrCreditsEntry == NULL) { + s16 numHealthWedges = gMarioState->health > 0 ? gMarioState->health >> 8 : 0; + + if (gCurrCourseNum >= COURSE_MIN) { + gHudDisplay.flags |= HUD_DISPLAY_FLAG_COIN_COUNT; + } else { + gHudDisplay.flags &= ~HUD_DISPLAY_FLAG_COIN_COUNT; + } + + if (gHudDisplay.coins < gMarioState->numCoins) { + if (gGlobalTimer & 0x00000001) { + u32 coinSound; + if (gMarioState->action & (ACT_FLAG_SWIMMING | ACT_FLAG_METAL_WATER)) { + coinSound = SOUND_GENERAL_COIN_WATER; + } else { + coinSound = SOUND_GENERAL_COIN; + } + + gHudDisplay.coins += 1; + play_sound(coinSound, gMarioState->marioObj->header.gfx.cameraToObject); + } + } + + if (gMarioState->numLives > 100) { + gMarioState->numLives = 100; + } + +#if BUGFIX_MAX_LIVES + if (gMarioState->numCoins > 999) { + gMarioState->numCoins = 999; + } + + if (gHudDisplay.coins > 999) { + gHudDisplay.coins = 999; + } +#else + if (gMarioState->numCoins > 999) { + gMarioState->numLives = (s8) 999; //! Wrong variable + } +#endif + + gHudDisplay.stars = gMarioState->numStars; + gHudDisplay.lives = gMarioState->numLives; + gHudDisplay.keys = gMarioState->numKeys; + + if (numHealthWedges > gHudDisplay.wedges) { + play_sound(SOUND_MENU_POWER_METER, gGlobalSoundSource); + } + gHudDisplay.wedges = numHealthWedges; + + if (gMarioState->hurtCounter > 0) { + gHudDisplay.flags |= HUD_DISPLAY_FLAG_EMPHASIZE_POWER; + } else { + gHudDisplay.flags &= ~HUD_DISPLAY_FLAG_EMPHASIZE_POWER; + } + } +} + +/** + * Update objects, HUD, and camera. This update function excludes things like + * endless staircase, warps, pausing, etc. This is used when entering a painting, + * presumably to allow painting and camera updating while avoiding triggering the + * warp twice. + */ +void basic_update(UNUSED s16 *arg) { + area_update_objects(); + update_hud_values(); + + if (gCurrentArea != NULL) { + update_camera(gCurrentArea->camera); + } +} + +s32 play_mode_normal(void) { + if (gCurrDemoInput != NULL) { + print_intro_text(); + if (gPlayer1Controller->buttonPressed & END_DEMO) { + level_trigger_warp(gMarioState, + gCurrLevelNum == LEVEL_PSS ? WARP_OP_DEMO_END : WARP_OP_DEMO_NEXT); + } else if (!gWarpTransition.isActive && sDelayedWarpOp == WARP_OP_NONE + && (gPlayer1Controller->buttonPressed & START_BUTTON)) { + level_trigger_warp(gMarioState, WARP_OP_DEMO_NEXT); + } + } + + warp_area(); + check_instant_warp(); + + if (sTimerRunning && gHudDisplay.timer < 17999) { + gHudDisplay.timer += 1; + } + + area_update_objects(); + update_hud_values(); + + if (gCurrentArea != NULL) { + update_camera(gCurrentArea->camera); + } + + initiate_painting_warp(); + initiate_delayed_warp(); + + // If either initiate_painting_warp or initiate_delayed_warp initiated a + // warp, change play mode accordingly. + if (sCurrPlayMode == PLAY_MODE_NORMAL) { + if (sWarpDest.type == WARP_TYPE_CHANGE_LEVEL) { + set_play_mode(PLAY_MODE_CHANGE_LEVEL); + } else if (sTransitionTimer != 0) { + set_play_mode(PLAY_MODE_CHANGE_AREA); + } else if (pressed_pause()) { + lower_background_noise(1); +#if ENABLE_RUMBLE + cancel_rumble(); +#endif + gCameraMovementFlags |= CAM_MOVE_PAUSE_SCREEN; + set_play_mode(PLAY_MODE_PAUSED); + } + } + + return 0; +} + +s32 play_mode_paused(void) { + if (gPauseScreenMode == 0) { + set_menu_mode(RENDER_PAUSE_SCREEN); + } else if (gPauseScreenMode == 1) { + raise_background_noise(1); + gCameraMovementFlags &= ~CAM_MOVE_PAUSE_SCREEN; + set_play_mode(PLAY_MODE_NORMAL); + } else { + // Exit level + + if (gDebugLevelSelect) { + fade_into_special_warp(-9, 1); + } else { + initiate_warp(LEVEL_CASTLE, 1, 0x1F, 0); + fade_into_special_warp(0, 0); + gSavedCourseNum = COURSE_NONE; + } + + gCameraMovementFlags &= ~CAM_MOVE_PAUSE_SCREEN; + } + + return 0; +} + +/** + * Debug mode that lets you frame advance by pressing D-pad down. Unfortunately + * it uses the pause camera, making it basically unusable in most levels. + */ +s32 play_mode_frame_advance(void) { + if (gPlayer1Controller->buttonPressed & D_JPAD) { + gCameraMovementFlags &= ~CAM_MOVE_PAUSE_SCREEN; + play_mode_normal(); + } else if (gPlayer1Controller->buttonPressed & START_BUTTON) { + gCameraMovementFlags &= ~CAM_MOVE_PAUSE_SCREEN; + raise_background_noise(1); + set_play_mode(PLAY_MODE_NORMAL); + } else { + gCameraMovementFlags |= CAM_MOVE_PAUSE_SCREEN; + } + + return 0; +} + +/** + * Set the transition, which is a period of time after the warp is initiated + * but before it actually occurs. If updateFunction is not NULL, it will be + * called each frame during the transition. + */ +void level_set_transition(s16 length, void (*updateFunction)(s16 *)) { + sTransitionTimer = length; + sTransitionUpdate = updateFunction; +} + +/** + * Play the transition and then return to normal play mode. + */ +s32 play_mode_change_area(void) { + //! This maybe was supposed to be sTransitionTimer == -1? sTransitionUpdate + // is never set to -1. + if (sTransitionUpdate == (void (*)(s16 *)) - 1) { + update_camera(gCurrentArea->camera); + } else if (sTransitionUpdate != NULL) { + sTransitionUpdate(&sTransitionTimer); + } + + if (sTransitionTimer > 0) { + sTransitionTimer -= 1; + } + + if (sTransitionTimer == 0) { + sTransitionUpdate = NULL; + set_play_mode(PLAY_MODE_NORMAL); + } + + return 0; +} + +/** + * Play the transition and then return to normal play mode. + */ +s32 play_mode_change_level(void) { + if (sTransitionUpdate != NULL) { + sTransitionUpdate(&sTransitionTimer); + } + + if (--sTransitionTimer == -1) { + gHudDisplay.flags = HUD_DISPLAY_NONE; + sTransitionTimer = 0; + sTransitionUpdate = NULL; + + if (sWarpDest.type != WARP_TYPE_NOT_WARPING) { + return sWarpDest.levelNum; + } else { + return D_80339EE0; + } + } + + return 0; +} + +/** + * Unused play mode. Doesn't call transition update and doesn't reset transition at the end. + */ +static s32 play_mode_unused(void) { + if (--sTransitionTimer == -1) { + gHudDisplay.flags = HUD_DISPLAY_NONE; + + if (sWarpDest.type != WARP_TYPE_NOT_WARPING) { + return sWarpDest.levelNum; + } else { + return D_80339EE0; + } + } + + return 0; +} + +s32 update_level(void) { + s32 changeLevel; + + switch (sCurrPlayMode) { + case PLAY_MODE_NORMAL: + changeLevel = play_mode_normal(); + break; + case PLAY_MODE_PAUSED: + changeLevel = play_mode_paused(); + break; + case PLAY_MODE_CHANGE_AREA: + changeLevel = play_mode_change_area(); + break; + case PLAY_MODE_CHANGE_LEVEL: + changeLevel = play_mode_change_level(); + break; + case PLAY_MODE_FRAME_ADVANCE: + changeLevel = play_mode_frame_advance(); + break; + } + + if (changeLevel) { + reset_volume(); + enable_background_sound(); + } + + return changeLevel; +} + +s32 init_level(void) { + s32 val4 = 0; + + set_play_mode(PLAY_MODE_NORMAL); + + sDelayedWarpOp = WARP_OP_NONE; + sTransitionTimer = 0; + D_80339EE0 = 0; + + if (gCurrCreditsEntry == NULL) { + gHudDisplay.flags = HUD_DISPLAY_DEFAULT; + } else { + gHudDisplay.flags = HUD_DISPLAY_NONE; + } + + sTimerRunning = FALSE; + + if (sWarpDest.type != WARP_TYPE_NOT_WARPING) { + if (sWarpDest.nodeId >= WARP_NODE_CREDITS_MIN) { + warp_credits(); + } else { + warp_level(); + } + } else { + if (gPlayerSpawnInfos[0].areaIndex >= 0) { + load_mario_area(); + init_mario(); + } + + if (gCurrentArea != NULL) { + reset_camera(gCurrentArea->camera); + + if (gCurrDemoInput != NULL) { + set_mario_action(gMarioState, ACT_IDLE, 0); + } else if (gDebugLevelSelect == 0) { + if (gMarioState->action != ACT_UNINITIALIZED) { + set_mario_action(gMarioState, ACT_IDLE, 0); + } + } + } + + if (val4 != 0) { + play_transition(WARP_TRANSITION_FADE_FROM_COLOR, 0x5A, 0xFF, 0xFF, 0xFF); + } else { + play_transition(WARP_TRANSITION_FADE_FROM_STAR, 0x10, 0xFF, 0xFF, 0xFF); + } + + if (gCurrDemoInput == NULL) { + set_background_music(gCurrentArea->musicParam, gCurrentArea->musicParam2, 0); + } + } +#if ENABLE_RUMBLE + if (gCurrDemoInput == NULL) { + cancel_rumble(); + } +#endif + + if (gMarioState->action == ACT_INTRO_CUTSCENE) { + sound_banks_disable(SEQ_PLAYER_SFX, SOUND_BANKS_DISABLED_DURING_INTRO_CUTSCENE); + } + + return 1; +} + +/** + * Initialize the current level if initOrUpdate is 0, or update the level if it is 1. + */ +s32 lvl_init_or_update(s16 initOrUpdate, UNUSED s32 unused) { + s32 result = 0; + + switch (initOrUpdate) { + case 0: + result = init_level(); + break; + case 1: + result = update_level(); + break; + } + + return result; +} + +s32 lvl_init_from_save_file(UNUSED s16 arg0, s32 levelNum) { +#ifdef VERSION_EU + s16 var = eu_get_language(); + switch (var) { + case LANGUAGE_ENGLISH: + load_segment_decompress(0x19, _translation_en_yay0SegmentRomStart, + _translation_en_yay0SegmentRomEnd); + break; + case LANGUAGE_FRENCH: + load_segment_decompress(0x19, _translation_fr_yay0SegmentRomStart, + _translation_fr_yay0SegmentRomEnd); + break; + case LANGUAGE_GERMAN: + load_segment_decompress(0x19, _translation_de_yay0SegmentRomStart, + _translation_de_yay0SegmentRomEnd); + break; + } +#endif + sWarpDest.type = WARP_TYPE_NOT_WARPING; + sDelayedWarpOp = WARP_OP_NONE; + gNeverEnteredCastle = !save_file_exists(gCurrSaveFileNum - 1); + + gCurrLevelNum = levelNum; + gCurrCourseNum = COURSE_NONE; + gSavedCourseNum = COURSE_NONE; + gCurrCreditsEntry = NULL; + gSpecialTripleJump = FALSE; + + init_mario_from_save_file(); + disable_warp_checkpoint(); + save_file_move_cap_to_default_location(); + select_mario_cam_mode(); + set_yoshi_as_not_dead(); + + return levelNum; +} + +s32 lvl_set_current_level(UNUSED s16 arg0, s32 levelNum) { + s32 warpCheckpointActive = sWarpCheckpointActive; + + sWarpCheckpointActive = FALSE; + gCurrLevelNum = levelNum; + gCurrCourseNum = gLevelToCourseNumTable[levelNum - 1]; + + if (gCurrDemoInput != NULL || gCurrCreditsEntry != NULL || gCurrCourseNum == COURSE_NONE) { + return 0; + } + + if (gCurrLevelNum != LEVEL_BOWSER_1 && gCurrLevelNum != LEVEL_BOWSER_2 + && gCurrLevelNum != LEVEL_BOWSER_3) { + gMarioState->numCoins = 0; + gHudDisplay.coins = 0; + gCurrCourseStarFlags = save_file_get_star_flags(gCurrSaveFileNum - 1, gCurrCourseNum - 1); + } + + if (gSavedCourseNum != gCurrCourseNum) { + gSavedCourseNum = gCurrCourseNum; + nop_change_course(); + disable_warp_checkpoint(); + } + + if (gCurrCourseNum > COURSE_STAGES_MAX || warpCheckpointActive) { + return 0; + } + + if (gDebugLevelSelect && !gShowProfiler) { + return 0; + } + + return 1; +} + +/** + * Play the "thank you so much for to playing my game" sound. + */ +s32 lvl_play_the_end_screen_sound(UNUSED s16 arg0, UNUSED s32 arg1) { + play_sound(SOUND_MENU_THANK_YOU_PLAYING_MY_GAME, gGlobalSoundSource); + return 1; +} diff --git a/src/game/level_update.h b/src/game/level_update.h index 521b4efe..58d4e750 100644 --- a/src/game/level_update.h +++ b/src/game/level_update.h @@ -128,5 +128,6 @@ s32 lvl_init_from_save_file(UNUSED s16 arg0, s32 levelNum); s32 lvl_set_current_level(UNUSED s16 arg0, s32 levelNum); s32 lvl_play_the_end_screen_sound(UNUSED s16 arg0, UNUSED s32 arg1); void basic_update(UNUSED s16 *arg); +void lvl_set_widescreen(void); #endif // LEVEL_UPDATE_H diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c index a3007a59..8089f9bc 100644 --- a/src/game/rendering_graph_node.c +++ b/src/game/rendering_graph_node.c @@ -10,6 +10,7 @@ #include "rendering_graph_node.h" #include "shadow.h" #include "sm64.h" +#define WIDESCREEN /** * This file contains the code that processes the scene graph for rendering. @@ -39,6 +40,7 @@ s16 gMatStackIndex; Mat4 gMatStack[32]; Mtx *gMatStackFixed[32]; +f32 aspect; /** * Animation nodes have state in global variables, so this struct captures @@ -235,6 +237,7 @@ static void geo_process_ortho_projection(struct GraphNodeOrthoProjection *node) /** * Process a perspective projection node. */ +extern u8 widescreen; static void geo_process_perspective(struct GraphNodePerspective *node) { if (node->fnNode.func != NULL) { node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); @@ -246,7 +249,12 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { #ifdef VERSION_EU f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; #else - f32 aspect = (f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height; + if (widescreen == 1){ + aspect = 1.775f; + } + else{ + aspect = 1.33333f; + } #endif guPerspective(mtx, &perspNorm, node->fov, aspect, node->near / WORLD_SCALE, node->far / WORLD_SCALE, 1.0f); @@ -763,7 +771,7 @@ static s32 obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { // ! @bug The aspect ratio is not accounted for. When the fov value is 45, // the horizontal effective fov is actually 60 degrees, so you can see objects // visibly pop in or out at the edge of the screen. - halfFov = (gCurGraphNodeCamFrustum->fov / 2.0f + 1.0f) * 32768.0f / 180.0f + 0.5f; + halfFov = ((gCurGraphNodeCamFrustum->fov*aspect) / 2.0f + 1.0f) * 32768.0f / 180.0f + 0.5f; hScreenEdge = -matrix[3][2] * sins(halfFov) / coss(halfFov); // -matrix[3][2] is the depth, which gets multiplied by tan(halfFov) to get diff --git a/src/game/rendering_graph_node.c.orig b/src/game/rendering_graph_node.c.orig new file mode 100644 index 00000000..a3007a59 --- /dev/null +++ b/src/game/rendering_graph_node.c.orig @@ -0,0 +1,1092 @@ +#include + +#include "area.h" +#include "engine/math_util.h" +#include "game_init.h" +#include "gfx_dimensions.h" +#include "main.h" +#include "memory.h" +#include "print.h" +#include "rendering_graph_node.h" +#include "shadow.h" +#include "sm64.h" + +/** + * This file contains the code that processes the scene graph for rendering. + * The scene graph is responsible for drawing everything except the HUD / text boxes. + * First the root of the scene graph is processed when geo_process_root + * is called from level_script.c. The rest of the tree is traversed recursively + * using the function geo_process_node_and_siblings, which switches over all + * geo node types and calls a specialized function accordingly. + * The types are defined in engine/graph_node.h + * + * The scene graph typically looks like: + * - Root (viewport) + * - Master list + * - Ortho projection + * - Background (skybox) + * - Master list + * - Perspective + * - Camera + * - + * - Object parent + * - + * - Master list + * - Script node (Cannon overlay) + * + */ + +s16 gMatStackIndex; +Mat4 gMatStack[32]; +Mtx *gMatStackFixed[32]; + +/** + * Animation nodes have state in global variables, so this struct captures + * the animation state so a 'context switch' can be made when rendering the + * held object. + */ +struct GeoAnimState { + /*0x00*/ u8 type; + /*0x01*/ u8 enabled; + /*0x02*/ s16 frame; + /*0x04*/ f32 translationMultiplier; + /*0x08*/ u16 *attribute; + /*0x0C*/ s16 *data; +}; + +// For some reason, this is a GeoAnimState struct, but the current state consists +// of separate global variables. It won't match EU otherwise. +struct GeoAnimState gGeoTempState; + +u8 gCurAnimType; +u8 gCurAnimEnabled; +s16 gCurrAnimFrame; +f32 gCurAnimTranslationMultiplier; +u16 *gCurrAnimAttribute; +s16 *gCurAnimData; + +struct AllocOnlyPool *gDisplayListHeap; + +struct RenderModeContainer { + u32 modes[8]; +}; + +/* Rendermode settings for cycle 1 for all 8 layers. */ +struct RenderModeContainer renderModeTable_1Cycle[2] = { { { + G_RM_OPA_SURF, + G_RM_AA_OPA_SURF, + G_RM_AA_OPA_SURF, + G_RM_AA_OPA_SURF, + G_RM_AA_TEX_EDGE, + G_RM_AA_XLU_SURF, + G_RM_AA_XLU_SURF, + G_RM_AA_XLU_SURF, + } }, + { { + /* z-buffered */ + G_RM_ZB_OPA_SURF, + G_RM_AA_ZB_OPA_SURF, + G_RM_AA_ZB_OPA_DECAL, + G_RM_AA_ZB_OPA_INTER, + G_RM_AA_ZB_TEX_EDGE, + G_RM_AA_ZB_XLU_SURF, + G_RM_AA_ZB_XLU_DECAL, + G_RM_AA_ZB_XLU_INTER, + } } }; + +/* Rendermode settings for cycle 2 for all 8 layers. */ +struct RenderModeContainer renderModeTable_2Cycle[2] = { { { + G_RM_OPA_SURF2, + G_RM_AA_OPA_SURF2, + G_RM_AA_OPA_SURF2, + G_RM_AA_OPA_SURF2, + G_RM_AA_TEX_EDGE2, + G_RM_AA_XLU_SURF2, + G_RM_AA_XLU_SURF2, + G_RM_AA_XLU_SURF2, + } }, + { { + /* z-buffered */ + G_RM_ZB_OPA_SURF2, + G_RM_AA_ZB_OPA_SURF2, + G_RM_AA_ZB_OPA_DECAL2, + G_RM_AA_ZB_OPA_INTER2, + G_RM_AA_ZB_TEX_EDGE2, + G_RM_AA_ZB_XLU_SURF2, + G_RM_AA_ZB_XLU_DECAL2, + G_RM_AA_ZB_XLU_INTER2, + } } }; + +struct GraphNodeRoot *gCurGraphNodeRoot = NULL; +struct GraphNodeMasterList *gCurGraphNodeMasterList = NULL; +struct GraphNodePerspective *gCurGraphNodeCamFrustum = NULL; +struct GraphNodeCamera *gCurGraphNodeCamera = NULL; +struct GraphNodeObject *gCurGraphNodeObject = NULL; +struct GraphNodeHeldObject *gCurGraphNodeHeldObject = NULL; +u16 gAreaUpdateCounter = 0; + +#ifdef F3DEX_GBI_2 +LookAt lookAt; +#endif + +/** + * Process a master list node. + */ +static void geo_process_master_list_sub(struct GraphNodeMasterList *node) { + struct DisplayListNode *currList; + s32 i; + s32 enableZBuffer = (node->node.flags & GRAPH_RENDER_Z_BUFFER) != 0; + struct RenderModeContainer *modeList = &renderModeTable_1Cycle[enableZBuffer]; + struct RenderModeContainer *mode2List = &renderModeTable_2Cycle[enableZBuffer]; + + // @bug This is where the LookAt values should be calculated but aren't. + // As a result, environment mapping is broken on Fast3DEX2 without the + // changes below. +#ifdef F3DEX_GBI_2 + Mtx lMtx; + guLookAtReflect(&lMtx, &lookAt, 0, 0, 0, /* eye */ 0, 0, 1, /* at */ 1, 0, 0 /* up */); +#endif + + if (enableZBuffer != 0) { + gDPPipeSync(gDisplayListHead++); + gSPSetGeometryMode(gDisplayListHead++, G_ZBUFFER); + } + + for (i = 0; i < GFX_NUM_MASTER_LISTS; i++) { + if ((currList = node->listHeads[i]) != NULL) { + gDPSetRenderMode(gDisplayListHead++, modeList->modes[i], mode2List->modes[i]); + while (currList != NULL) { + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(currList->transform), + G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); + gSPDisplayList(gDisplayListHead++, currList->displayList); + currList = currList->next; + } + } + } + if (enableZBuffer != 0) { + gDPPipeSync(gDisplayListHead++); + gSPClearGeometryMode(gDisplayListHead++, G_ZBUFFER); + } +} + +/** + * Appends the display list to one of the master lists based on the layer + * parameter. Look at the RenderModeContainer struct to see the corresponding + * render modes of layers. + */ +static void geo_append_display_list(void *displayList, s16 layer) { + +#ifdef F3DEX_GBI_2 + gSPLookAt(gDisplayListHead++, &lookAt); +#endif + if (gCurGraphNodeMasterList != 0) { + struct DisplayListNode *listNode = + alloc_only_pool_alloc(gDisplayListHeap, sizeof(struct DisplayListNode)); + + listNode->transform = gMatStackFixed[gMatStackIndex]; + listNode->displayList = displayList; + listNode->next = 0; + if (gCurGraphNodeMasterList->listHeads[layer] == 0) { + gCurGraphNodeMasterList->listHeads[layer] = listNode; + } else { + gCurGraphNodeMasterList->listTails[layer]->next = listNode; + } + gCurGraphNodeMasterList->listTails[layer] = listNode; + } +} + +/** + * Process the master list node. + */ +static void geo_process_master_list(struct GraphNodeMasterList *node) { + s32 i; + UNUSED s32 sp1C; + + if (gCurGraphNodeMasterList == NULL && node->node.children != NULL) { + gCurGraphNodeMasterList = node; + for (i = 0; i < GFX_NUM_MASTER_LISTS; i++) { + node->listHeads[i] = NULL; + } + geo_process_node_and_siblings(node->node.children); + geo_process_master_list_sub(node); + gCurGraphNodeMasterList = NULL; + } +} + +/** + * Process an orthographic projection node. + */ +static void geo_process_ortho_projection(struct GraphNodeOrthoProjection *node) { + if (node->node.children != NULL) { + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + f32 left = (gCurGraphNodeRoot->x - gCurGraphNodeRoot->width) / 2.0f * node->scale; + f32 right = (gCurGraphNodeRoot->x + gCurGraphNodeRoot->width) / 2.0f * node->scale; + f32 top = (gCurGraphNodeRoot->y - gCurGraphNodeRoot->height) / 2.0f * node->scale; + f32 bottom = (gCurGraphNodeRoot->y + gCurGraphNodeRoot->height) / 2.0f * node->scale; + + guOrtho(mtx, left, right, bottom, top, -2.0f, 2.0f, 1.0f); + gSPPerspNormalize(gDisplayListHead++, 0xFFFF); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + + geo_process_node_and_siblings(node->node.children); + } +} + +/** + * Process a perspective projection node. + */ +static void geo_process_perspective(struct GraphNodePerspective *node) { + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); + } + if (node->fnNode.node.children != NULL) { + u16 perspNorm; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + +#ifdef VERSION_EU + f32 aspect = ((f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height) * 1.1f; +#else + f32 aspect = (f32) gCurGraphNodeRoot->width / (f32) gCurGraphNodeRoot->height; +#endif + + guPerspective(mtx, &perspNorm, node->fov, aspect, node->near / WORLD_SCALE, node->far / WORLD_SCALE, 1.0f); + gSPPerspNormalize(gDisplayListHead++, perspNorm); + + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + + gCurGraphNodeCamFrustum = node; + geo_process_node_and_siblings(node->fnNode.node.children); + gCurGraphNodeCamFrustum = NULL; + } +} + +/** + * Process a level of detail node. From the current transformation matrix, + * the perpendicular distance to the camera is extracted and the children + * of this node are only processed if that distance is within the render + * range of this node. + */ +static void geo_process_level_of_detail(struct GraphNodeLevelOfDetail *node) { + f32 distanceFromCam; + if (gIsConsole) { + distanceFromCam = -gMatStack[gMatStackIndex][3][2]; + } else { + distanceFromCam = 50; + } + + if ((f32)node->minDistance <= distanceFromCam && distanceFromCam < (f32)node->maxDistance) { + if (node->node.children != 0) { + geo_process_node_and_siblings(node->node.children); + } + } +} + +/** + * Process a switch case node. The node's selection function is called + * if it is 0, and among the node's children, only the selected child is + * processed next. + */ +static void geo_process_switch(struct GraphNodeSwitchCase *node) { + struct GraphNode *selectedChild = node->fnNode.node.children; + s32 i; + + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); + } + for (i = 0; selectedChild != NULL && node->selectedCase > i; i++) { + selectedChild = selectedChild->next; + } + if (selectedChild != NULL) { + geo_process_node_and_siblings(selectedChild); + } +} + +static void make_roll_matrix(Mtx *mtx, s16 angle) { + Mat4 temp; + + mtxf_identity(temp); + temp[0][0] = coss(angle); + temp[0][1] = sins(angle); + temp[1][0] = -temp[0][1]; + temp[1][1] = temp[0][0]; + guMtxF2L(temp, mtx); +} + +/** + * Process a camera node. + */ +static void geo_process_camera(struct GraphNodeCamera *node) { + Mat4 cameraTransform; + Mtx *rollMtx = alloc_display_list(sizeof(*rollMtx)); + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); + } + make_roll_matrix(rollMtx, node->rollScreen); + + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(rollMtx), G_MTX_PROJECTION | G_MTX_MUL | G_MTX_NOPUSH); + + mtxf_lookat(cameraTransform, node->pos, node->focus, node->roll); + mtxf_mul(gMatStack[gMatStackIndex + 1], cameraTransform, gMatStack[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + if (node->fnNode.node.children != 0) { + gCurGraphNodeCamera = node; + node->matrixPtr = &gMatStack[gMatStackIndex]; + geo_process_node_and_siblings(node->fnNode.node.children); + gCurGraphNodeCamera = NULL; + } + gMatStackIndex--; +} + +/** + * Process a translation / rotation node. A transformation matrix based + * on the node's translation and rotation is created and pushed on both + * the float and fixed point matrix stacks. + * For the rest it acts as a normal display list node. + */ +static void geo_process_translation_rotation(struct GraphNodeTranslationRotation *node) { + Mat4 mtxf; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + vec3s_to_vec3f(translation, node->translation); + mtxf_rotate_zxy_and_translate(mtxf, translation, node->rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } + gMatStackIndex--; +} + +/** + * Process a translation node. A transformation matrix based on the node's + * translation is created and pushed on both the float and fixed point matrix stacks. + * For the rest it acts as a normal display list node. + */ +static void geo_process_translation(struct GraphNodeTranslation *node) { + Mat4 mtxf; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + vec3s_to_vec3f(translation, node->translation); + mtxf_rotate_zxy_and_translate(mtxf, translation, gVec3sZero); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } + gMatStackIndex--; +} + +/** + * Process a rotation node. A transformation matrix based on the node's + * rotation is created and pushed on both the float and fixed point matrix stacks. + * For the rest it acts as a normal display list node. + */ +static void geo_process_rotation(struct GraphNodeRotation *node) { + Mat4 mtxf; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, node->rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } + gMatStackIndex--; +} + +/** + * Process a scaling node. A transformation matrix based on the node's + * scale is created and pushed on both the float and fixed point matrix stacks. + * For the rest it acts as a normal display list node. + */ +static void geo_process_scale(struct GraphNodeScale *node) { + UNUSED Mat4 transform; + Vec3f scaleVec; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + vec3f_set(scaleVec, node->scale, node->scale, node->scale); + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], scaleVec); + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } + gMatStackIndex--; +} + +/** + * Process a billboard node. A transformation matrix is created that makes its + * children face the camera, and it is pushed on the floating point and fixed + * point matrix stacks. + * For the rest it acts as a normal display list node. + */ +static void geo_process_billboard(struct GraphNodeBillboard *node) { + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + gMatStackIndex++; + vec3s_to_vec3f(translation, node->translation); + mtxf_billboard(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex - 1], translation, + gCurGraphNodeCamera->roll); + if (gCurGraphNodeHeldObject != NULL) { + mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], + gCurGraphNodeHeldObject->objNode->header.gfx.scale); + } else if (gCurGraphNodeObject != NULL) { + mtxf_scale_vec3f(gMatStack[gMatStackIndex], gMatStack[gMatStackIndex], + gCurGraphNodeObject->scale); + } + + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } + gMatStackIndex--; +} + +/** + * Process a display list node. It draws a display list without first pushing + * a transformation on the stack, so all transformations are inherited from the + * parent node. It processes its children if it has them. + */ +static void geo_process_display_list(struct GraphNodeDisplayList *node) { + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } +} + +/** + * Process a generated list. Instead of storing a pointer to a display list, + * the list is generated on the fly by a function. + */ +static void geo_process_generated_list(struct GraphNodeGenerated *node) { + if (node->fnNode.func != NULL) { + Gfx *list = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, + (struct AllocOnlyPool *) gMatStack[gMatStackIndex]); + + if (list != NULL) { + geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(list), node->fnNode.node.flags >> 8); + } + } + if (node->fnNode.node.children != NULL) { + geo_process_node_and_siblings(node->fnNode.node.children); + } +} + +/** + * Process a background node. Tries to retrieve a background display list from + * the function of the node. If that function is null or returns null, a black + * rectangle is drawn instead. + */ +static void geo_process_background(struct GraphNodeBackground *node) { + Gfx *list = NULL; + + if (node->fnNode.func != NULL) { + list = node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, + (struct AllocOnlyPool *) gMatStack[gMatStackIndex]); + } + if (list != NULL) { + geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(list), node->fnNode.node.flags >> 8); + } else if (gCurGraphNodeMasterList != NULL) { +#ifndef F3DEX_GBI_2E + Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 7); +#else + Gfx *gfxStart = alloc_display_list(sizeof(Gfx) * 8); +#endif + Gfx *gfx = gfxStart; + + gDPPipeSync(gfx++); + gDPSetCycleType(gfx++, G_CYC_FILL); + gDPSetFillColor(gfx++, node->background); + gDPFillRectangle(gfx++, GFX_DIMENSIONS_RECT_FROM_LEFT_EDGE(0), BORDER_HEIGHT, + GFX_DIMENSIONS_RECT_FROM_RIGHT_EDGE(0) - 1, SCREEN_HEIGHT - BORDER_HEIGHT - 1); + gDPPipeSync(gfx++); + gDPSetCycleType(gfx++, G_CYC_1CYCLE); + gSPEndDisplayList(gfx++); + + geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(gfxStart), 0); + } + if (node->fnNode.node.children != NULL) { + geo_process_node_and_siblings(node->fnNode.node.children); + } +} + +/** + * Render an animated part. The current animation state is not part of the node + * but set in global variables. If an animated part is skipped, everything afterwards desyncs. + */ +static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { + Mat4 matrix; + Vec3s rotation; + Vec3f translation; + Mtx *matrixPtr = alloc_display_list(sizeof(*matrixPtr)); + + vec3s_copy(rotation, gVec3sZero); + vec3f_set(translation, node->translation[0], node->translation[1], node->translation[2]); + if (gCurAnimType == ANIM_TYPE_TRANSLATION) { + translation[0] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] + * gCurAnimTranslationMultiplier; + translation[1] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] + * gCurAnimTranslationMultiplier; + translation[2] += gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] + * gCurAnimTranslationMultiplier; + gCurAnimType = ANIM_TYPE_ROTATION; + } else { + if (gCurAnimType == ANIM_TYPE_LATERAL_TRANSLATION) { + translation[0] += + gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] + * gCurAnimTranslationMultiplier; + gCurrAnimAttribute += 2; + translation[2] += + gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] + * gCurAnimTranslationMultiplier; + gCurAnimType = ANIM_TYPE_ROTATION; + } else { + if (gCurAnimType == ANIM_TYPE_VERTICAL_TRANSLATION) { + gCurrAnimAttribute += 2; + translation[1] += + gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] + * gCurAnimTranslationMultiplier; + gCurrAnimAttribute += 2; + gCurAnimType = ANIM_TYPE_ROTATION; + } else if (gCurAnimType == ANIM_TYPE_NO_TRANSLATION) { + gCurrAnimAttribute += 6; + gCurAnimType = ANIM_TYPE_ROTATION; + } + } + } + + if (gCurAnimType == ANIM_TYPE_ROTATION) { + rotation[0] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; + rotation[1] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; + rotation[2] = gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)]; + } + mtxf_rotate_xyz_and_translate(matrix, translation, rotation); + mtxf_mul(gMatStack[gMatStackIndex + 1], matrix, gMatStack[gMatStackIndex]); + gMatStackIndex++; + mtxf_to_mtx(matrixPtr, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = matrixPtr; + if (node->displayList != NULL) { + geo_append_display_list(node->displayList, node->node.flags >> 8); + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } + gMatStackIndex--; +} + +/** + * Initialize the animation-related global variables for the currently drawn + * object's animation. + */ +void geo_set_animation_globals(struct AnimInfo *node, s32 hasAnimation) { + struct Animation *anim = node->curAnim; + + if (hasAnimation) { + node->animFrame = geo_update_animation_frame(node, &node->animFrameAccelAssist); + } + node->animTimer = gAreaUpdateCounter; + if (anim->flags & ANIM_FLAG_HOR_TRANS) { + gCurAnimType = ANIM_TYPE_VERTICAL_TRANSLATION; + } else if (anim->flags & ANIM_FLAG_VERT_TRANS) { + gCurAnimType = ANIM_TYPE_LATERAL_TRANSLATION; + } else if (anim->flags & ANIM_FLAG_6) { + gCurAnimType = ANIM_TYPE_NO_TRANSLATION; + } else { + gCurAnimType = ANIM_TYPE_TRANSLATION; + } + + gCurrAnimFrame = node->animFrame; + gCurAnimEnabled = (anim->flags & ANIM_FLAG_5) == 0; + gCurrAnimAttribute = segmented_to_virtual((void *) anim->index); + gCurAnimData = segmented_to_virtual((void *) anim->values); + + if (anim->animYTransDivisor == 0) { + gCurAnimTranslationMultiplier = 1.0f; + } else { + gCurAnimTranslationMultiplier = (f32) node->animYTrans / (f32) anim->animYTransDivisor; + } +} + +/** + * Process a shadow node. Renders a shadow under an object offset by the + * translation of the first animated component and rotated according to + * the floor below it. + */ +static void geo_process_shadow(struct GraphNodeShadow *node) { + Gfx *shadowList; + Mat4 mtxf; + Vec3f shadowPos; + Vec3f animOffset; + f32 objScale; + f32 shadowScale; + f32 sinAng; + f32 cosAng; + struct GraphNode *geo; + Mtx *mtx; + + if (gCurGraphNodeCamera != NULL && gCurGraphNodeObject != NULL) { + if (gCurGraphNodeHeldObject != NULL) { + get_pos_from_transform_mtx(shadowPos, gMatStack[gMatStackIndex], + *gCurGraphNodeCamera->matrixPtr); + shadowScale = node->shadowScale; + } else { + vec3f_copy(shadowPos, gCurGraphNodeObject->pos); + shadowScale = node->shadowScale * gCurGraphNodeObject->scale[0]; + } + + objScale = 1.0f; + if (gCurAnimEnabled) { + if (gCurAnimType == ANIM_TYPE_TRANSLATION + || gCurAnimType == ANIM_TYPE_LATERAL_TRANSLATION) { + geo = node->node.children; + if (geo != NULL && geo->type == GRAPH_NODE_TYPE_SCALE) { + objScale = ((struct GraphNodeScale *) geo)->scale; + } + animOffset[0] = + gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] + * gCurAnimTranslationMultiplier * objScale; + animOffset[1] = 0.0f; + gCurrAnimAttribute += 2; + animOffset[2] = + gCurAnimData[retrieve_animation_index(gCurrAnimFrame, &gCurrAnimAttribute)] + * gCurAnimTranslationMultiplier * objScale; + gCurrAnimAttribute -= 6; + + // simple matrix rotation so the shadow offset rotates along with the object + sinAng = sins(gCurGraphNodeObject->angle[1]); + cosAng = coss(gCurGraphNodeObject->angle[1]); + + shadowPos[0] += animOffset[0] * cosAng + animOffset[2] * sinAng; + shadowPos[2] += -animOffset[0] * sinAng + animOffset[2] * cosAng; + } + } + + shadowList = create_shadow_below_xyz(shadowPos[0], shadowPos[1], shadowPos[2], shadowScale, + node->shadowSolidity, node->shadowType); + if (shadowList != NULL) { + mtx = alloc_display_list(sizeof(*mtx)); + gMatStackIndex++; + mtxf_translate(mtxf, shadowPos); + mtxf_mul(gMatStack[gMatStackIndex], mtxf, *gCurGraphNodeCamera->matrixPtr); + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + if (gShadowAboveWaterOrLava == TRUE) { + geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 4); + } else if (gMarioOnIceOrCarpet == 1 || gShadowAboveCustomWater == 1) { + geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 5); + } else { + geo_append_display_list((void *) VIRTUAL_TO_PHYSICAL(shadowList), 6); + } + gMatStackIndex--; + } + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } +} + +/** + * Check whether an object is in view to determine whether it should be drawn. + * This is known as frustum culling. + * It checks whether the object is far away, very close / behind the camera, + * or horizontally out of view. It does not check whether it is vertically + * out of view. It assumes a sphere of 300 units around the object's position + * unless the object has a culling radius node that specifies otherwise. + * + * The matrix parameter should be the top of the matrix stack, which is the + * object's transformation matrix times the camera 'look-at' matrix. The math + * is counter-intuitive, but it checks column 3 (translation vector) of this + * matrix to determine where the origin (0,0,0) in object space will be once + * transformed to camera space (x+ = right, y+ = up, z = 'coming out the screen'). + * In 3D graphics, you typically model the world as being moved in front of a + * static camera instead of a moving camera through a static world, which in + * this case simplifies calculations. Note that the perspective matrix is not + * on the matrix stack, so there are still calculations with the fov to compute + * the slope of the lines of the frustum. + * + * z- + * + * \ | / + * \ | / + * \ | / + * \ | / + * \ | / + * \|/ + * C x+ + * + * Since (0,0,0) is unaffected by rotation, columns 0, 1 and 2 are ignored. + */ +static s32 obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { + s16 cullingRadius; + s16 halfFov; // half of the fov in in-game angle units instead of degrees + struct GraphNode *geo; + f32 hScreenEdge; + + if (node->node.flags & GRAPH_RENDER_INVISIBLE) { + return FALSE; + } + + geo = node->sharedChild; + + // ! @bug The aspect ratio is not accounted for. When the fov value is 45, + // the horizontal effective fov is actually 60 degrees, so you can see objects + // visibly pop in or out at the edge of the screen. + halfFov = (gCurGraphNodeCamFrustum->fov / 2.0f + 1.0f) * 32768.0f / 180.0f + 0.5f; + + hScreenEdge = -matrix[3][2] * sins(halfFov) / coss(halfFov); + // -matrix[3][2] is the depth, which gets multiplied by tan(halfFov) to get + // the amount of units between the center of the screen and the horizontal edge + // given the distance from the object to the camera. + +#ifdef WIDESCREEN + // This multiplication should really be performed on 4:3 as well, + // but the issue will be more apparent on widescreen. + hScreenEdge *= GFX_DIMENSIONS_ASPECT_RATIO; +#endif + + if (geo != NULL && geo->type == GRAPH_NODE_TYPE_CULLING_RADIUS) { + cullingRadius = + (f32)((struct GraphNodeCullingRadius *) geo)->cullingRadius; //! Why is there a f32 cast? + } else { + cullingRadius = 300; + } + + // Don't render if the object is close to or behind the camera + if (matrix[3][2] > -100.0f + cullingRadius) { + return FALSE; + } + + //! This makes the HOLP not update when the camera is far away, and it + // makes PU travel safe when the camera is locked on the main map. + // If Mario were rendered with a depth over 65536 it would cause overflow + // when converting the transformation matrix to a fixed point matrix. + if (matrix[3][2] < -20000.0f - cullingRadius) { + return FALSE; + } + + // Check whether the object is horizontally in view + if (matrix[3][0] > hScreenEdge + cullingRadius) { + return FALSE; + } + if (matrix[3][0] < -hScreenEdge - cullingRadius) { + return FALSE; + } + return TRUE; +} + +/** + * Process an object node. + */ +static void geo_process_object(struct Object *node) { + Mat4 mtxf; + s32 hasAnimation = (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0; + + if (node->header.gfx.areaIndex == gCurGraphNodeRoot->areaIndex) { + if (node->header.gfx.throwMatrix != NULL) { + mtxf_mul(gMatStack[gMatStackIndex + 1], *node->header.gfx.throwMatrix, + gMatStack[gMatStackIndex]); + } else if (node->header.gfx.node.flags & GRAPH_RENDER_BILLBOARD) { + mtxf_billboard(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], + node->header.gfx.pos, gCurGraphNodeCamera->roll); + } else { + mtxf_rotate_zxy_and_translate(mtxf, node->header.gfx.pos, node->header.gfx.angle); + mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]); + } + + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], + node->header.gfx.scale); + node->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex]; + node->header.gfx.cameraToObject[0] = gMatStack[gMatStackIndex][3][0]; + node->header.gfx.cameraToObject[1] = gMatStack[gMatStackIndex][3][1]; + node->header.gfx.cameraToObject[2] = gMatStack[gMatStackIndex][3][2]; + + // FIXME: correct types + if (node->header.gfx.animInfo.curAnim != NULL) { + geo_set_animation_globals(&node->header.gfx.animInfo, hasAnimation); + } + if (obj_is_in_view(&node->header.gfx, gMatStack[gMatStackIndex])) { + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + if (node->header.gfx.sharedChild != NULL) { + gCurGraphNodeObject = (struct GraphNodeObject *) node; + node->header.gfx.sharedChild->parent = &node->header.gfx.node; + geo_process_node_and_siblings(node->header.gfx.sharedChild); + node->header.gfx.sharedChild->parent = NULL; + gCurGraphNodeObject = NULL; + } + if (node->header.gfx.node.children != NULL) { + geo_process_node_and_siblings(node->header.gfx.node.children); + } + } + + gMatStackIndex--; + gCurAnimType = ANIM_TYPE_NONE; + node->header.gfx.throwMatrix = NULL; + } +} + +/** + * Process an object parent node. Temporarily assigns itself as the parent of + * the subtree rooted at 'sharedChild' and processes the subtree, after which the + * actual children are be processed. (in practice they are null though) + */ +static void geo_process_object_parent(struct GraphNodeObjectParent *node) { + if (node->sharedChild != NULL) { + node->sharedChild->parent = (struct GraphNode *) node; + geo_process_node_and_siblings(node->sharedChild); + node->sharedChild->parent = NULL; + } + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } +} + +/** + * Process a held object node. + */ +void geo_process_held_object(struct GraphNodeHeldObject *node) { + Mat4 mat; + Vec3f translation; + Mtx *mtx = alloc_display_list(sizeof(*mtx)); + +#ifdef F3DEX_GBI_2 + gSPLookAt(gDisplayListHead++, &lookAt); +#endif + + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_RENDER, &node->fnNode.node, gMatStack[gMatStackIndex]); + } + if (node->objNode != NULL && node->objNode->header.gfx.sharedChild != NULL) { + s32 hasAnimation = (node->objNode->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0; + + translation[0] = node->translation[0] / 4.0f; + translation[1] = node->translation[1] / 4.0f; + translation[2] = node->translation[2] / 4.0f; + + mtxf_translate(mat, translation); + mtxf_copy(gMatStack[gMatStackIndex + 1], *gCurGraphNodeObject->throwMatrix); + gMatStack[gMatStackIndex + 1][3][0] = gMatStack[gMatStackIndex][3][0]; + gMatStack[gMatStackIndex + 1][3][1] = gMatStack[gMatStackIndex][3][1]; + gMatStack[gMatStackIndex + 1][3][2] = gMatStack[gMatStackIndex][3][2]; + mtxf_mul(gMatStack[gMatStackIndex + 1], mat, gMatStack[gMatStackIndex + 1]); + mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], + node->objNode->header.gfx.scale); + if (node->fnNode.func != NULL) { + node->fnNode.func(GEO_CONTEXT_HELD_OBJ, &node->fnNode.node, + (struct AllocOnlyPool *) gMatStack[gMatStackIndex + 1]); + } + gMatStackIndex++; + mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = mtx; + gGeoTempState.type = gCurAnimType; + gGeoTempState.enabled = gCurAnimEnabled; + gGeoTempState.frame = gCurrAnimFrame; + gGeoTempState.translationMultiplier = gCurAnimTranslationMultiplier; + gGeoTempState.attribute = gCurrAnimAttribute; + gGeoTempState.data = gCurAnimData; + gCurAnimType = 0; + gCurGraphNodeHeldObject = (void *) node; + if (node->objNode->header.gfx.animInfo.curAnim != NULL) { + geo_set_animation_globals(&node->objNode->header.gfx.animInfo, hasAnimation); + } + + geo_process_node_and_siblings(node->objNode->header.gfx.sharedChild); + gCurGraphNodeHeldObject = NULL; + gCurAnimType = gGeoTempState.type; + gCurAnimEnabled = gGeoTempState.enabled; + gCurrAnimFrame = gGeoTempState.frame; + gCurAnimTranslationMultiplier = gGeoTempState.translationMultiplier; + gCurrAnimAttribute = gGeoTempState.attribute; + gCurAnimData = gGeoTempState.data; + gMatStackIndex--; + } + + if (node->fnNode.node.children != NULL) { + geo_process_node_and_siblings(node->fnNode.node.children); + } +} + +/** + * Processes the children of the given GraphNode if it has any + */ +void geo_try_process_children(struct GraphNode *node) { + if (node->children != NULL) { + geo_process_node_and_siblings(node->children); + } +} + +/** + * Process a generic geo node and its siblings. + * The first argument is the start node, and all its siblings will + * be iterated over. + */ +void geo_process_node_and_siblings(struct GraphNode *firstNode) { + s16 iterateChildren = TRUE; + struct GraphNode *curGraphNode = firstNode; + struct GraphNode *parent = curGraphNode->parent; + + // In the case of a switch node, exactly one of the children of the node is + // processed instead of all children like usual + if (parent != NULL) { + iterateChildren = (parent->type != GRAPH_NODE_TYPE_SWITCH_CASE); + } + + do { + if (curGraphNode->flags & GRAPH_RENDER_ACTIVE) { + if (curGraphNode->flags & GRAPH_RENDER_CHILDREN_FIRST) { + geo_try_process_children(curGraphNode); + } else { + switch (curGraphNode->type) { + case GRAPH_NODE_TYPE_ORTHO_PROJECTION: + geo_process_ortho_projection((struct GraphNodeOrthoProjection *) curGraphNode); + break; + case GRAPH_NODE_TYPE_PERSPECTIVE: + geo_process_perspective((struct GraphNodePerspective *) curGraphNode); + break; + case GRAPH_NODE_TYPE_MASTER_LIST: + geo_process_master_list((struct GraphNodeMasterList *) curGraphNode); + break; + case GRAPH_NODE_TYPE_LEVEL_OF_DETAIL: + geo_process_level_of_detail((struct GraphNodeLevelOfDetail *) curGraphNode); + break; + case GRAPH_NODE_TYPE_SWITCH_CASE: + geo_process_switch((struct GraphNodeSwitchCase *) curGraphNode); + break; + case GRAPH_NODE_TYPE_CAMERA: + geo_process_camera((struct GraphNodeCamera *) curGraphNode); + break; + case GRAPH_NODE_TYPE_TRANSLATION_ROTATION: + geo_process_translation_rotation( + (struct GraphNodeTranslationRotation *) curGraphNode); + break; + case GRAPH_NODE_TYPE_TRANSLATION: + geo_process_translation((struct GraphNodeTranslation *) curGraphNode); + break; + case GRAPH_NODE_TYPE_ROTATION: + geo_process_rotation((struct GraphNodeRotation *) curGraphNode); + break; + case GRAPH_NODE_TYPE_OBJECT: + geo_process_object((struct Object *) curGraphNode); + break; + case GRAPH_NODE_TYPE_ANIMATED_PART: + geo_process_animated_part((struct GraphNodeAnimatedPart *) curGraphNode); + break; + case GRAPH_NODE_TYPE_BILLBOARD: + geo_process_billboard((struct GraphNodeBillboard *) curGraphNode); + break; + case GRAPH_NODE_TYPE_DISPLAY_LIST: + geo_process_display_list((struct GraphNodeDisplayList *) curGraphNode); + break; + case GRAPH_NODE_TYPE_SCALE: + geo_process_scale((struct GraphNodeScale *) curGraphNode); + break; + case GRAPH_NODE_TYPE_SHADOW: + geo_process_shadow((struct GraphNodeShadow *) curGraphNode); + break; + case GRAPH_NODE_TYPE_OBJECT_PARENT: + geo_process_object_parent((struct GraphNodeObjectParent *) curGraphNode); + break; + case GRAPH_NODE_TYPE_GENERATED_LIST: + geo_process_generated_list((struct GraphNodeGenerated *) curGraphNode); + break; + case GRAPH_NODE_TYPE_BACKGROUND: + geo_process_background((struct GraphNodeBackground *) curGraphNode); + break; + case GRAPH_NODE_TYPE_HELD_OBJ: + geo_process_held_object((struct GraphNodeHeldObject *) curGraphNode); + break; + default: + geo_try_process_children((struct GraphNode *) curGraphNode); + break; + } + } + } else { + if (curGraphNode->type == GRAPH_NODE_TYPE_OBJECT) { + ((struct GraphNodeObject *) curGraphNode)->throwMatrix = NULL; + } + } + } while (iterateChildren && (curGraphNode = curGraphNode->next) != firstNode); +} + +/** + * Process a root node. This is the entry point for processing the scene graph. + * The root node itself sets up the viewport, then all its children are processed + * to set up the projection and draw display lists. + */ +void geo_process_root(struct GraphNodeRoot *node, Vp *b, Vp *c, s32 clearColor) { + UNUSED s32 unused; + + if (node->node.flags & GRAPH_RENDER_ACTIVE) { + Mtx *initialMatrix; + Vp *viewport = alloc_display_list(sizeof(*viewport)); + + gDisplayListHeap = alloc_only_pool_init(main_pool_available() - sizeof(struct AllocOnlyPool), + MEMORY_POOL_LEFT); + initialMatrix = alloc_display_list(sizeof(*initialMatrix)); + gMatStackIndex = 0; + gCurAnimType = 0; + vec3s_set(viewport->vp.vtrans, node->x * 4, node->y * 4, 511); + vec3s_set(viewport->vp.vscale, node->width * 4, node->height * 4, 511); + if (b != NULL) { + clear_frame_buffer(clearColor); + make_viewport_clip_rect(b); + *viewport = *b; + } + + else if (c != NULL) { + clear_frame_buffer(clearColor); + make_viewport_clip_rect(c); + } + + mtxf_identity(gMatStack[gMatStackIndex]); + mtxf_to_mtx(initialMatrix, gMatStack[gMatStackIndex]); + gMatStackFixed[gMatStackIndex] = initialMatrix; + gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(viewport)); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(gMatStackFixed[gMatStackIndex]), + G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH); + gCurGraphNodeRoot = node; + if (node->node.children != NULL) { + geo_process_node_and_siblings(node->node.children); + } + gCurGraphNodeRoot = NULL; + if (gShowDebugText) { + print_text_fmt_int(180, 36, "MEM %d", + gDisplayListHeap->totalSpace - gDisplayListHeap->usedSpace); + } + main_pool_free(gDisplayListHeap); + } +} diff --git a/src/menu/file_select.c b/src/menu/file_select.c index 9437dcc6..fceab623 100644 --- a/src/menu/file_select.c +++ b/src/menu/file_select.c @@ -2854,10 +2854,12 @@ Gfx *geo_file_select_strings_and_menu_cursor(s32 callContext, UNUSED struct Grap * Relocates cursor position of the last save if the game goes back to the Mario Screen * either completing a course choosing "SAVE & QUIT" or having a game over. */ +extern u8 widescreen; s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused) { #ifdef VERSION_EU s8 fileNum; #endif + widescreen = 0; sSelectedButtonID = MENU_BUTTON_NONE; sCurrentMenuLevel = MENU_LAYER_MAIN; sTextBaseAlpha = 0; diff --git a/src/menu/star_select.c b/src/menu/star_select.c index 07610faa..ec4d1b05 100644 --- a/src/menu/star_select.c +++ b/src/menu/star_select.c @@ -52,7 +52,7 @@ static s8 sSelectableStarIndex = 0; // Act Selector menu timer that keeps counting until you choose an act. static s32 sActSelectorMenuTimer = 0; - +extern u8 widescreen; /** * Act Selector Star Type Loop Action * Defines a select type for a star in the act selector. @@ -92,8 +92,15 @@ void bhv_act_selector_star_type_loop(void) { 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); + if (widescreen == 1){ + sStarSelectorModels[6] = spawn_object_abs_with_rot(gCurrentObject, 0, MODEL_STAR, + bhvActSelectorStarType, ((370*4.0f)/3), 24, -300, 0, 0, 0); + } + else{ + 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; } @@ -147,11 +154,21 @@ void bhv_act_selector_init(void) { } // 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; + if (widescreen == 1) { + for (i = 0; i < sVisibleStars; i++) { + sStarSelectorModels[i] = + spawn_object_abs_with_rot(gCurrentObject, 0, selectorModelIDs[i], bhvActSelectorStarType, + (((75 + sVisibleStars * -75 + i * 152)*4.0f)/3), 248, -300, 0, 0, 0); + sStarSelectorModels[i]->oStarSelectorSize = 1.0f; + } + } + else { + 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);