From 5abd0ca1ab470dce614e88da43a64bb1c3d17009 Mon Sep 17 00:00:00 2001 From: MysterD Date: Sun, 13 Sep 2020 23:33:39 -0700 Subject: [PATCH] Complete rewrite of custom main menu The vanilla main menu is an awful pile of code that is extremely painful to make additions to. So I've side-stepped the problem by writing an entiely new menu system without all of vanilla's quirks. I've separated the custom menu system from the menus I want to make, which allows for easy additions and a clear separation of responsibilities. Along with this rewrite comes a host menu, adding the missing configuration options in-game. Fixes #29 --- bin/segment2.c | 9 +- build-windows-visual-studio/sm64ex.vcxproj | 4 + .../sm64ex.vcxproj.filters | 12 + include/object_fields.h | 1 + src/menu/custom_menu.c | 250 ++++++++++++ src/menu/custom_menu.h | 13 + src/menu/custom_menu_system.c | 323 +++++++++++++++ src/menu/custom_menu_system.h | 40 ++ src/menu/file_select.c | 385 ++---------------- src/menu/file_select.h | 16 +- src/pc/configfile.c | 8 +- src/pc/configfile.h | 1 + src/pc/controller/controller_keyboard.c | 9 +- src/pc/controller/controller_keyboard.h | 1 + src/pc/network/discord/activity.c | 4 +- src/pc/network/packets/packet_save_file.c | 6 +- src/pc/network/socket/socket.c | 5 +- textures/segment2/custom_hud_j.rgba16.png | Bin 0 -> 3185 bytes 18 files changed, 719 insertions(+), 368 deletions(-) create mode 100644 src/menu/custom_menu.c create mode 100644 src/menu/custom_menu.h create mode 100644 src/menu/custom_menu_system.c create mode 100644 src/menu/custom_menu_system.h create mode 100644 textures/segment2/custom_hud_j.rgba16.png diff --git a/bin/segment2.c b/bin/segment2.c index 8cf462ad..652bffa9 100644 --- a/bin/segment2.c +++ b/bin/segment2.c @@ -82,6 +82,11 @@ ALIGNED8 static const u8 texture_hud_char_I[] = { #include "textures/segment2/segment2.02400.rgba16.inc.c" }; +ALIGNED8 static const u8 texture_hud_char_J[] = { +#include "textures/segment2/custom_hud_j.rgba16.inc.c" +}; + + #if defined(VERSION_JP) || defined(VERSION_SH) ALIGNED8 static const u8 texture_hud_char_J[] = { #include "textures/segment2/segment2.02600.rgba16.inc.c" @@ -1811,7 +1816,7 @@ const u8 *const main_hud_lut[] = { texture_hud_char_4, texture_hud_char_5, texture_hud_char_6, texture_hud_char_7, texture_hud_char_8, texture_hud_char_9, texture_hud_char_A, texture_hud_char_B, texture_hud_char_C, texture_hud_char_D, texture_hud_char_E, texture_hud_char_F, - texture_hud_char_G, texture_hud_char_H, texture_hud_char_I, 0x0, + texture_hud_char_G, texture_hud_char_H, texture_hud_char_I, texture_hud_char_J, texture_hud_char_K, texture_hud_char_L, texture_hud_char_M, texture_hud_char_N, texture_hud_char_O, texture_hud_char_P, 0x0, texture_hud_char_R, texture_hud_char_S, texture_hud_char_T, texture_hud_char_U, texture_hud_char_V, @@ -1827,7 +1832,7 @@ const u8 *const main_hud_lut[] = { texture_hud_char_4, texture_hud_char_5, texture_hud_char_6, texture_hud_char_7, texture_hud_char_8, texture_hud_char_9, texture_hud_char_A, texture_hud_char_B, texture_hud_char_C, texture_hud_char_D, texture_hud_char_E, texture_hud_char_F, - texture_hud_char_G, texture_hud_char_H, texture_hud_char_I, 0x0, + texture_hud_char_G, texture_hud_char_H, texture_hud_char_I, texture_hud_char_J, texture_hud_char_K, texture_hud_char_L, texture_hud_char_M, texture_hud_char_N, texture_hud_char_O, texture_hud_char_P, 0x0, texture_hud_char_R, texture_hud_char_S, texture_hud_char_T, texture_hud_char_U, 0x0, diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj index eac557d5..c09f789e 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj +++ b/build-windows-visual-studio/sm64ex.vcxproj @@ -3918,7 +3918,9 @@ + + @@ -4311,6 +4313,8 @@ + + diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters index 5e56b6d8..3345c01c 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj.filters +++ b/build-windows-visual-studio/sm64ex.vcxproj.filters @@ -15036,6 +15036,12 @@ Source Files\src\pc\network\discord + + Source Files\src\menu + + + Source Files\src\menu + @@ -15976,5 +15982,11 @@ Header Files\src\pc\network\discord + + Header Files\src\menu + + + Header Files\src\menu + \ No newline at end of file diff --git a/include/object_fields.h b/include/object_fields.h index 6629ebd1..5a3dac77 100644 --- a/include/object_fields.h +++ b/include/object_fields.h @@ -672,6 +672,7 @@ #define /*0x104*/ oMenuButtonOrigPosZ OBJECT_FIELD_F32(0x1F) #define /*0x108*/ oMenuButtonScale OBJECT_FIELD_F32(0x20) #define /*0x10C*/ oMenuButtonActionPhase OBJECT_FIELD_S32(0x21) +#define /*0x110*/ oMenuButtonIsCustom OBJECT_FIELD_S32(0x22) /* Manta Ray */ #define /*0x0F4*/ oMantaUnkF4 OBJECT_FIELD_S32(0x1B) diff --git a/src/menu/custom_menu.c b/src/menu/custom_menu.c new file mode 100644 index 00000000..c388271e --- /dev/null +++ b/src/menu/custom_menu.c @@ -0,0 +1,250 @@ +#include +#include +#include +#include "custom_menu.h" +#include "custom_menu_system.h" +#include "pc/network/network.h" + +#include "pc/configfile.h" +#include "pc/controller/controller_keyboard.h" +#include "game/object_list_processor.h" +#include "game/object_helpers.h" +#include "game/ingame_menu.h" +#include "game/game_init.h" +#include "game/segment2.h" +#include "object_fields.h" +#include "model_ids.h" +#include "behavior_data.h" + +#define MAIN_MENU_HEADER_TEXT "SM64 COOP" + +char gConnectionJoinError[128] = { 0 }; +char gConnectionText[128] = { 0 }; +struct CustomMenu* sConnectMenu = NULL; +u8 gOpenConnectMenu = FALSE; +s8 sGotoGame = 0; + +static void menu_main_draw_strings(void) { + print_generic_ascii_string(98, 150, "Still in early development."); + u8 red = (gGlobalTimer % 20 > 10) ? 0 : 222; + if (gGlobalTimer > 200) { red = 222; } + gDPSetEnvColor(gDisplayListHead++, 222, red, red, gMenuStringAlpha); + print_generic_ascii_string(50, 55, "Levels after Bowser 1 don't synchronize yet."); +} + +static void host_menu_draw_strings(void) { + // set up server setting strings + char* buttonText[4]; + buttonText[0] = (configNetworkSystem == 0) ? "Host through Discord." : "Host direct connection."; + switch (configPlayerInteraction) { + case 0: buttonText[1] = "Non-solid players."; break; + case 1: buttonText[1] = "Solid players."; break; + case 2: buttonText[1] = "Friendly fire."; break; + default: buttonText[1] = "UNKNOWN"; break; + } + if (configPlayerKnockbackStrength <= 20) { + buttonText[2] = "Weak knockback."; + } else if (configPlayerKnockbackStrength <= 40) { + buttonText[2] = "Normal knockback."; + } else { + buttonText[2] = "Too much knockback."; + } + + buttonText[3] = configStayInLevelAfterStar ? "Stay in level after star." : "Leave level after star."; + + // display server setting strings + for (int i = 0; i < 4; i++) { + print_generic_ascii_string(95, 158 + -35 * i, buttonText[i]); + } + + // display direct connection warning + if (configNetworkSystem != 0) { + print_generic_ascii_string(0, 30, "For direct connections -"); + f32 red = (f32)fabs(sin(gGlobalTimer / 20.0f)); + gDPSetEnvColor(gDisplayListHead++, 222, 222 * red, 222 * red, gMenuStringAlpha); + char warning[128]; + snprintf(warning, 127, "You must forward port '%d' in your router or use Hamachi.", configHostPort); + print_generic_ascii_string(0, 15, warning); + + } +} + +static void host_menu_do_host(void) { + if (configNetworkSystem == 0) { + network_set_system(NS_DISCORD); + } + else { + network_set_system(NS_SOCKET); + } + custom_menu_close_system(); +} + +static void host_menu_setting_network_system(void) { + configNetworkSystem = (configNetworkSystem == 0) ? 1 : 0; +} + +static void host_menu_setting_interaction(void) { + switch (configPlayerInteraction) { + case 0: configPlayerInteraction = 1; break; + case 1: configPlayerInteraction = 2; break; + default: configPlayerInteraction = 0; break; + } +} + +static void host_menu_setting_knockback(void) { + if (configPlayerKnockbackStrength <= 20) { + configPlayerKnockbackStrength = 25; + } else if (configPlayerKnockbackStrength <= 40) { + configPlayerKnockbackStrength = 75; + } else { + configPlayerKnockbackStrength = 10; + } +} + +static void host_menu_setting_stay_in_level(void) { + configStayInLevelAfterStar = (configStayInLevelAfterStar == 0) ? 1 : 0; +} + +static void join_menu_draw_strings(void) { + print_generic_ascii_string(30, 155, "Accept a Discord game invite in order to join."); + print_generic_ascii_string(30, 130, "For direct connections, click connect to type in an IP."); +} + +static void connect_menu_draw_strings(void) { + if (gNetworkType == NT_CLIENT) { + f32 alpha = (f32)fabs(sin(gGlobalTimer / 20.0f)); + gDPSetEnvColor(gDisplayListHead++, 222, 222, 222, gMenuStringAlpha * alpha); + print_generic_ascii_string(130, 130, "Connecting..."); + return; + } + + if (*gConnectionJoinError) { + f32 red = (f32)fabs(sin(gGlobalTimer / 20.0f)); + gDPSetEnvColor(gDisplayListHead++, 222, 222 * red, 222 * red, gMenuStringAlpha); + print_generic_ascii_string(30, 130, gConnectionJoinError); + return; + } + + print_generic_ascii_string(30, 175, "Type in or paste the host's IP."); + print_generic_ascii_string(30, 160, "Note - the host must forward a port on their router."); + + if (keyboard_in_text_input()) { + if (strlen(gTextInput) >= 7) { + print_generic_ascii_string(30, 100, "Press (ENTER) to connect."); + } else { + print_generic_ascii_string(30, 100, "Press (ESC) to cancel."); + } + } + + gDPSetEnvColor(gDisplayListHead++, 130, 222, 140, gMenuStringAlpha); + print_generic_ascii_string(30, 130, gTextInput); +} + +static void connect_menu_on_connection_attempt(void) { + keyboard_stop_text_input(); + if (gNetworkType != NT_NONE) { return; } + + char delims[2] = " "; + + // copy input + char buffer[MAX_TEXT_INPUT] = { 0 }; + strncpy(buffer, gTextInput, MAX_TEXT_INPUT); + char* text = buffer; + + // trim whitespace + while (*text == ' ') { text++; } + + // grab IP + char* ip = strtok(text, delims); + if (ip == NULL) { custom_menu_close(); return; } + strncpy(configJoinIp, ip, MAX_CONFIG_STRING); + + // grab port + char* port = strtok(NULL, delims); + if (port != NULL) { + unsigned int intPort = atoi(port); + if (intPort == 0) { custom_menu_close(); return; } + configJoinPort = intPort; + } + else { + configJoinPort = DEFAULT_PORT; + } + + network_set_system(NS_SOCKET); + network_init(NT_CLIENT); + +} + +static void connect_menu_on_click(void) { + gConnectionJoinError[0] = '\0'; + + keyboard_start_text_input(TIM_IP, custom_menu_close, connect_menu_on_connection_attempt); + + // fill in our last attempt + if (configJoinPort == 0) { configJoinPort = DEFAULT_PORT; } + sprintf(gTextInput, "%s %d", configJoinIp, configJoinPort); + +} + +static void connect_menu_on_close(void) { + keyboard_stop_text_input(); + network_shutdown(); +} + +void custom_menu_init(struct CustomMenu* head) { + + // set the header text + head->me->label = calloc(strlen(MAIN_MENU_HEADER_TEXT) + 1, sizeof(char)); + strcpy(head->me->label, MAIN_MENU_HEADER_TEXT); + + // set main menu settings + head->headerY = 55; + head->draw_strings = menu_main_draw_strings; + + // create sub menus and buttons + struct CustomMenu* hostMenu = custom_menu_create(head, "HOST", -266, 0); + hostMenu->draw_strings = host_menu_draw_strings; + custom_menu_create_button(hostMenu, "CANCEL", 700, -400 + (250 * 3), custom_menu_close); + custom_menu_create_button(hostMenu, "HOST", 700, -400, host_menu_do_host); + custom_menu_create_button(hostMenu, "", -700, -400 + (250 * 3), host_menu_setting_network_system); + custom_menu_create_button(hostMenu, "", -700, -400 + (250 * 2), host_menu_setting_interaction); + custom_menu_create_button(hostMenu, "", -700, -400 + (250 * 1), host_menu_setting_knockback); + custom_menu_create_button(hostMenu, "", -700, -400 + (250 * 0), host_menu_setting_stay_in_level); + + struct CustomMenu* joinMenu = custom_menu_create(head, "JOIN", 266, 0); + custom_menu_create_button(joinMenu, "CANCEL", -266, -320, custom_menu_close); + joinMenu->draw_strings = join_menu_draw_strings; + + struct CustomMenu* connectMenu = custom_menu_create(joinMenu, "CONNECT", 266, -320); + connectMenu->me->on_click = connect_menu_on_click; + connectMenu->on_close = connect_menu_on_close; + connectMenu->draw_strings = connect_menu_draw_strings; + custom_menu_create_button(connectMenu, "CANCEL", 0, -400, custom_menu_close); + sConnectMenu = connectMenu; +} + +void custom_menu_loop(void) { + // we've received an event that makes us exit the menus + if (sGotoGame) { sSelectedFileNum = sGotoGame; } + + // force-start the load when command-line server hosting + if (gNetworkType == NT_SERVER && sSelectedFileNum == 0) { + sSelectedFileNum = 1; + } + + if (gOpenConnectMenu && sConnectMenu != NULL) { + gOpenConnectMenu = FALSE; + custom_menu_open(sConnectMenu); + } +} + +void custom_menu_on_load_save_file(s8 saveFileNum) { + if (gNetworkType != NT_CLIENT && saveFileNum != 0) { + configHostSaveSlot = saveFileNum; + network_init(NT_SERVER); + } +} + +void custom_menu_goto_game(s16 saveFileNum) { + sGotoGame = saveFileNum; +} \ No newline at end of file diff --git a/src/menu/custom_menu.h b/src/menu/custom_menu.h new file mode 100644 index 00000000..04b9e2b6 --- /dev/null +++ b/src/menu/custom_menu.h @@ -0,0 +1,13 @@ +#ifndef CUSTOM_MENU_H +#define CUSTOM_MENU_H +#include "custom_menu_system.h" + +extern char gConnectionJoinError[]; +extern u8 gOpenConnectMenu; + +void custom_menu_init(struct CustomMenu* head); +void custom_menu_loop(void); +void custom_menu_on_load_save_file(s8 saveFileNum); +void custom_menu_goto_game(s16 saveFileNum); + +#endif // CUSTOM_MENU_H diff --git a/src/menu/custom_menu_system.c b/src/menu/custom_menu_system.c new file mode 100644 index 00000000..128e173c --- /dev/null +++ b/src/menu/custom_menu_system.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include "custom_menu_system.h" +#include "custom_menu.h" + +#include "game/object_list_processor.h" +#include "game/object_helpers.h" +#include "game/ingame_menu.h" +#include "game/game_init.h" +#include "game/segment2.h" +#include "object_fields.h" +#include "model_ids.h" +#include "behavior_data.h" +#include "audio_defines.h" +#include "audio/external.h" + +#define MAIN_MENU_HEADER_TEXT "SM64 COOP" + +static struct CustomMenu* sHead = NULL; +static struct CustomMenu* sCurrentMenu = NULL; +static struct CustomMenu* sLastMenu = NULL; +u8 gMenuStringAlpha = 255; + +struct CustomMenuButton* custom_menu_create_button(struct CustomMenu* parent, char* label, u16 x, u16 y, void (*on_click)(void)) { + struct CustomMenuButton* button = calloc(1, sizeof(struct CustomMenuButton)); + if (parent->buttons == NULL) { + parent->buttons = button; + } else { + struct CustomMenuButton* parentButton = parent->buttons; + while (parentButton->next != NULL) { parentButton = parentButton->next; } + parentButton->next = button; + } + button->label = calloc(strlen(label), sizeof(char) + 1); + strcpy(button->label, label); + + button->on_click = on_click; + + struct Object* obj = spawn_object_rel_with_rot(parent->me->object, MODEL_MAIN_MENU_MARIO_NEW_BUTTON, bhvMenuButton, x * -1, y, -1, 0, 0x8000, 0); + obj->oMenuButtonScale = 0.11111111f; + obj->oFaceAngleRoll = 0; + obj->oMenuButtonTimer = 0; + obj->oMenuButtonOrigPosX = obj->oParentRelativePosX; + obj->oMenuButtonOrigPosY = obj->oParentRelativePosY; + obj->oMenuButtonOrigPosZ = obj->oParentRelativePosZ; + obj->oMenuButtonIsCustom = 1; + obj->oMenuButtonState = MENU_BUTTON_STATE_DEFAULT; + button->object = obj; + return button; +} + +struct CustomMenu* custom_menu_create(struct CustomMenu* parent, char* label, u16 x, u16 y) { + struct CustomMenuButton* button = custom_menu_create_button(parent, label, x, y, NULL); + struct CustomMenu* menu = calloc(1, sizeof(struct CustomMenu)); + menu->parent = parent; + menu->depth = parent->depth + 1; + menu->headerY = 25; + menu->me = button; + button->menu = menu; + return menu; +} + +void custom_menu_system_init(void) { + + // allocate the main menu and set it to current + sHead = calloc(1, sizeof(struct CustomMenu)); + sHead->me = calloc(1, sizeof(struct CustomMenuButton)); + sCurrentMenu = sHead; + + // spawn the main menu game object + struct Object* obj = spawn_object_rel_with_rot(gCurrentObject, MODEL_MAIN_MENU_GREEN_SCORE_BUTTON, bhvMenuButton, 0, 0, 0, 0, 0, 0); + obj->oParentRelativePosZ += 1000; + obj->oMenuButtonState = MENU_BUTTON_STATE_FULLSCREEN; + obj->oFaceAngleYaw = 0x8000; + obj->oFaceAngleRoll = 0; + obj->oMenuButtonScale = 9.0f; + obj->oMenuButtonOrigPosZ = obj->oPosZ; + obj->oMenuButtonOrigPosX = 99999; + obj->oMenuButtonIsCustom = 1; + sHead->me->object = obj; + + custom_menu_init(sHead); +} + +void custom_menu_destroy(void) { + /* TODO: we should probably clean up all of this stuff */ +} + +void custom_menu_system_loop(void) { + custom_menu_loop(); +} + +static void button_force_instant_close(struct Object* obj) { + obj->oFaceAngleYaw = 0x8000; + obj->oFaceAnglePitch = 0; + obj->oParentRelativePosX = obj->oMenuButtonOrigPosX; + obj->oParentRelativePosY = obj->oMenuButtonOrigPosY; + obj->oParentRelativePosZ = obj->oMenuButtonOrigPosZ; + obj->oMenuButtonScale = 0.11111111f; + obj->oMenuButtonState = MENU_BUTTON_STATE_DEFAULT; + obj->oMenuButtonTimer = 0; +} + +static void button_force_instant_open(struct Object* obj) { + obj->oFaceAngleYaw = 0; + obj->oFaceAnglePitch = 0; + obj->oParentRelativePosX = 0.0f; + obj->oParentRelativePosY = 0.0f; + obj->oParentRelativePosZ = -801; + obj->oMenuButtonScale = 0.623111; + obj->oMenuButtonState = MENU_BUTTON_STATE_FULLSCREEN; + obj->oMenuButtonTimer = 0; +} + +void custom_menu_open(struct CustomMenu* menu) { + if (sCurrentMenu == menu) { return; } + if (menu == NULL) { return; } + // force instant-close all parents if not a direct route + { + // check all buttons of current menu to see if the desired menu is directly beneath it + struct CustomMenuButton* onButton = sCurrentMenu->buttons; + u8 foundMenu = FALSE; + while (onButton != NULL) { + if (onButton == menu->me) { foundMenu = TRUE; break; } + onButton = onButton->next; + } + + // if not direct route, force close all the way to the main menu + if (!foundMenu) { + struct CustomMenu* onMenu = sCurrentMenu; + while (onMenu != NULL && onMenu != sHead) { + struct Object* obj = onMenu->me->object; + if (obj->oMenuButtonState != MENU_BUTTON_STATE_FULLSCREEN) { break; } + button_force_instant_close(obj); + if (onMenu->on_close != NULL) { onMenu->on_close(); } + onMenu = onMenu->parent; + } + } + } + + // force instant-open all parents + { + struct CustomMenu* onMenu = menu->parent; + while (onMenu != NULL) { + struct Object* obj = onMenu->me->object; + if (obj->oMenuButtonState == MENU_BUTTON_STATE_FULLSCREEN) { break; } + button_force_instant_open(obj); + onMenu = onMenu->parent; + } + } + struct Object* obj = menu->me->object; + obj->oMenuButtonState = MENU_BUTTON_STATE_GROWING; + obj->oMenuButtonTimer = 0; + gMenuStringAlpha = 0; + sLastMenu = sCurrentMenu; + sCurrentMenu = menu; + play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); +} + +void custom_menu_close(void) { + struct Object* obj = sCurrentMenu->me->object; + obj->oMenuButtonState = MENU_BUTTON_STATE_SHRINKING; + obj->oMenuButtonTimer = 0; + gMenuStringAlpha = 0; + if (sCurrentMenu->on_close != NULL) { sCurrentMenu->on_close(); } + sLastMenu = sCurrentMenu; + if (sCurrentMenu->parent != NULL) { + sCurrentMenu = sCurrentMenu->parent; + } + play_sound(SOUND_MENU_CAMERA_ZOOM_OUT, gDefaultSoundArgs); +} + +void custom_menu_close_system(void) { + sHead->me->object->oMenuButtonState = MENU_BUTTON_STATE_SHRINKING; + gInCustomMenu = FALSE; + play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); +} + +static s32 cursor_inside_button(struct CustomMenuButton* button, f32 cursorX, f32 cursorY) { + f32 x = button->object->oParentRelativePosX; + f32 y = button->object->oParentRelativePosY; + x *= -0.137f; + y *= 0.137f; + + s16 maxX = x + 25.0f; + s16 minX = x - 25.0f; + s16 maxY = y + 21.0f; + s16 minY = y - 21.0f; + + return (cursorX < maxX && minX < cursorX && cursorY < maxY && minY < cursorY); +} + +void custom_menu_cursor_click(f32 cursorX, f32 cursorY) { +#ifdef VERSION_EU + u16 cursorClickButtons = (A_BUTTON | B_BUTTON | START_BUTTON | Z_TRIG); +#else + u16 cursorClickButtons = (A_BUTTON | B_BUTTON | START_BUTTON); +#endif + if (!(gPlayer3Controller->buttonPressed & cursorClickButtons)) { return; } + if (sCurrentMenu->me->object->oMenuButtonState != MENU_BUTTON_STATE_FULLSCREEN) { return; } + + struct CustomMenuButton* button = sCurrentMenu->buttons; + while (button != NULL) { + if (cursor_inside_button(button, cursorX, cursorY)) { + u8 didSomething = FALSE; + if (button->menu != NULL) { custom_menu_open(button->menu); didSomething = TRUE; } + if (button->on_click != NULL) { button->on_click(); didSomething = TRUE; } + if (didSomething) { break; } + } + button = button->next; + } +} + +static void button_label_pos(struct CustomMenuButton* button, s16* outX, s16* outY) { + f32 x = button->object->oParentRelativePosX; + f32 y = button->object->oParentRelativePosY; + x -= strlen(button->label) * -27.0f; + y += -163.0f; + *outX = 165.0f + x * -0.137f; + *outY = 110.0f + y * 0.137f; +} + +void custom_menu_print_strings(void) { + // figure out alpha + struct Object* curObj = sCurrentMenu->me->object; + struct Object* lastObj = (sLastMenu != NULL) ? sLastMenu->me->object : NULL; + + if (curObj != NULL && lastObj != NULL) { + if (curObj->oMenuButtonState == MENU_BUTTON_STATE_FULLSCREEN && lastObj->oMenuButtonState != MENU_BUTTON_STATE_SHRINKING) { + if (gMenuStringAlpha < 250) { + gMenuStringAlpha += 10; + } else { + gMenuStringAlpha = 255; + } + } + } + + // print header + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gMenuStringAlpha); + char* headerLabel = sCurrentMenu->me->label; + u16 headerLabelLen = strlen(headerLabel); + u16 headerX = (u16)(159.66f - (headerLabelLen * 5.66f)); + + unsigned char header[64]; + str_ascii_to_dialog(headerLabel, header, headerLabelLen); + + print_hud_lut_string(HUD_LUT_DIFF, headerX, sCurrentMenu->headerY, header); + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); + + // print text + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + struct CustomMenuButton* button = sCurrentMenu->buttons; + while (button != NULL) { + gDPSetEnvColor(gDisplayListHead++, 222, 222, 222, gMenuStringAlpha); + s16 x, y; + button_label_pos(button, &x, &y); + print_generic_ascii_string(x, y, button->label); + button = button->next; + } + if (sCurrentMenu->draw_strings != NULL) { sCurrentMenu->draw_strings(); } + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); + +} + +/** + * Grow from submenu, used by selecting a file in the score menu. + */ +void bhv_menu_button_growing_from_custom(struct Object* button) { + if (button->oMenuButtonTimer < 16) { + button->oFaceAngleYaw += 0x800; + } + if (button->oMenuButtonTimer < 8) { + button->oFaceAnglePitch += 0x800; + } + if (button->oMenuButtonTimer >= 8 && button->oMenuButtonTimer < 16) { + button->oFaceAnglePitch -= 0x800; + } + + button->oParentRelativePosX -= button->oMenuButtonOrigPosX / 16.0; + button->oParentRelativePosY -= button->oMenuButtonOrigPosY / 16.0; + button->oParentRelativePosZ -= 50; + + button->oMenuButtonScale += 0.032f; + + button->oMenuButtonTimer++; + if (button->oMenuButtonTimer == 16) { + button->oParentRelativePosX = 0.0f; + button->oParentRelativePosY = 0.0f; + button->oMenuButtonState = MENU_BUTTON_STATE_FULLSCREEN; + button->oMenuButtonTimer = 0; + } +} + +/** + * Shrink back to submenu, used to return back while inside a score save menu. + */ +void bhv_menu_button_shrinking_to_custom(struct Object* button) { + if (button->oMenuButtonTimer < 16) { + button->oFaceAngleYaw -= 0x800; + } + if (button->oMenuButtonTimer < 8) { + button->oFaceAnglePitch -= 0x800; + } + if (button->oMenuButtonTimer >= 8 && button->oMenuButtonTimer < 16) { + button->oFaceAnglePitch += 0x800; + } + + button->oParentRelativePosX += button->oMenuButtonOrigPosX / 16.0; + button->oParentRelativePosY += button->oMenuButtonOrigPosY / 16.0; + button->oParentRelativePosZ += 50; + + button->oMenuButtonScale -= 0.032f; + + button->oMenuButtonTimer++; + if (button->oMenuButtonTimer == 16) { + button->oParentRelativePosX = button->oMenuButtonOrigPosX; + button->oParentRelativePosY = button->oMenuButtonOrigPosY; + button->oMenuButtonScale = 0.11111111f; + button->oMenuButtonState = MENU_BUTTON_STATE_DEFAULT; + button->oMenuButtonTimer = 0; + } +} diff --git a/src/menu/custom_menu_system.h b/src/menu/custom_menu_system.h new file mode 100644 index 00000000..018eca3d --- /dev/null +++ b/src/menu/custom_menu_system.h @@ -0,0 +1,40 @@ +#ifndef CUSTOM_MENU_SYSTEM_H +#define CUSTOM_MENU_SYSTEM_H +#include "file_select.h" + +struct CustomMenuButton { + struct Object* object; + char* label; + void (*on_click)(void); + struct CustomMenu* menu; + struct CustomMenuButton* next; +}; + +struct CustomMenu { + struct CustomMenu* parent; + struct CustomMenuButton* me; + struct CustomMenuButton* buttons; + u16 headerY; + u16 depth; + void (*draw_strings)(void); + void (*on_close)(void); +}; + +extern u8 gMenuStringAlpha; + +void custom_menu_system_init(void); +struct CustomMenu* custom_menu_create(struct CustomMenu* parent, char* label, u16 x, u16 y); +struct CustomMenuButton* custom_menu_create_button(struct CustomMenu* parent, char* label, u16 x, u16 y, void (*on_click)(void)); + +void custom_menu_system_loop(void); +void custom_menu_print_strings(void); +void custom_menu_cursor_click(f32 x, f32 y); + +void custom_menu_open(struct CustomMenu* menu); +void custom_menu_close(void); +void custom_menu_close_system(void); + +void bhv_menu_button_growing_from_custom(struct Object* button); +void bhv_menu_button_shrinking_to_custom(struct Object* button); + +#endif // CUSTOM_MENU_SYSTEM_H diff --git a/src/menu/file_select.c b/src/menu/file_select.c index 719962b3..041d73e3 100644 --- a/src/menu/file_select.c +++ b/src/menu/file_select.c @@ -21,19 +21,16 @@ #include "sm64.h" #include "text_strings.h" -#include "game/ingame_menu.h" - #include "eu_translation.h" + +#include "custom_menu_system.h" +#include "custom_menu.h" + #ifdef VERSION_EU #undef LANGUAGE_FUNCTION #define LANGUAGE_FUNCTION sLanguageMode #endif -#include -#include "pc/configfile.h" -#include "pc/controller/controller_keyboard.h" -#include "pc/network/network.h" - /** * @file file_select.c * This file implements how the file select and it's menus render and function. @@ -41,9 +38,8 @@ * special menu messages and phases, button states and button clicked checks. */ -static u8 joinVersionMismatch = FALSE; -static char joinMenuCustomText[64] = { 0 }; -static u8 forceOpenJoinMenu = 0; +u8 gInCustomMenu = 1; +u8 sIgnoreMenuTimer = 5; #ifdef VERSION_US // The current sound mode is automatically centered on US due to @@ -63,7 +59,7 @@ static s16 sSoundTextY; #define NUM_BUTTONS 34 #endif #else -#define NUM_BUTTONS 36 +#define NUM_BUTTONS 32 #endif // Amount of main menu buttons defined in the code called by spawn_object_rel_with_rot. @@ -98,7 +94,7 @@ static u8 sTextBaseAlpha = 0; // 2D position of the cursor on the screen. // sCursorPos[0]: X | sCursorPos[1]: Y -static f32 sCursorPos[] = {0, 0}; +f32 sCursorPos[] = {0, 0}; // Determines which graphic to use for the cursor. static s16 sCursorClickingTimer = 0; @@ -144,7 +140,7 @@ static s8 sAllFilesExist = FALSE; // Defines the value of the save slot selected in the menu. // Mario A: 1 | Mario B: 2 | Mario C: 3 | Mario D: 4 -static s8 sSelectedFileNum = 0; +s8 sSelectedFileNum = 0; // Which coin score mode to use when scoring files. 0 for local // coin high score, 1 for high score across all files. @@ -379,256 +375,10 @@ static void bhv_menu_button_growing_from_main_menu(struct Object *button) { } } -// ---------------------------------------- // -// ------- custom network menu code ------- // -// ---------------------------------------- // - -void exit_join_to_network_menu(void) { - // Begin exit - if (sMainMenuButtons[MENU_BUTTON_JOIN]->oMenuButtonState == MENU_BUTTON_STATE_FULLSCREEN - && sCursorClickingTimer == 2) { - // clear custom text - joinMenuCustomText[0] = '\0'; - - play_sound(SOUND_MENU_CAMERA_ZOOM_OUT, gDefaultSoundArgs); - sMainMenuButtons[MENU_BUTTON_JOIN]->oMenuButtonState = MENU_BUTTON_STATE_SHRINKING; - network_shutdown(); - keyboard_stop_text_input(); - } - // End exit - if (sMainMenuButtons[MENU_BUTTON_JOIN]->oMenuButtonState == MENU_BUTTON_STATE_DEFAULT) { - sSelectedButtonID = MENU_BUTTON_NETWORK_MODE; - if (sCurrentMenuLevel == MENU_LAYER_SUBMENU) { - sCurrentMenuLevel = MENU_LAYER_MAIN; - } - } -} - -void keyboard_exit_join_to_network_menu(void) { - sCursorClickingTimer = 2; - exit_join_to_network_menu(); -} - -void join_server_as_client(void) { - if (gNetworkType != NT_NONE) { return; } - - char delims[2] = " "; - - // copy input - char buffer[MAX_TEXT_INPUT] = { 0 }; - strncpy(buffer, gTextInput, MAX_TEXT_INPUT); - char* text = buffer; - - // trim whitespace - while (*text == ' ') { text++; } - - // grab IP - char* ip = strtok(text, delims); - if (ip == NULL) { - exit_join_to_network_menu(); - return; - } - strncpy(configJoinIp, ip, MAX_CONFIG_STRING); - - // grab port - char* port = strtok(NULL, delims); - if (port != NULL) { - unsigned int intPort = atoi(port); - if (intPort == 0) { - exit_join_to_network_menu(); - return; - } - configJoinPort = intPort; - } else { - configJoinPort = DEFAULT_PORT; - } - - keyboard_stop_text_input(); - joinVersionMismatch = FALSE; - network_init(NT_CLIENT); -} - -void joined_server_as_client(s16 fileIndex) { - if (gNetworkType != NT_CLIENT) { return; } - sSelectedFileNum = fileIndex; -} - -void joined_server_version_mismatch(void) { - if (gNetworkType != NT_CLIENT) { return; } - joinVersionMismatch = TRUE; -} - -void render_network_mode_menu_buttons(struct Object* soundModeButton) { -#define NETWORK_BUTTON_Y 0 - // Host option button - sMainMenuButtons[MENU_BUTTON_HOST] = spawn_object_rel_with_rot( - soundModeButton, MODEL_MAIN_MENU_MARIO_NEW_BUTTON, bhvMenuButton, 266, NETWORK_BUTTON_Y, -100, 0, -0x8000, 0); - sMainMenuButtons[MENU_BUTTON_HOST]->oMenuButtonScale = 0.11111111f; - sMainMenuButtons[MENU_BUTTON_HOST]->oFaceAngleRoll = 0; - - // Join option button - sMainMenuButtons[MENU_BUTTON_JOIN] = spawn_object_rel_with_rot( - soundModeButton, MODEL_MAIN_MENU_MARIO_NEW_BUTTON, bhvMenuButton, -266, NETWORK_BUTTON_Y, -100, 0, -0x8000, 0); - sMainMenuButtons[MENU_BUTTON_JOIN]->oMenuButtonScale = 0.11111111f; - sMainMenuButtons[MENU_BUTTON_JOIN]->oFaceAngleRoll = 0; -} - -void open_join_menu(char* customText) { - if (sMainMenuButtons[MENU_BUTTON_JOIN] == NULL) { - forceOpenJoinMenu = (forceOpenJoinMenu == 0) ? 1 : forceOpenJoinMenu; - } else if (sMainMenuButtons[MENU_BUTTON_JOIN]->oMenuButtonState != MENU_BUTTON_STATE_FULLSCREEN) { - forceOpenJoinMenu = 0; - play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); - sMainMenuButtons[MENU_BUTTON_JOIN]->oMenuButtonState = MENU_BUTTON_STATE_GROWING; - sSelectedButtonID = MENU_BUTTON_JOIN; - sCurrentMenuLevel = MENU_LAYER_SUBMENU; - } else { - forceOpenJoinMenu = 0; - } - - if (customText == joinMenuCustomText) { return; } - - if (customText != NULL) { - strncpy(joinMenuCustomText, customText, 63); - } else if (*gTextInput == '\0') { - joinMenuCustomText[0] = '\0'; - } -} - -void check_network_mode_menu_clicked_buttons(struct Object* networkModeButton) { - if (networkModeButton->oMenuButtonState == MENU_BUTTON_STATE_FULLSCREEN) { - - if (forceOpenJoinMenu && forceOpenJoinMenu++ > 3) { - forceOpenJoinMenu = 0; - open_join_menu(joinMenuCustomText); - return; - } - - s32 buttonID; - // Configure sound mode menu button group - for (buttonID = MENU_BUTTON_NETWORK_MIN; buttonID < MENU_BUTTON_NETWORK_MAX; buttonID++) { - s16 buttonX = sMainMenuButtons[buttonID]->oPosX; - s16 buttonY = sMainMenuButtons[buttonID]->oPosY; - - if (check_clicked_button(buttonX, buttonY, 22.0f) == TRUE) { - if (buttonID == MENU_BUTTON_HOST) { - if (networkModeButton->oMenuButtonActionPhase == SOUND_MODE_PHASE_MAIN) { - play_sound(SOUND_MENU_CLICK_FILE_SELECT, gDefaultSoundArgs); - sMainMenuButtons[buttonID]->oMenuButtonState = MENU_BUTTON_STATE_ZOOM_IN_OUT; - sSelectedButtonID = buttonID; - //sSoundMode = buttonID - MENU_BUTTON_OPTION_MIN; - } - } - else if (buttonID == MENU_BUTTON_JOIN) { - open_join_menu(NULL); - - // start input - keyboard_start_text_input(TIM_IP, keyboard_exit_join_to_network_menu, join_server_as_client); - - // fill in config ip/port - static u8 openedJoinMenu = FALSE; - if (!openedJoinMenu && strlen(configJoinIp) > 0) { - if (configJoinPort == 0) { configJoinPort = DEFAULT_PORT; } - sprintf(gTextInput, "%s %d", configJoinIp, configJoinPort); - } - } - sCurrentMenuLevel = MENU_LAYER_SUBMENU; - - break; - } - } - } -} - -void print_network_mode_menu_strings(void) { - s32 mode; - s16 textX; -#define HEADER_HUD_X 106 - unsigned char textHeader[10]; - str_ascii_to_dialog("SM64 COOP", textHeader, 9); - - // Print header text - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); - gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, sTextBaseAlpha); - - print_hud_lut_string(HUD_LUT_DIFF, HEADER_HUD_X, 55, textHeader); - - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); - - gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); - - #define TEXT_HOST 0x11,0x18,0x1C,0x1D,0xFF - #define TEXT_JOIN 0x13,0x18,0x12,0x17,0xFF - static unsigned char textNetworkModes[][5] = { { TEXT_HOST }, { TEXT_JOIN } }; - - // Print network mode names - for (mode = 0; mode < 2; mode++) { - gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, sTextBaseAlpha); - - textX = get_str_x_pos_from_center(mode * 72 + 124, textNetworkModes[mode], 10.0f); - print_generic_string(textX, 87, textNetworkModes[mode]); - } - - // Print disclaimers - print_generic_ascii_string(98, 150, "Still in early development."); - print_generic_ascii_string(35, 34, "Levels after Bowser 1 don't synchronize yet."); - - gSPDisplayList(gDisplayListHead++, dl_ia_text_end); -} - -void print_join_mode_menu_strings(void) { -#define JOIN_MARIO_X 25 -#define JOIN_FILE_LETTER_X 95 -#define JOIN_LEVEL_NAME_X 25 -#define JOIN_SECRET_STARS_X 171 -#define JOIN_MYSCORE_X 238 -#define JOIN_HISCORE_X 231 - - unsigned char textMario[8]; - str_ascii_to_dialog("CONNECT", textMario, 7); - - // Print file name at top - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); - gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, sTextBaseAlpha); - print_hud_lut_string(HUD_LUT_DIFF, JOIN_MARIO_X, 15, textMario); - - // Print course scores - gSPDisplayList(gDisplayListHead++, dl_menu_ia8_text_begin); - gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, sTextBaseAlpha); - - // Print level name - if (joinMenuCustomText[0] != '\0') { - print_generic_ascii_string(JOIN_LEVEL_NAME_X, 191 - (14 * 3), joinMenuCustomText); - } else { - print_generic_ascii_string(JOIN_LEVEL_NAME_X, 191 - (14 * 0), "Accept a Discord invite."); - print_generic_ascii_string(JOIN_LEVEL_NAME_X, 191 - (14 * 1), "Alternatively, type or paste the host's IP."); - print_generic_ascii_string(JOIN_LEVEL_NAME_X, 191 - (14 * 3), gTextInput); - } - - // Print status - if (joinVersionMismatch) { - print_generic_ascii_string(JOIN_LEVEL_NAME_X, 191 - (13 * 14), "Error - versions don't match. Both should rebuild!"); - } else if (gNetworkType == NT_CLIENT) { - print_generic_ascii_string(JOIN_LEVEL_NAME_X, 191 - (13 * 14), "Connecting..."); - } else if (strlen(gTextInput) > 0) { - print_generic_ascii_string(JOIN_LEVEL_NAME_X, 191 - (13 * 14), "Press (ENTER) to directly connect."); - } - - gSPDisplayList(gDisplayListHead++, dl_menu_ia8_text_end); -} - -// ---------------------------------------- // - /** * Shrink back to main menu, used to return back while inside menus. */ static void bhv_menu_button_shrinking_to_main_menu(struct Object *button) { - // hack, make sure network button goes off-screen - if (button == sMainMenuButtons[MENU_BUTTON_NETWORK_MODE]) { - button->oMenuButtonOrigPosX = 0; - button->oMenuButtonOrigPosY = -9999; - } - if (button->oMenuButtonTimer < 16) { button->oFaceAngleYaw -= 0x800; } @@ -764,6 +514,7 @@ static void bhv_menu_button_zoom_out(struct Object *button) { * positions when you choose a button. */ void bhv_menu_button_init(void) { + if (gCurrentObject->oMenuButtonIsCustom) { return; } gCurrentObject->oMenuButtonOrigPosX = gCurrentObject->oParentRelativePosX; gCurrentObject->oMenuButtonOrigPosY = gCurrentObject->oParentRelativePosY; } @@ -779,10 +530,11 @@ void bhv_menu_button_loop(void) { gCurrentObject->oMenuButtonOrigPosZ = gCurrentObject->oPosZ; break; case MENU_BUTTON_STATE_GROWING: // Switching from button to menu state - if (sCurrentMenuLevel == MENU_LAYER_MAIN) { + if (gInCustomMenu) { + bhv_menu_button_growing_from_custom(gCurrentObject); + } else if (sCurrentMenuLevel == MENU_LAYER_MAIN) { bhv_menu_button_growing_from_main_menu(gCurrentObject); - } - if (sCurrentMenuLevel == MENU_LAYER_SUBMENU) { + } else if (sCurrentMenuLevel == MENU_LAYER_SUBMENU) { bhv_menu_button_growing_from_submenu(gCurrentObject); // Only used for score files } sTextBaseAlpha = 0; @@ -791,10 +543,11 @@ void bhv_menu_button_loop(void) { case MENU_BUTTON_STATE_FULLSCREEN: // Menu state break; case MENU_BUTTON_STATE_SHRINKING: // Switching from menu to button state - if (sCurrentMenuLevel == MENU_LAYER_MAIN) { + if (gInCustomMenu) { + bhv_menu_button_shrinking_to_custom(gCurrentObject); + } else if (sCurrentMenuLevel == MENU_LAYER_MAIN) { bhv_menu_button_shrinking_to_main_menu(gCurrentObject); - } - if (sCurrentMenuLevel == MENU_LAYER_SUBMENU) { + } else if (sCurrentMenuLevel == MENU_LAYER_SUBMENU) { bhv_menu_button_shrinking_to_submenu(gCurrentObject); // Only used for score files } sTextBaseAlpha = 0; @@ -1279,17 +1032,14 @@ void render_sound_mode_menu_buttons(struct Object *soundModeButton) { sMainMenuButtons[MENU_BUTTON_STEREO] = spawn_object_rel_with_rot( soundModeButton, MODEL_MAIN_MENU_GENERIC_BUTTON, bhvMenuButton, 533, SOUND_BUTTON_Y, -100, 0, -0x8000, 0); sMainMenuButtons[MENU_BUTTON_STEREO]->oMenuButtonScale = 0.11111111f; - sMainMenuButtons[MENU_BUTTON_STEREO]->oFaceAngleRoll = 0; // Mono option button sMainMenuButtons[MENU_BUTTON_MONO] = spawn_object_rel_with_rot( soundModeButton, MODEL_MAIN_MENU_GENERIC_BUTTON, bhvMenuButton, 0, SOUND_BUTTON_Y, -100, 0, -0x8000, 0); sMainMenuButtons[MENU_BUTTON_MONO]->oMenuButtonScale = 0.11111111f; - sMainMenuButtons[MENU_BUTTON_MONO]->oFaceAngleRoll = 0; // Headset option button sMainMenuButtons[MENU_BUTTON_HEADSET] = spawn_object_rel_with_rot( soundModeButton, MODEL_MAIN_MENU_GENERIC_BUTTON, bhvMenuButton, -533, SOUND_BUTTON_Y, -100, 0, -0x8000, 0); sMainMenuButtons[MENU_BUTTON_HEADSET]->oMenuButtonScale = 0.11111111f; - sMainMenuButtons[MENU_BUTTON_HEADSET]->oFaceAngleRoll = 0; #ifdef VERSION_EU // English option button @@ -1377,8 +1127,7 @@ void check_sound_mode_menu_clicked_buttons(struct Object *soundModeButton) { void load_main_menu_save_file(struct Object *fileButton, s32 fileNum) { if (fileButton->oMenuButtonState == MENU_BUTTON_STATE_FULLSCREEN) { sSelectedFileNum = fileNum; - configHostSaveSlot = fileNum; - network_init(NT_SERVER); + custom_menu_on_load_save_file(sSelectedFileNum); } } @@ -1420,11 +1169,6 @@ void return_to_main_menu(s16 prevMenuButtonID, struct Object *sourceButton) { mark_obj_for_deletion(sMainMenuButtons[buttonID]); } } - if (prevMenuButtonID == MENU_BUTTON_NETWORK_MODE) { - for (buttonID = MENU_BUTTON_NETWORK_MIN; buttonID < MENU_BUTTON_NETWORK_MAX; buttonID++) { - mark_obj_for_deletion(sMainMenuButtons[buttonID]); - } - } } } @@ -1616,12 +1360,11 @@ void bhv_menu_button_manager_init(void) { sMainMenuButtons[MENU_BUTTON_SOUND_MODE] = spawn_object_rel_with_rot( gCurrentObject, MODEL_MAIN_MENU_PURPLE_SOUND_BUTTON, bhvMenuButton, 6400, -3500, 0, 0, 0, 0); sMainMenuButtons[MENU_BUTTON_SOUND_MODE]->oMenuButtonScale = 1.0f; - // Network menu button - sMainMenuButtons[MENU_BUTTON_NETWORK_MODE] = spawn_object_rel_with_rot( - gCurrentObject, MODEL_MAIN_MENU_GREEN_SCORE_BUTTON, bhvMenuButton, 6400, -5500, 0, 0, 0, 0); - sMainMenuButtons[MENU_BUTTON_NETWORK_MODE]->oMenuButtonScale = 1.0f; sTextBaseAlpha = 0; + + // custom menus + custom_menu_system_init(); } #if defined(VERSION_JP) || defined(VERSION_SH) @@ -1635,31 +1378,10 @@ void bhv_menu_button_manager_init(void) { * Also play a sound and/or render buttons depending of the button ID selected. */ void check_main_menu_clicked_buttons(void) { - - // force the network screen to open automatically - static u8 networkInit = FALSE; - if (!networkInit) { - sMainMenuButtons[MENU_BUTTON_NETWORK_MODE]->oMenuButtonState = MENU_BUTTON_STATE_GROWING; - - struct Object* button = sMainMenuButtons[MENU_BUTTON_NETWORK_MODE]; - button->oFaceAnglePitch = 0; - button->oFaceAngleYaw = 32768; - button->oFaceAngleRoll = 0; - button->oParentRelativePosX = 0.0f; - button->oParentRelativePosY = 0.0f; - button->oParentRelativePosZ = 17800.0f; - button->oMenuButtonOrigPosX = 0; - button->oMenuButtonOrigPosY = 0; - button->oMenuButtonOrigPosZ = -17800.0f; - button->oMenuButtonScale = 1.0f; - button->oMenuButtonState = MENU_BUTTON_STATE_FULLSCREEN; - button->oMenuButtonTimer = 0; - - sSelectedButtonID = MENU_BUTTON_NETWORK_MODE; - - networkInit = TRUE; + if (sIgnoreMenuTimer > 0) { + sIgnoreMenuTimer--; + return; } - #ifdef VERSION_EU if (sMainMenuTimer >= 5) { #endif @@ -1725,10 +1447,6 @@ void check_main_menu_clicked_buttons(void) { play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); render_sound_mode_menu_buttons(sMainMenuButtons[MENU_BUTTON_SOUND_MODE]); break; - case MENU_BUTTON_NETWORK_MODE: - play_sound(SOUND_MENU_CAMERA_ZOOM_IN, gDefaultSoundArgs); - render_network_mode_menu_buttons(sMainMenuButtons[MENU_BUTTON_NETWORK_MODE]); - break; } #ifdef VERSION_EU } @@ -1743,6 +1461,10 @@ void check_main_menu_clicked_buttons(void) { * is loaded, and that checks what buttonID is clicked in the main menu. */ void bhv_menu_button_manager_loop(void) { + if (gInCustomMenu) { + custom_menu_system_loop(); + return; + } switch (sSelectedButtonID) { case MENU_BUTTON_NONE: check_main_menu_clicked_buttons(); @@ -1837,17 +1559,6 @@ void bhv_menu_button_manager_loop(void) { check_sound_mode_menu_clicked_buttons(sMainMenuButtons[MENU_BUTTON_SOUND_MODE]); break; - case MENU_BUTTON_NETWORK_MODE: - check_network_mode_menu_clicked_buttons(sMainMenuButtons[MENU_BUTTON_NETWORK_MODE]); - break; - - case MENU_BUTTON_HOST: - return_to_main_menu(MENU_BUTTON_NETWORK_MODE, sMainMenuButtons[MENU_BUTTON_HOST]); - break; - case MENU_BUTTON_JOIN: - exit_join_to_network_menu(); - break; - // STEREO, MONO and HEADSET buttons are undefined so they can be selected without // exiting the Options menu, as a result they added a return button #ifdef VERSION_EU @@ -1904,16 +1615,6 @@ void handle_cursor_button_input(void) { sClickPos[1] = sCursorPos[1]; sCursorClickingTimer = 1; } - if (gNetworkType == NT_SERVER) { - sClickPos[0] = sCursorPos[0]; - sClickPos[1] = sCursorPos[1]; - sCursorClickingTimer = 1; - } -/*#ifdef IMMEDIATELOAD - sClickPos[0] = sCursorPos[0]; - sClickPos[1] = sCursorPos[1]; - sCursorClickingTimer = 1; -#endif*/ } } @@ -1953,6 +1654,9 @@ void handle_controller_cursor_input(void) { if (sCursorClickingTimer == 0) { handle_cursor_button_input(); + if (gInCustomMenu) { + custom_menu_cursor_click(sCursorPos[0], sCursorPos[1]); + } } } @@ -3036,6 +2740,12 @@ static void print_file_select_strings(void) { UNUSED s32 unused2; create_dl_ortho_matrix(); + + if (gInCustomMenu) { + custom_menu_print_strings(); + return; + } + switch (sSelectedButtonID) { case MENU_BUTTON_NONE: #ifdef VERSION_EU @@ -3070,12 +2780,6 @@ static void print_file_select_strings(void) { case MENU_BUTTON_SOUND_MODE: print_sound_mode_menu_strings(); break; - case MENU_BUTTON_NETWORK_MODE: - print_network_mode_menu_strings(); - break; - case MENU_BUTTON_JOIN: - print_join_mode_menu_strings(); - break; } // If all 4 save file exists, define true to sAllFilesExist to prevent more copies in copy menu if (save_file_exists(SAVE_FILE_A) == TRUE && save_file_exists(SAVE_FILE_B) == TRUE && @@ -3140,7 +2844,7 @@ s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused) { sClickPos[0] = -10000; sClickPos[1] = -10000; sCursorClickingTimer = 0; - if (gNetworkType != NT_CLIENT) { sSelectedFileNum = 0; } + sSelectedFileNum = 0; sSelectedFileIndex = MENU_BUTTON_NONE; sFadeOutText = FALSE; sStatusMessageID = 0; @@ -3161,14 +2865,9 @@ s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused) { } #endif - // center cursor - sCursorPos[0] = 0.0f; - sCursorPos[1] = -24.0f; - - // immediately jump in - if (gNetworkType == NT_SERVER) { - sSelectedFileNum = configHostSaveSlot; - } + // custom - center the cursor + sCursorPos[0] = 0; + sCursorPos[1] = 0; //! no return value #ifdef AVOID_UB diff --git a/src/menu/file_select.h b/src/menu/file_select.h index 63e74045..f05eeecc 100644 --- a/src/menu/file_select.h +++ b/src/menu/file_select.h @@ -87,14 +87,7 @@ enum MenuButtonTypes { MENU_BUTTON_LANGUAGE_RETURN, #endif - MENU_BUTTON_OPTION_MAX, - - MENU_BUTTON_NETWORK_MODE, - MENU_BUTTON_NETWORK_MIN, - MENU_BUTTON_HOST = MENU_BUTTON_NETWORK_MIN, - MENU_BUTTON_JOIN, - MENU_BUTTON_NETWORK_MAX, - + MENU_BUTTON_OPTION_MAX }; enum ScoreMenuMessageID { @@ -134,6 +127,10 @@ enum SoundModeMenuActionPhase { SOUND_MODE_PHASE_MAIN }; +extern f32 sCursorPos[2]; +extern s8 sSelectedFileNum; +extern u8 gInCustomMenu; + void beh_yellow_background_menu_init(void); void beh_yellow_background_menu_loop(void); void bhv_menu_button_init(void); @@ -143,8 +140,5 @@ void bhv_menu_button_manager_loop(void); Gfx *geo_file_select_strings_and_menu_cursor(s32 callContext, UNUSED struct GraphNode *node, UNUSED Mat4 mtx); s32 lvl_init_menu_values_and_cursor_pos(UNUSED s32 arg, UNUSED s32 unused); s32 lvl_update_obj_and_load_file_selected(UNUSED s32 arg, UNUSED s32 unused); -void joined_server_as_client(s16 fileIndex); -void joined_server_version_mismatch(void); -void open_join_menu(char* customText); #endif // FILE_SELECT_H diff --git a/src/pc/configfile.c b/src/pc/configfile.c index ba05873f..6b35a181 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -103,6 +103,7 @@ unsigned int configHostSaveSlot = 1; unsigned int configPlayerInteraction = 1; unsigned int configPlayerKnockbackStrength = 25; unsigned int configStayInLevelAfterStar = 0; +unsigned int configNetworkSystem = 0; static const struct ConfigOption options[] = { {.name = "fullscreen", .type = CONFIG_TYPE_BOOL, .boolValue = &configWindow.fullscreen}, @@ -156,9 +157,10 @@ static const struct ConfigOption options[] = { {.name = "coop_join_port", .type = CONFIG_TYPE_UINT , .uintValue = &configJoinPort}, {.name = "coop_host_port", .type = CONFIG_TYPE_UINT , .uintValue = &configHostPort}, {.name = "coop_host_save_slot", .type = CONFIG_TYPE_UINT , .uintValue = &configHostSaveSlot}, - {.name = "coop_player_interaction", .type = CONFIG_TYPE_UINT , .uintValue = &configPlayerInteraction}, - {.name = "coop_player_knockback_strength", .type = CONFIG_TYPE_UINT , .uintValue = &configPlayerKnockbackStrength}, - {.name = "coop_stay_in_level_after_star", .type = CONFIG_TYPE_UINT , .uintValue = &configStayInLevelAfterStar}, + {.name = "coop_player_interaction", .type = CONFIG_TYPE_UINT , .uintValue = &configPlayerInteraction}, + {.name = "coop_player_knockback_strength", .type = CONFIG_TYPE_UINT , .uintValue = &configPlayerKnockbackStrength}, + {.name = "coop_stay_in_level_after_star", .type = CONFIG_TYPE_UINT , .uintValue = &configStayInLevelAfterStar}, + {.name = "coop_network_system", .type = CONFIG_TYPE_UINT , .uintValue = &configNetworkSystem}, }; // Reads an entire line from a file (excluding the newline character) and returns an allocated string diff --git a/src/pc/configfile.h b/src/pc/configfile.h index b940d856..ec2edb8e 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -69,6 +69,7 @@ extern unsigned int configHostSaveSlot; extern unsigned int configPlayerInteraction; extern unsigned int configPlayerKnockbackStrength; extern unsigned int configStayInLevelAfterStar; +extern unsigned int configNetworkSystem; void configfile_load(const char *filename); void configfile_save(const char *filename); diff --git a/src/pc/controller/controller_keyboard.c b/src/pc/controller/controller_keyboard.c index f4692a92..8536a62c 100644 --- a/src/pc/controller/controller_keyboard.c +++ b/src/pc/controller/controller_keyboard.c @@ -79,7 +79,9 @@ static void keyboard_alter_text_input_modifier(int scancode, bool down) { bool keyboard_on_key_down(int scancode) { #ifdef DEBUG - debug_keyboard_on_key_down(scancode); + if (!inTextInput) { + debug_keyboard_on_key_down(scancode); + } #endif if (inTextInput) { // alter the held value of modifier keys @@ -155,10 +157,12 @@ char* keyboard_start_text_input(enum TextInputMode inInputMode, void (*onEscape) void keyboard_stop_text_input(void) { // stop allowing text input - wm_api->stop_text_input(); inTextInput = false; + wm_api->stop_text_input(); } +bool keyboard_in_text_input(void) { return inTextInput; } + static bool keyboard_allow_character_input(char c) { switch (textInputMode) { case TIM_IP: @@ -190,6 +194,7 @@ static bool keyboard_allow_character_input(char c) { } void keyboard_on_text_input(char* text) { + if (!inTextInput) { return; } // sanity check input if (text == NULL) { return; } diff --git a/src/pc/controller/controller_keyboard.h b/src/pc/controller/controller_keyboard.h index 43213630..2451397b 100644 --- a/src/pc/controller/controller_keyboard.h +++ b/src/pc/controller/controller_keyboard.h @@ -25,6 +25,7 @@ void keyboard_on_all_keys_up(void); void keyboard_on_text_input(char* text); char* keyboard_start_text_input(enum TextInputMode, void (*onEscape)(void), void (*onEnter)(void)); void keyboard_stop_text_input(void); +bool keyboard_in_text_input(void); #ifdef __cplusplus } diff --git a/src/pc/network/discord/activity.c b/src/pc/network/discord/activity.c index 7d3f1802..29d5dc88 100644 --- a/src/pc/network/discord/activity.c +++ b/src/pc/network/discord/activity.c @@ -2,7 +2,7 @@ #include "lobby.h" #include "discord_network.h" #include "pc/debuglog.h" -#include "menu/file_select.h" +#include "menu/custom_menu.h" #define HASH_LENGTH 8 struct DiscordActivity gCurActivity = { 0 }; @@ -32,7 +32,7 @@ static void on_activity_join_callback(UNUSED void* data, enum EDiscordResult res static void on_activity_join(UNUSED void* data, const char* secret) { LOG_INFO("> on_activity_join, secret: %s", secret); - open_join_menu("Joining Discord invite..."); + gOpenConnectMenu = TRUE; app.lobbies->connect_lobby_with_activity_secret(app.lobbies, (char*)secret, NULL, on_activity_join_callback); } diff --git a/src/pc/network/packets/packet_save_file.c b/src/pc/network/packets/packet_save_file.c index dd75f0f4..34f8e19f 100644 --- a/src/pc/network/packets/packet_save_file.c +++ b/src/pc/network/packets/packet_save_file.c @@ -7,7 +7,7 @@ #include "src/game/interaction.h" #include "src/engine/math_util.h" #include "src/game/save_file.h" -#include "src/menu/file_select.h" +#include "src/menu/custom_menu.h" #include "src/pc/fs/fs.h" #include "PR/os_eeprom.h" @@ -69,9 +69,9 @@ void network_receive_save_file(struct Packet* p) { save_file_load_all(TRUE); if (memcmp(hash, remoteHash, HASH_LENGTH) != 0) { - joined_server_version_mismatch(); + strcpy(gConnectionJoinError, "Your versions don't match, both should rebuild!"); network_shutdown(); return; } - joined_server_as_client(gCurrSaveFileNum); + custom_menu_goto_game(gCurrSaveFileNum); } diff --git a/src/pc/network/socket/socket.c b/src/pc/network/socket/socket.c index 5421096e..84227bef 100644 --- a/src/pc/network/socket/socket.c +++ b/src/pc/network/socket/socket.c @@ -2,7 +2,7 @@ #include "socket.h" #include "pc/configfile.h" #include "pc/debuglog.h" -#include "menu/file_select.h" +#include "menu/custom_menu.h" static SOCKET curSocket = INVALID_SOCKET; struct sockaddr_in txAddr = { 0 }; @@ -75,7 +75,7 @@ static bool ns_socket_initialize(enum NetworkType networkType) { if (networkType == NT_CLIENT) { char joinText[128] = { 0 }; snprintf(joinText, 63, "%s %d", configJoinIp, configJoinPort); - open_join_menu(joinText); + gOpenConnectMenu = TRUE; gNetworkType = NT_CLIENT; network_on_joined(); @@ -88,6 +88,7 @@ static bool ns_socket_initialize(enum NetworkType networkType) { } static void ns_socket_update(void) { + if (gNetworkType == NT_NONE) { return; } do { // receive packet u8 data[PACKET_LENGTH]; diff --git a/textures/segment2/custom_hud_j.rgba16.png b/textures/segment2/custom_hud_j.rgba16.png new file mode 100644 index 0000000000000000000000000000000000000000..37f64209dff6af2dfed21acfc1fbceca4b1793d8 GIT binary patch literal 3185 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 X$wi3S8CYdh00000NkvXXu0mjfdQIWk literal 0 HcmV?d00001