From 4a13fd3380e7a286a6636ef97c26b607618f37aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20R=2E=20Miguel?= Date: Fri, 15 May 2020 15:38:35 -0300 Subject: [PATCH] Add in-game control binding menu Originally on the testing branch --- Makefile | 6 +- include/text_strings.h.in | 47 +- src/game/bettercamera.h | 5 - src/game/bettercamera.inc.h | 249 +---------- src/game/ingame_menu.c | 20 +- src/game/level_update.c | 4 +- src/game/options_menu.c | 463 ++++++++++++++++++++ src/game/options_menu.h | 11 + src/pc/configfile.c | 107 +++-- src/pc/configfile.h | 41 +- src/pc/controller/controller_api.h | 16 +- src/pc/controller/controller_entry_point.c | 15 + src/pc/controller/controller_keyboard.c | 71 ++- src/pc/controller/controller_keyboard.h | 2 + src/pc/controller/controller_recorded_tas.c | 9 +- src/pc/controller/controller_sdl.c | 108 ++++- src/pc/controller/controller_sdl.h | 2 + 17 files changed, 770 insertions(+), 406 deletions(-) create mode 100644 src/game/options_menu.c create mode 100644 src/game/options_menu.h diff --git a/Makefile b/Makefile index 7f519d81..8a52418f 100644 --- a/Makefile +++ b/Makefile @@ -448,8 +448,8 @@ endif # Check for better camera option ifeq ($(BETTERCAMERA),1) -CC_CHECK += -DBETTERCAMERA -CFLAGS += -DBETTERCAMERA +CC_CHECK += -DBETTERCAMERA -DEXT_OPTIONS_MENU +CFLAGS += -DBETTERCAMERA -DEXT_OPTIONS_MENU endif # Check for no drawing distance option @@ -588,11 +588,13 @@ ifeq ($(VERSION),eu) $(BUILD_DIR)/src/menu/file_select.o: $(BUILD_DIR)/include/text_strings.h $(BUILD_DIR)/bin/eu/translation_en.o $(BUILD_DIR)/bin/eu/translation_de.o $(BUILD_DIR)/bin/eu/translation_fr.o $(BUILD_DIR)/src/menu/star_select.o: $(BUILD_DIR)/include/text_strings.h $(BUILD_DIR)/bin/eu/translation_en.o $(BUILD_DIR)/bin/eu/translation_de.o $(BUILD_DIR)/bin/eu/translation_fr.o $(BUILD_DIR)/src/game/ingame_menu.o: $(BUILD_DIR)/include/text_strings.h $(BUILD_DIR)/bin/eu/translation_en.o $(BUILD_DIR)/bin/eu/translation_de.o $(BUILD_DIR)/bin/eu/translation_fr.o +$(BUILD_DIR)/src/game/options_menu.o: $(BUILD_DIR)/include/text_strings.h $(BUILD_DIR)/bin/eu/translation_en.o $(BUILD_DIR)/bin/eu/translation_de.o $(BUILD_DIR)/bin/eu/translation_fr.o O_FILES += $(BUILD_DIR)/bin/eu/translation_en.o $(BUILD_DIR)/bin/eu/translation_de.o $(BUILD_DIR)/bin/eu/translation_fr.o else $(BUILD_DIR)/src/menu/file_select.o: $(BUILD_DIR)/include/text_strings.h $(BUILD_DIR)/src/menu/star_select.o: $(BUILD_DIR)/include/text_strings.h $(BUILD_DIR)/src/game/ingame_menu.o: $(BUILD_DIR)/include/text_strings.h +$(BUILD_DIR)/src/game/options_menu.o: $(BUILD_DIR)/include/text_strings.h endif ################################################################ diff --git a/include/text_strings.h.in b/include/text_strings.h.in index 030f3959..88fe2f7c 100644 --- a/include/text_strings.h.in +++ b/include/text_strings.h.in @@ -3,20 +3,39 @@ #include "text_menu_strings.h" -#define NC_CAMX _("Camera X Sensitivity") -#define NC_CAMY _("Camera Y Sensitivity") -#define NC_INVERTX _("Invert X Axis") -#define NC_INVERTY _("Invert Y Axis") -#define NC_CAMC _("Camera Centre Aggression") -#define NC_CAMP _("Camera Pan Level") -#define NC_ENABLED _("Enabled") -#define NC_DISABLED _("Disabled") -#define NC_BUTTON _("[R]: Options") -#define NC_BUTTON2 _("[R]: Return") -#define NC_OPTION _("OPTIONS") -#define NC_HIGHLIGHT _("O") -#define NC_ANALOGUE _("Analogue Camera") -#define NC_MOUSE _("Mouse Look") +#define TEXT_OPT_CAMX _("Camera X Sensitivity") +#define TEXT_OPT_CAMY _("Camera Y Sensitivity") +#define TEXT_OPT_INVERTX _("Invert X Axis") +#define TEXT_OPT_INVERTY _("Invert Y Axis") +#define TEXT_OPT_CAMC _("Camera Centre Aggression") +#define TEXT_OPT_CAMP _("Camera Pan Level") +#define TEXT_OPT_ENABLED _("Enabled") +#define TEXT_OPT_DISABLED _("Disabled") +#define TEXT_OPT_BUTTON1 _("[R]: Options") +#define TEXT_OPT_BUTTON2 _("[R]: Return") +#define TEXT_OPT_OPTIONS _("OPTIONS") +#define TEXT_OPT_CAMERA _("CAMERA") +#define TEXT_OPT_CONTROLS _("CONTROLS") +#define TEXT_OPT_HIGHLIGHT _("O") +#define TEXT_OPT_ANALOGUE _("Analogue Camera") +#define TEXT_OPT_MOUSE _("Mouse Look") + +#define TEXT_OPT_UNBOUND _("NONE") +#define TEXT_OPT_PRESSKEY _("...") +#define TEXT_BIND_A _("A Button") +#define TEXT_BIND_B _("B Button") +#define TEXT_BIND_START _("Start Button") +#define TEXT_BIND_L _("L Trigger") +#define TEXT_BIND_R _("R Trigger") +#define TEXT_BIND_Z _("Z Trigger") +#define TEXT_BIND_C_UP _("C-Up") +#define TEXT_BIND_C_DOWN _("C-Down") +#define TEXT_BIND_C_LEFT _("C-Left") +#define TEXT_BIND_C_RIGHT _("C-Right") +#define TEXT_BIND_UP _("Stick Up") +#define TEXT_BIND_DOWN _("Stick Down") +#define TEXT_BIND_LEFT _("Stick Left") +#define TEXT_BIND_RIGHT _("Stick Right") /** * Global Symbols diff --git a/src/game/bettercamera.h b/src/game/bettercamera.h index b3355ef6..ea814dd7 100644 --- a/src/game/bettercamera.h +++ b/src/game/bettercamera.h @@ -26,14 +26,9 @@ enum newcam_flagvalues }; -extern void newcam_display_options(void); -extern void newcam_check_pause_buttons(void); extern void newcam_init_settings(void); -extern void newcam_save_settings(void); -extern void newcam_render_option_text(void); extern void newcam_diagnostics(void); -extern u8 newcam_option_open; extern u8 newcam_sensitivityX; //How quick the camera works. extern u8 newcam_sensitivityY; diff --git a/src/game/bettercamera.inc.h b/src/game/bettercamera.inc.h index 6c9592b8..a4ffa7fe 100644 --- a/src/game/bettercamera.inc.h +++ b/src/game/bettercamera.inc.h @@ -105,17 +105,6 @@ u16 newcam_mode; u16 newcam_intendedmode = 0; // which camera mode the camera's going to try to be in when not forced into another. u16 newcam_modeflags; -u8 newcam_option_open = 0; -s8 newcam_option_selection = 0; -f32 newcam_option_timer = 0; -u8 newcam_option_index = 0; -u8 newcam_option_scroll = 0; -u8 newcam_option_scroll_last = 0; -u8 newcam_total = 8; //How many options there are in newcam_uptions. - -u8 newcam_options[][64] = {{NC_ANALOGUE}, {NC_MOUSE}, {NC_CAMX}, {NC_CAMY}, {NC_INVERTX}, {NC_INVERTY}, {NC_CAMC}, {NC_CAMP}}; -u8 newcam_flags[][64] = {{NC_DISABLED}, {NC_ENABLED}}; -u8 newcam_strings[][64] = {{NC_BUTTON}, {NC_BUTTON2}, {NC_OPTION}, {NC_HIGHLIGHT}}; extern int mouse_x; extern int mouse_y; @@ -168,18 +157,6 @@ void newcam_init_settings(void) newcam_analogue = (u8)configEnableCamera; } -void newcam_save_settings(void) -{ - configCameraXSens = newcam_sensitivityX; - configCameraYSens = newcam_sensitivityY; - configCameraAggr = newcam_aggression; - configCameraPan = newcam_panlevel; - configCameraInvertX = newcam_invertX != 0; - configCameraInvertY = newcam_invertY != 0; - configEnableCamera = newcam_analogue != 0; - configCameraMouse = newcam_mouse != 0; -} - /** Mathematic calculations. This stuffs so basic even *I* understand it lol Basically, it just returns a position based on angle */ static s16 lengthdir_x(f32 length, s16 dir) @@ -670,234 +647,10 @@ void newcam_loop(struct Camera *c) newcam_position_cam(); newcam_find_fixed(); if (gMarioObject) - newcam_apply_values(c); + newcam_apply_values(c); //Just some visual information on the values of the camera. utilises ifdef because it's better at runtime. #ifdef NEWCAM_DEBUG newcam_diagnostics(); #endif // NEWCAM_DEBUG } - - - -//Displays a box. -void newcam_display_box(s16 x1, s16 y1, s16 x2, s16 y2, u8 r, u8 g, u8 b) -{ - gDPPipeSync(gDisplayListHead++); - gDPSetRenderMode(gDisplayListHead++, G_RM_OPA_SURF, G_RM_OPA_SURF2); - gDPSetCycleType(gDisplayListHead++, G_CYC_FILL); - gDPSetFillColor(gDisplayListHead++, GPACK_RGBA5551(r, g, b, 255)); - gDPFillRectangle(gDisplayListHead++, x1, y1, x2 - 1, y2 - 1); - gDPPipeSync(gDisplayListHead++); - gDPSetCycleType(gDisplayListHead++, G_CYC_1CYCLE); -} - -//I actually took the time to redo this, properly. Lmao. Please don't bully me over this anymore :( -void newcam_change_setting(u8 toggle) -{ - switch (newcam_option_selection) - { - case 0: - newcam_analogue ^= 1; - break; - case 1: - newcam_mouse ^= 1; - break; - case 2: - newcam_sensitivityX = newcam_clamp(newcam_sensitivityX + toggle, 10, 250); - break; - case 3: - newcam_sensitivityY = newcam_clamp(newcam_sensitivityY + toggle, 10, 250); - break; - case 4: - newcam_invertX ^= 1; - break; - case 5: - newcam_invertY ^= 1; - break; - case 6: - newcam_aggression = newcam_clamp(newcam_aggression + toggle, 0, 100); - break; - case 7: - newcam_panlevel = newcam_clamp(newcam_panlevel + toggle, 0, 100); - break; - } -} - -void newcam_text(s16 x, s16 y, u8 str[], u8 col) -{ - u8 textX; - textX = get_str_x_pos_from_center(x,str,10.0f); - gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); - print_generic_string(textX+1,y-1,str); - if (col != 0) - { - gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); - } - else - { - gDPSetEnvColor(gDisplayListHead++, 255, 32, 32, 255); - } - print_generic_string(textX,y,str); -} - -//Options menu -void newcam_display_options() -{ - u8 i = 0; - u8 newstring[32]; - s16 scroll; - s16 scrollpos; - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); - gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); - print_hud_lut_string(HUD_LUT_GLOBAL, 118, 40, newcam_strings[2]); - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); - - if (newcam_total>4) - { - newcam_display_box(272,90,280,208,0x80,0x80,0x80); - scrollpos = (54)*((f32)newcam_option_scroll/(newcam_total-4)); - newcam_display_box(272,90+scrollpos,280,154+scrollpos,0xFF,0xFF,0xFF); - } - - - gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); - gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, 80, SCREEN_WIDTH, SCREEN_HEIGHT); - for (i = 0; i < newcam_total; i++) - { - scroll = 140-(32*i)+(newcam_option_scroll*32); - if (scroll <= 140 && scroll > 32) - { - newcam_text(160,scroll,newcam_options[i],newcam_option_selection-i); - switch (i) - { - case 0: - newcam_text(160,scroll-12,newcam_flags[newcam_analogue],newcam_option_selection-i); - break; - case 1: - newcam_text(160,scroll-12,newcam_flags[newcam_mouse],newcam_option_selection-i); - break; - case 2: - int_to_str(newcam_sensitivityX,newstring); - newcam_text(160,scroll-12,newstring,newcam_option_selection-i); - break; - case 3: - int_to_str(newcam_sensitivityY,newstring); - newcam_text(160,scroll-12,newstring,newcam_option_selection-i); - break; - case 4: - newcam_text(160,scroll-12,newcam_flags[newcam_invertX],newcam_option_selection-i); - break; - case 5: - newcam_text(160,scroll-12,newcam_flags[newcam_invertY],newcam_option_selection-i); - break; - case 6: - int_to_str(newcam_aggression,newstring); - newcam_text(160,scroll-12,newstring,newcam_option_selection-i); - break; - case 7: - int_to_str(newcam_panlevel,newstring); - newcam_text(160,scroll-12,newstring,newcam_option_selection-i); - break; - } - } - } - gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); - gSPDisplayList(gDisplayListHead++, dl_ia_text_end); - gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); - print_hud_lut_string(HUD_LUT_GLOBAL, 80, 90+(32*(newcam_option_selection-newcam_option_scroll)), newcam_strings[3]); - print_hud_lut_string(HUD_LUT_GLOBAL, 224, 90+(32*(newcam_option_selection-newcam_option_scroll)), newcam_strings[3]); - gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); -} - -//This has been separated for interesting reasons. Don't question it. -void newcam_render_option_text(void) -{ - gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); - newcam_text(278,212,newcam_strings[newcam_option_open],1); - gSPDisplayList(gDisplayListHead++, dl_ia_text_end); -} - -void newcam_check_pause_buttons() -{ - if (gPlayer1Controller->buttonPressed & R_TRIG) - { - if (newcam_option_open == 0) - { - #ifndef nosound - play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); - #endif - newcam_option_open = 1; - } - else - { - #ifndef nosound - play_sound(SOUND_MENU_MARIO_CASTLE_WARP2, gDefaultSoundArgs); - #endif - newcam_option_open = 0; - newcam_save_settings(); - } - } - - if (newcam_option_open) - { - if (ABS(gPlayer1Controller->stickY) > 60) - { - newcam_option_timer -= 1; - if (newcam_option_timer <= 0) - { - switch (newcam_option_index) - { - case 0: newcam_option_index++; newcam_option_timer += 10; break; - default: newcam_option_timer += 3; break; - } - #ifndef nosound - play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); - #endif - if (gPlayer1Controller->stickY >= 60) - { - newcam_option_selection--; - if (newcam_option_selection < 0) - newcam_option_selection = newcam_total-1; - } - else - { - newcam_option_selection++; - if (newcam_option_selection >= newcam_total) - newcam_option_selection = 0; - } - } - } - else - if (ABS(gPlayer1Controller->stickX) > 60) - { - newcam_option_timer -= 1; - if (newcam_option_timer <= 0) - { - switch (newcam_option_index) - { - case 0: newcam_option_index++; newcam_option_timer += 10; break; - default: newcam_option_timer += 3; break; - } - #ifndef nosound - play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); - #endif - if (gPlayer1Controller->stickX >= 60) - newcam_change_setting(1); - else - newcam_change_setting(-1); - } - } - else - { - newcam_option_timer = 0; - newcam_option_index = 0; - } - - while (newcam_option_scroll - newcam_option_selection < -3 && newcam_option_selection > newcam_option_scroll) - newcam_option_scroll +=1; - while (newcam_option_scroll + newcam_option_selection > 0 && newcam_option_selection < newcam_option_scroll) - newcam_option_scroll -=1; - } -} diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index bfcbf42b..55ab55b3 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -22,6 +22,9 @@ #ifdef BETTERCAMERA #include "bettercamera.h" #endif +#ifdef EXT_OPTIONS_MENU +#include "options_menu.h" +#endif extern Gfx *gDisplayListHead; extern s16 gCurrCourseNum; @@ -2629,9 +2632,8 @@ s16 render_pause_courses_and_castle(void) { #ifdef VERSION_EU gInGameLanguage = eu_get_language(); #endif -#ifdef BETTERCAMERA - if (newcam_option_open == 0) - { +#ifdef EXT_OPTIONS_MENU + if (optmenu_open == 0) { #endif switch (gDialogBoxState) { case DIALOG_STATE_OPENING: @@ -2708,15 +2710,13 @@ s16 render_pause_courses_and_castle(void) { if (gDialogTextAlpha < 250) { gDialogTextAlpha += 25; } -#ifdef BETTERCAMERA - } - else - { +#ifdef EXT_OPTIONS_MENU + } else { shade_screen(); - newcam_display_options(); + optmenu_draw(); } - newcam_check_pause_buttons(); - newcam_render_option_text(); + optmenu_check_buttons(); + optmenu_draw_prompt(); #endif return 0; diff --git a/src/game/level_update.c b/src/game/level_update.c index 13246ad5..cdff8853 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -179,7 +179,7 @@ u8 unused3[4]; u8 unused4[2]; // For configfile intro skipping -extern unsigned int configSkipIntro; +//extern unsigned int configSkipIntro; void basic_update(s16 *arg); @@ -1217,7 +1217,7 @@ s32 init_level(void) { if (gMarioState->action != ACT_UNINITIALIZED) { if (save_file_exists(gCurrSaveFileNum - 1)) { set_mario_action(gMarioState, ACT_IDLE, 0); - } else if (gCLIOpts.SkipIntro == 0 && configSkipIntro == 0) { + } else if (gCLIOpts.SkipIntro == 0) { set_mario_action(gMarioState, ACT_INTRO_CUTSCENE, 0); val4 = 1; } diff --git a/src/game/options_menu.c b/src/game/options_menu.c new file mode 100644 index 00000000..9e2b7e86 --- /dev/null +++ b/src/game/options_menu.c @@ -0,0 +1,463 @@ +#ifdef EXT_OPTIONS_MENU + +#include "sm64.h" +#include "include/text_strings.h" +#include "engine/math_util.h" +#include "audio/external.h" +#include "game/camera.h" +#include "game/level_update.h" +#include "game/print.h" +#include "game/segment2.h" +#include "game/save_file.h" +#include "game/bettercamera.h" +#include "game/mario_misc.h" +#include "game/game_init.h" +#include "game/ingame_menu.h" +#include "game/options_menu.h" +#include "pc/configfile.h" +#include "pc/controller/controller_api.h" + +#include + +u8 optmenu_open = 0; + +static u8 optmenu_binding = 0; +static u8 optmenu_bind_idx = 0; + +// How to add stuff: +// strings: add them to include/text_strings.h.in +// and to menuStr[] / opts*Str[] +// options: add them to the relevant options list +// menus: add a new submenu definition and a new +// option to the optsMain list + +static const u8 toggleStr[][64] = { + { TEXT_OPT_DISABLED }, + { TEXT_OPT_ENABLED }, +}; + +static const u8 menuStr[][64] = { + { TEXT_OPT_HIGHLIGHT }, + { TEXT_OPT_BUTTON1 }, + { TEXT_OPT_BUTTON2 }, + { TEXT_OPT_OPTIONS }, + { TEXT_OPT_CAMERA }, + { TEXT_OPT_CONTROLS }, +}; + +static const u8 optsCameraStr[][64] = { + { TEXT_OPT_CAMX }, + { TEXT_OPT_CAMY }, + { TEXT_OPT_INVERTX }, + { TEXT_OPT_INVERTY }, + { TEXT_OPT_CAMC }, + { TEXT_OPT_CAMP }, + { TEXT_OPT_ANALOGUE }, + { TEXT_OPT_MOUSE }, +}; + +static const u8 bindStr[][64] = { + { TEXT_OPT_UNBOUND }, + { TEXT_OPT_PRESSKEY }, + { TEXT_BIND_A }, + { TEXT_BIND_B }, + { TEXT_BIND_START }, + { TEXT_BIND_L }, + { TEXT_BIND_R }, + { TEXT_BIND_Z }, + { TEXT_BIND_C_UP }, + { TEXT_BIND_C_DOWN }, + { TEXT_BIND_C_LEFT }, + { TEXT_BIND_C_RIGHT }, + { TEXT_BIND_UP }, + { TEXT_BIND_DOWN }, + { TEXT_BIND_LEFT }, + { TEXT_BIND_RIGHT }, +}; + +enum OptType { + OPT_INVALID = 0, + OPT_TOGGLE, + OPT_CHOICE, + OPT_SCROLL, + OPT_SUBMENU, + OPT_BIND, +}; + +struct SubMenu; + +struct Option { + enum OptType type; + const u8 *label; + union { + u32 *uval; + bool *bval; + }; + union { + struct { + const u8 **choices; + u32 numChoices; + }; + struct { + u32 scrMin; + u32 scrMax; + u32 scrStep; + }; + struct SubMenu *nextMenu; + }; +}; + +struct SubMenu { + struct SubMenu *prev; // this is set at runtime to avoid needless complication + const u8 *label; + struct Option *opts; + s32 numOpts; + s32 select; + s32 scroll; +}; + +/* submenu option lists */ + +static struct Option optsCamera[] = { + { .type = OPT_TOGGLE, .label = optsCameraStr[6], .bval = &configEnableCamera, }, + { .type = OPT_TOGGLE, .label = optsCameraStr[7], .bval = &configCameraMouse, }, + { .type = OPT_TOGGLE, .label = optsCameraStr[2], .bval = &configCameraInvertX, }, + { .type = OPT_TOGGLE, .label = optsCameraStr[3], .bval = &configCameraInvertY, }, + { .type = OPT_SCROLL, .label = optsCameraStr[0], .uval = &configCameraXSens, .scrMin = 10, .scrMax = 250, .scrStep = 1 }, + { .type = OPT_SCROLL, .label = optsCameraStr[1], .uval = &configCameraYSens, .scrMin = 10, .scrMax = 250, .scrStep = 1 }, + { .type = OPT_SCROLL, .label = optsCameraStr[4], .uval = &configCameraAggr, .scrMin = 0, .scrMax = 100, .scrStep = 1 }, + { .type = OPT_SCROLL, .label = optsCameraStr[5], .uval = &configCameraPan, .scrMin = 0, .scrMax = 100, .scrStep = 1 }, +}; + +static struct Option optsControls[] = { + { .type = OPT_BIND, .label = bindStr[2], .uval = configKeyA, }, + { .type = OPT_BIND, .label = bindStr[3], .uval = configKeyB, }, + { .type = OPT_BIND, .label = bindStr[4], .uval = configKeyStart, }, + { .type = OPT_BIND, .label = bindStr[5], .uval = configKeyL, }, + { .type = OPT_BIND, .label = bindStr[6], .uval = configKeyR, }, + { .type = OPT_BIND, .label = bindStr[7], .uval = configKeyZ, }, + { .type = OPT_BIND, .label = bindStr[8], .uval = configKeyCUp, }, + { .type = OPT_BIND, .label = bindStr[9], .uval = configKeyCDown, }, + { .type = OPT_BIND, .label = bindStr[10], .uval = configKeyCLeft, }, + { .type = OPT_BIND, .label = bindStr[11], .uval = configKeyCRight, }, + { .type = OPT_BIND, .label = bindStr[12], .uval = configKeyStickUp, }, + { .type = OPT_BIND, .label = bindStr[13], .uval = configKeyStickDown, }, + { .type = OPT_BIND, .label = bindStr[14], .uval = configKeyStickLeft, }, + { .type = OPT_BIND, .label = bindStr[15], .uval = configKeyStickRight, }, +}; + +/* submenu definitions */ + +static struct SubMenu menuCamera = { + .label = menuStr[4], + .opts = optsCamera, + .numOpts = sizeof(optsCamera) / sizeof(optsCamera[0]), +}; + +static struct SubMenu menuControls = { + .label = menuStr[5], + .opts = optsControls, + .numOpts = sizeof(optsControls) / sizeof(optsControls[0]), +}; + +/* main options menu definition */ + +static struct Option optsMain[] = { + { .type = OPT_SUBMENU, .label = menuStr[4], .nextMenu = &menuCamera, }, + { .type = OPT_SUBMENU, .label = menuStr[5], .nextMenu = &menuControls, }, +}; + +static struct SubMenu menuMain = { + .label = menuStr[3], + .opts = optsMain, + .numOpts = sizeof(optsMain) / sizeof(optsMain[0]), +}; + +/* implementation */ + +static s32 optmenu_option_timer = 0; +static u8 optmenu_hold_count = 0; + +static struct SubMenu *currentMenu = &menuMain; + +static inline s32 wrap_add(s32 a, const s32 b, const s32 min, const s32 max) { + a += b; + if (a < min) a = max - (min - a) + 1; + else if (a > max) a = min + (a - max) - 1; + return a; +} + +static void uint_to_hex(u32 num, u8 *dst) { + u8 places = 4; + while (places--) { + const u32 digit = num & 0xF; + dst[places] = digit; + num >>= 4; + } + dst[4] = DIALOG_CHAR_TERMINATOR; +} + +//Displays a box. +static void optmenu_draw_box(s16 x1, s16 y1, s16 x2, s16 y2, u8 r, u8 g, u8 b) { + gDPPipeSync(gDisplayListHead++); + gDPSetRenderMode(gDisplayListHead++, G_RM_OPA_SURF, G_RM_OPA_SURF2); + gDPSetCycleType(gDisplayListHead++, G_CYC_FILL); + gDPSetFillColor(gDisplayListHead++, GPACK_RGBA5551(r, g, b, 255)); + gDPFillRectangle(gDisplayListHead++, x1, y1, x2 - 1, y2 - 1); + gDPPipeSync(gDisplayListHead++); + gDPSetCycleType(gDisplayListHead++, G_CYC_1CYCLE); +} + +static void optmenu_draw_text(s16 x, s16 y, const u8 *str, u8 col) { + const u8 textX = get_str_x_pos_from_center(x, (u8*)str, 10.0f); + gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); + print_generic_string(textX+1, y-1, str); + if (col == 0) { + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); + } else { + gDPSetEnvColor(gDisplayListHead++, 255, 32, 32, 255); + } + print_generic_string(textX, y, str); +} + +static void optmenu_draw_opt(const struct Option *opt, s16 x, s16 y, u8 sel) { + u8 buf[32] = { 0 }; + + if (opt->type == OPT_SUBMENU) + y -= 6; + + optmenu_draw_text(x, y, opt->label, sel); + + switch (opt->type) { + case OPT_TOGGLE: + optmenu_draw_text(x, y-13, toggleStr[(int)*opt->bval], sel); + break; + + case OPT_CHOICE: + optmenu_draw_text(x, y-13, opt->choices[*opt->uval], sel); + break; + + case OPT_SCROLL: + int_to_str(*opt->uval, buf); + optmenu_draw_text(x, y-13, buf, sel); + break; + + case OPT_BIND: + x = 112; + for (u8 i = 0; i < MAX_BINDS; ++i, x += 48) { + const u8 white = (sel && (optmenu_bind_idx == i)); + // TODO: button names + if (opt->uval[i] == VK_INVALID) { + if (optmenu_binding && white) + optmenu_draw_text(x, y-13, bindStr[1], 1); + else + optmenu_draw_text(x, y-13, bindStr[0], white); + } else { + uint_to_hex(opt->uval[i], buf); + optmenu_draw_text(x, y-13, buf, white); + } + } + break; + + default: break; + }; +} + +static void optmenu_opt_change(struct Option *opt, s32 val) { + switch (opt->type) { + case OPT_TOGGLE: + *opt->bval = !*opt->bval; + break; + + case OPT_CHOICE: + *opt->uval = wrap_add(*opt->uval, val, 0, opt->numChoices - 1); + break; + + case OPT_SCROLL: + *opt->uval = wrap_add(*opt->uval, val, opt->scrMin, opt->scrMax); + break; + + case OPT_SUBMENU: + opt->nextMenu->prev = currentMenu; + currentMenu = opt->nextMenu; + break; + + case OPT_BIND: + if (val == 0xFF) { + // clear the bind + opt->uval[optmenu_bind_idx] = VK_INVALID; + } else if (val == 0) { + opt->uval[optmenu_bind_idx] = VK_INVALID; + optmenu_binding = 1; + controller_get_raw_key(); // clear the last key, which is probably A + } else { + optmenu_bind_idx = wrap_add(optmenu_bind_idx, val, 0, MAX_BINDS - 1); + } + break; + + default: break; + }; +} + +static inline s16 get_hudstr_centered_x(const s16 sx, const u8 *str) { + const u8 *chr = str; + s16 len = 0; + while (*chr != GLOBAR_CHAR_TERMINATOR) ++chr, ++len; + return sx - len * 6; // stride is 12 +} + +//Options menu +void optmenu_draw(void) { + s16 scroll; + s16 scrollpos; + + const s16 labelX = get_hudstr_centered_x(160, currentMenu->label); + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); + print_hud_lut_string(HUD_LUT_GLOBAL, labelX, 40, currentMenu->label); + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); + + if (currentMenu->numOpts > 4) { + optmenu_draw_box(272, 90, 280, 208, 0x80, 0x80, 0x80); + scrollpos = 54 * ((f32)currentMenu->scroll / (currentMenu->numOpts-4)); + optmenu_draw_box(272, 90+scrollpos, 280, 154+scrollpos, 0xFF, 0xFF, 0xFF); + } + + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, 80, SCREEN_WIDTH, SCREEN_HEIGHT); + for (u8 i = 0; i < currentMenu->numOpts; i++) { + scroll = 140 - 32 * i + currentMenu->scroll * 32; + // FIXME: just start from the first visible option bruh + if (scroll <= 140 && scroll > 32) + optmenu_draw_opt(¤tMenu->opts[i], 160, scroll, (currentMenu->select == i)); + } + + gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); + print_hud_lut_string(HUD_LUT_GLOBAL, 80, 90 + (32 * (currentMenu->select - currentMenu->scroll)), menuStr[0]); + print_hud_lut_string(HUD_LUT_GLOBAL, 224, 90 + (32 * (currentMenu->select - currentMenu->scroll)), menuStr[0]); + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); +} + +//This has been separated for interesting reasons. Don't question it. +void optmenu_draw_prompt(void) { + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + optmenu_draw_text(278, 212, menuStr[1 + optmenu_open], 0); + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); +} + +void optmenu_toggle(void) { + if (optmenu_open == 0) { + #ifndef nosound + play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); + #endif + currentMenu = &menuMain; + optmenu_open = 1; + } else { + #ifndef nosound + play_sound(SOUND_MENU_MARIO_CASTLE_WARP2, gDefaultSoundArgs); + #endif + optmenu_open = 0; + newcam_init_settings(); // load bettercam settings from config vars + controller_reconfigure(); // rebind using new config values + configfile_save(CONFIG_FILE); + } +} + +void optmenu_check_buttons(void) { + if (optmenu_binding) { + u32 key = controller_get_raw_key(); + if (key != VK_INVALID) { + #ifndef nosound + play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); + #endif + currentMenu->opts[currentMenu->select].uval[optmenu_bind_idx] = key; + optmenu_binding = 0; + optmenu_option_timer = 12; + } + return; + } + + if (gPlayer1Controller->buttonPressed & R_TRIG) + optmenu_toggle(); + + if (!optmenu_open) return; + + u8 allowInput = 0; + + optmenu_option_timer--; + if (optmenu_option_timer <= 0) { + if (optmenu_hold_count == 0) { + optmenu_hold_count++; + optmenu_option_timer = 10; + } else { + optmenu_option_timer = 3; + } + allowInput = 1; + } + + if (ABS(gPlayer1Controller->stickY) > 60) { + if (allowInput) { + #ifndef nosound + play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); + #endif + + if (gPlayer1Controller->stickY >= 60) { + currentMenu->select--; + if (currentMenu->select < 0) + currentMenu->select = currentMenu->numOpts-1; + } else { + currentMenu->select++; + if (currentMenu->select >= currentMenu->numOpts) + currentMenu->select = 0; + } + + if (currentMenu->select < currentMenu->scroll) + currentMenu->scroll = currentMenu->select; + else if (currentMenu->select > currentMenu->scroll + 3) + currentMenu->scroll = currentMenu->select - 3; + } + } else if (ABS(gPlayer1Controller->stickX) > 60) { + if (allowInput) { + #ifndef nosound + play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); + #endif + if (gPlayer1Controller->stickX >= 60) + optmenu_opt_change(¤tMenu->opts[currentMenu->select], 1); + else + optmenu_opt_change(¤tMenu->opts[currentMenu->select], -1); + } + } else if (gPlayer1Controller->buttonPressed & A_BUTTON) { + if (allowInput) { + #ifndef nosound + play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); + #endif + optmenu_opt_change(¤tMenu->opts[currentMenu->select], 0); + } + } else if (gPlayer1Controller->buttonPressed & B_BUTTON) { + if (allowInput) { + if (currentMenu->prev) { + #ifndef nosound + play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); + #endif + currentMenu = currentMenu->prev; + } else { + // can't go back, exit the menu altogether + optmenu_toggle(); + } + } + } else if (gPlayer1Controller->buttonPressed & Z_TRIG) { + // HACK: clear binds with Z + if (allowInput && currentMenu->opts[currentMenu->select].type == OPT_BIND) + optmenu_opt_change(¤tMenu->opts[currentMenu->select], 0xFF); + } else if (gPlayer1Controller->buttonPressed & START_BUTTON) { + if (allowInput) optmenu_toggle(); + } else { + optmenu_hold_count = 0; + optmenu_option_timer = 0; + } +} + +#endif // EXT_OPTIONS_MENU diff --git a/src/game/options_menu.h b/src/game/options_menu.h new file mode 100644 index 00000000..4828ae9d --- /dev/null +++ b/src/game/options_menu.h @@ -0,0 +1,11 @@ +#ifndef OPTIONS_MENU_H +#define OPTIONS_MENU_H + +void optmenu_toggle(void); +void optmenu_draw(void); +void optmenu_draw_prompt(void); +void optmenu_check_buttons(void); + +extern u8 optmenu_open; + +#endif // OPTIONS_MENU_H diff --git a/src/pc/configfile.c b/src/pc/configfile.c index 6b1e83b0..cee4dfdb 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -7,6 +7,7 @@ #include #include "configfile.h" +#include "controller/controller_api.h" #define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0])) @@ -14,6 +15,7 @@ enum ConfigOptionType { CONFIG_TYPE_BOOL, CONFIG_TYPE_UINT, CONFIG_TYPE_FLOAT, + CONFIG_TYPE_BIND, }; struct ConfigOption { @@ -29,35 +31,24 @@ struct ConfigOption { /* *Config options and default values */ -bool configFullscreen = false; -// Keyboard mappings (scancode values) -unsigned int configKeyA = 0x26; -unsigned int configKeyB = 0x33; -unsigned int configKeyStart = 0x39; -unsigned int configKeyL = 0x34; -unsigned int configKeyR = 0x36; -unsigned int configKeyZ = 0x25; -unsigned int configKeyCUp = 0x148; -unsigned int configKeyCDown = 0x150; -unsigned int configKeyCLeft = 0x14B; -unsigned int configKeyCRight = 0x14D; -unsigned int configKeyStickUp = 0x11; -unsigned int configKeyStickDown = 0x1F; -unsigned int configKeyStickLeft = 0x1E; -unsigned int configKeyStickRight = 0x20; -// Gamepad mappings (SDL_GameControllerButton values) -unsigned int configJoyA = 0; -unsigned int configJoyB = 2; -unsigned int configJoyStart = 6; -unsigned int configJoyL = 7; -unsigned int configJoyR = 10; -unsigned int configJoyZ = 9; -// Mouse button mappings (0 for none, 1 for left, 2 for middle, 3 for right) -unsigned int configMouseA = 3; -unsigned int configMouseB = 1; -unsigned int configMouseL = 4; -unsigned int configMouseR = 5; -unsigned int configMouseZ = 2; + +bool configFullscreen = false; +// Keyboard mappings (VK_ values, by default keyboard/gamepad/mouse) +unsigned int configKeyA[MAX_BINDS] = { 0x0026, 0x1000, 0x1103 }; +unsigned int configKeyB[MAX_BINDS] = { 0x0033, 0x1002, 0x1101 }; +unsigned int configKeyStart[MAX_BINDS] = { 0x0039, 0x1006, VK_INVALID }; +unsigned int configKeyL[MAX_BINDS] = { 0x0034, 0x1007, 0x1104 }; +unsigned int configKeyR[MAX_BINDS] = { 0x0036, 0x100A, 0x1105 }; +unsigned int configKeyZ[MAX_BINDS] = { 0x0025, 0x1009, 0x1102 }; +unsigned int configKeyCUp[MAX_BINDS] = { 0x0148, VK_INVALID, VK_INVALID }; +unsigned int configKeyCDown[MAX_BINDS] = { 0x0150, VK_INVALID, VK_INVALID }; +unsigned int configKeyCLeft[MAX_BINDS] = { 0x014B, VK_INVALID, VK_INVALID }; +unsigned int configKeyCRight[MAX_BINDS] = { 0x014D, VK_INVALID, VK_INVALID }; +unsigned int configKeyStickUp[MAX_BINDS] = { 0x0011, VK_INVALID, VK_INVALID }; +unsigned int configKeyStickDown[MAX_BINDS] = { 0x001F, VK_INVALID, VK_INVALID }; +unsigned int configKeyStickLeft[MAX_BINDS] = { 0x001E, VK_INVALID, VK_INVALID }; +unsigned int configKeyStickRight[MAX_BINDS] = { 0x0020, VK_INVALID, VK_INVALID }; + #ifdef BETTERCAMERA // BetterCamera settings unsigned int configCameraXSens = 50; @@ -69,35 +60,23 @@ bool configCameraInvertY = false; bool configEnableCamera = false; bool configCameraMouse = false; #endif -unsigned int configSkipIntro = 0; static const struct ConfigOption options[] = { {.name = "fullscreen", .type = CONFIG_TYPE_BOOL, .boolValue = &configFullscreen}, - {.name = "key_a", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyA}, - {.name = "key_b", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyB}, - {.name = "key_start", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStart}, - {.name = "key_l", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyL}, - {.name = "key_r", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyR}, - {.name = "key_z", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyZ}, - {.name = "key_cup", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCUp}, - {.name = "key_cdown", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCDown}, - {.name = "key_cleft", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCLeft}, - {.name = "key_cright", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCRight}, - {.name = "key_stickup", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickUp}, - {.name = "key_stickdown", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickDown}, - {.name = "key_stickleft", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickLeft}, - {.name = "key_stickright", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickRight}, - {.name = "joy_a", .type = CONFIG_TYPE_UINT, .uintValue = &configJoyA}, - {.name = "joy_b", .type = CONFIG_TYPE_UINT, .uintValue = &configJoyB}, - {.name = "joy_start", .type = CONFIG_TYPE_UINT, .uintValue = &configJoyStart}, - {.name = "joy_l", .type = CONFIG_TYPE_UINT, .uintValue = &configJoyL}, - {.name = "joy_r", .type = CONFIG_TYPE_UINT, .uintValue = &configJoyR}, - {.name = "joy_z", .type = CONFIG_TYPE_UINT, .uintValue = &configJoyZ}, - {.name = "mouse_a", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseA}, - {.name = "mouse_b", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseB}, - {.name = "mouse_l", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseL}, - {.name = "mouse_r", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseR}, - {.name = "mouse_z", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseZ}, + {.name = "key_a", .type = CONFIG_TYPE_BIND, .uintValue = configKeyA}, + {.name = "key_b", .type = CONFIG_TYPE_BIND, .uintValue = configKeyB}, + {.name = "key_start", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStart}, + {.name = "key_l", .type = CONFIG_TYPE_BIND, .uintValue = configKeyL}, + {.name = "key_r", .type = CONFIG_TYPE_BIND, .uintValue = configKeyR}, + {.name = "key_z", .type = CONFIG_TYPE_BIND, .uintValue = configKeyZ}, + {.name = "key_cup", .type = CONFIG_TYPE_BIND, .uintValue = configKeyCUp}, + {.name = "key_cdown", .type = CONFIG_TYPE_BIND, .uintValue = configKeyCDown}, + {.name = "key_cleft", .type = CONFIG_TYPE_BIND, .uintValue = configKeyCLeft}, + {.name = "key_cright", .type = CONFIG_TYPE_BIND, .uintValue = configKeyCRight}, + {.name = "key_stickup", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickUp}, + {.name = "key_stickdown", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickDown}, + {.name = "key_stickleft", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickLeft}, + {.name = "key_stickright", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickRight}, #ifdef BETTERCAMERA {.name = "bettercam_enable", .type = CONFIG_TYPE_BOOL, .boolValue = &configEnableCamera}, {.name = "bettercam_mouse_look", .type = CONFIG_TYPE_BOOL, .boolValue = &configCameraMouse}, @@ -108,7 +87,7 @@ static const struct ConfigOption options[] = { {.name = "bettercam_aggression", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraAggr}, {.name = "bettercam_pan_level", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraPan}, #endif - {.name = "skip_intro", .type = CONFIG_TYPE_UINT, .uintValue = &configSkipIntro}, + //{.name = "skip_intro", .type = CONFIG_TYPE_UINT, .uintValue = &configSkipIntro}, // Add this back! }; // Reads an entire line from a file (excluding the newline character) and returns an allocated string @@ -209,9 +188,13 @@ void configfile_load(const char *filename) { while (isspace(*p)) p++; + + if (!*p || *p == '#') // comment or empty line + continue; + numTokens = tokenize_string(p, 2, tokens); if (numTokens != 0) { - if (numTokens == 2) { + if (numTokens >= 2) { const struct ConfigOption *option = NULL; for (unsigned int i = 0; i < ARRAY_LEN(options); i++) { @@ -233,6 +216,10 @@ void configfile_load(const char *filename) { case CONFIG_TYPE_UINT: sscanf(tokens[1], "%u", option->uintValue); break; + case CONFIG_TYPE_BIND: + for (int i = 0; i < MAX_BINDS && i < numTokens - 1; ++i) + sscanf(tokens[i + 1], "%x", option->uintValue + i); + break; case CONFIG_TYPE_FLOAT: sscanf(tokens[1], "%f", option->floatValue); break; @@ -275,6 +262,12 @@ void configfile_save(const char *filename) { case CONFIG_TYPE_FLOAT: fprintf(file, "%s %f\n", option->name, *option->floatValue); break; + case CONFIG_TYPE_BIND: + fprintf(file, "%s ", option->name); + for (int i = 0; i < MAX_BINDS; ++i) + fprintf(file, "%04x ", option->uintValue[i]); + fprintf(file, "\n"); + break; default: assert(0); // unknown type } diff --git a/src/pc/configfile.h b/src/pc/configfile.h index a2044dd7..c53d6991 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -4,34 +4,23 @@ #include #define CONFIG_FILE "sm64config.txt" +#define MAX_BINDS 3 extern bool configFullscreen; -extern unsigned int configKeyA; -extern unsigned int configKeyB; -extern unsigned int configKeyStart; -extern unsigned int configKeyL; -extern unsigned int configKeyR; -extern unsigned int configKeyZ; -extern unsigned int configKeyCUp; -extern unsigned int configKeyCDown; -extern unsigned int configKeyCLeft; -extern unsigned int configKeyCRight; -extern unsigned int configKeyStickUp; -extern unsigned int configKeyStickDown; -extern unsigned int configKeyStickLeft; -extern unsigned int configKeyStickRight; -extern unsigned int configJoyA; -extern unsigned int configJoyB; -extern unsigned int configJoyStart; -extern unsigned int configJoyL; -extern unsigned int configJoyR; -extern unsigned int configJoyZ; -extern unsigned int configMouseA; -extern unsigned int configMouseB; -extern unsigned int configMouseStart; -extern unsigned int configMouseL; -extern unsigned int configMouseR; -extern unsigned int configMouseZ; +extern unsigned int configKeyA[]; +extern unsigned int configKeyB[]; +extern unsigned int configKeyStart[]; +extern unsigned int configKeyL[]; +extern unsigned int configKeyR[]; +extern unsigned int configKeyZ[]; +extern unsigned int configKeyCUp[]; +extern unsigned int configKeyCDown[]; +extern unsigned int configKeyCLeft[]; +extern unsigned int configKeyCRight[]; +extern unsigned int configKeyStickUp[]; +extern unsigned int configKeyStickDown[]; +extern unsigned int configKeyStickLeft[]; +extern unsigned int configKeyStickRight[]; #ifdef BETTERCAMERA extern unsigned int configCameraXSens; extern unsigned int configCameraYSens; diff --git a/src/pc/controller/controller_api.h b/src/pc/controller/controller_api.h index e040551a..fdd6e503 100644 --- a/src/pc/controller/controller_api.h +++ b/src/pc/controller/controller_api.h @@ -2,15 +2,21 @@ #define CONTROLLER_API #define DEADZONE 4960 - -// Analog camera movement by Pathétique (github.com/vrmiguel), y0shin and Mors -// Contribute or communicate bugs at github.com/vrmiguel/sm64-analog-camera +#define VK_INVALID 0xFFFF +#define VK_SIZE 0x1000 #include struct ControllerAPI { - void (*init)(void); - void (*read)(OSContPad *pad); + const u32 vkbase; // base number in the virtual keyspace (e.g. keyboard is 0x0000-0x1000) + void (*init)(void); // call once, also calls reconfig() + void (*read)(OSContPad *pad); // read controller and update N64 pad values + u32 (*rawkey)(void); // returns last pressed virtual key or VK_INVALID if none + void (*reconfig)(void); // (optional) call when bindings have changed }; +// used for binding keys +u32 controller_get_raw_key(void); +void controller_reconfigure(void); + #endif diff --git a/src/pc/controller/controller_entry_point.c b/src/pc/controller/controller_entry_point.c index 9f3630a0..536b0798 100644 --- a/src/pc/controller/controller_entry_point.c +++ b/src/pc/controller/controller_entry_point.c @@ -56,3 +56,18 @@ void osContGetReadData(OSContPad *pad) { controller_implementations[i]->read(pad); } } + +u32 controller_get_raw_key(void) { + for (size_t i = 0; i < sizeof(controller_implementations) / sizeof(struct ControllerAPI *); i++) { + u32 vk = controller_implementations[i]->rawkey(); + if (vk != VK_INVALID) return vk + controller_implementations[i]->vkbase; + } + return VK_INVALID; +} + +void controller_reconfigure(void) { + for (size_t i = 0; i < sizeof(controller_implementations) / sizeof(struct ControllerAPI *); i++) { + if (controller_implementations[i]->reconfig) + controller_implementations[i]->reconfig(); + } +} diff --git a/src/pc/controller/controller_keyboard.c b/src/pc/controller/controller_keyboard.c index 87eaee0d..c9421a24 100644 --- a/src/pc/controller/controller_keyboard.c +++ b/src/pc/controller/controller_keyboard.c @@ -8,14 +8,19 @@ #endif #include "../configfile.h" +#include "controller_keyboard.h" static int keyboard_buttons_down; -static int keyboard_mapping[14][2]; +#define MAX_KEYBINDS 64 +static int keyboard_mapping[MAX_KEYBINDS][2]; +static int num_keybinds = 0; + +static u32 keyboard_lastkey = VK_INVALID; static int keyboard_map_scancode(int scancode) { int ret = 0; - for (size_t i = 0; i < sizeof(keyboard_mapping) / sizeof(keyboard_mapping[0]); i++) { + for (int i = 0; i < num_keybinds; i++) { if (keyboard_mapping[i][0] == scancode) { ret |= keyboard_mapping[i][1]; } @@ -26,12 +31,15 @@ static int keyboard_map_scancode(int scancode) { bool keyboard_on_key_down(int scancode) { int mapped = keyboard_map_scancode(scancode); keyboard_buttons_down |= mapped; + keyboard_lastkey = scancode; return mapped != 0; } bool keyboard_on_key_up(int scancode) { int mapped = keyboard_map_scancode(scancode); keyboard_buttons_down &= ~mapped; + if (keyboard_lastkey == (u32) scancode) + keyboard_lastkey = VK_INVALID; return mapped != 0; } @@ -39,28 +47,38 @@ void keyboard_on_all_keys_up(void) { keyboard_buttons_down = 0; } -static void set_keyboard_mapping(int index, int mask, int scancode) { - keyboard_mapping[index][0] = scancode; - keyboard_mapping[index][1] = mask; +static void keyboard_add_binds(int mask, unsigned int *scancode) { + for (int i = 0; i < MAX_BINDS && num_keybinds < MAX_KEYBINDS; ++i) { + if (scancode[i] < VK_BASE_KEYBOARD + VK_SIZE) { + keyboard_mapping[num_keybinds][0] = scancode[i]; + keyboard_mapping[num_keybinds][1] = mask; + num_keybinds++; + } + } +} + +static void keyboard_bindkeys(void) { + bzero(keyboard_mapping, sizeof(keyboard_mapping)); + num_keybinds = 0; + + keyboard_add_binds(0x80000, configKeyStickUp); + keyboard_add_binds(0x10000, configKeyStickLeft); + keyboard_add_binds(0x40000, configKeyStickDown); + keyboard_add_binds(0x20000, configKeyStickRight); + keyboard_add_binds(A_BUTTON, configKeyA); + keyboard_add_binds(B_BUTTON, configKeyB); + keyboard_add_binds(Z_TRIG, configKeyZ); + keyboard_add_binds(U_CBUTTONS, configKeyCUp); + keyboard_add_binds(L_CBUTTONS, configKeyCLeft); + keyboard_add_binds(D_CBUTTONS, configKeyCDown); + keyboard_add_binds(R_CBUTTONS, configKeyCRight); + keyboard_add_binds(L_TRIG, configKeyL); + keyboard_add_binds(R_TRIG, configKeyR); + keyboard_add_binds(START_BUTTON, configKeyStart); } static void keyboard_init(void) { - int i = 0; - - set_keyboard_mapping(i++, 0x80000, configKeyStickUp); - set_keyboard_mapping(i++, 0x10000, configKeyStickLeft); - set_keyboard_mapping(i++, 0x40000, configKeyStickDown); - set_keyboard_mapping(i++, 0x20000, configKeyStickRight); - set_keyboard_mapping(i++, A_BUTTON, configKeyA); - set_keyboard_mapping(i++, B_BUTTON, configKeyB); - set_keyboard_mapping(i++, Z_TRIG, configKeyZ); - set_keyboard_mapping(i++, U_CBUTTONS, configKeyCUp); - set_keyboard_mapping(i++, L_CBUTTONS, configKeyCLeft); - set_keyboard_mapping(i++, D_CBUTTONS, configKeyCDown); - set_keyboard_mapping(i++, R_CBUTTONS, configKeyCRight); - set_keyboard_mapping(i++, L_TRIG, configKeyL); - set_keyboard_mapping(i++, R_TRIG, configKeyR); - set_keyboard_mapping(i++, START_BUTTON, configKeyStart); + keyboard_bindkeys(); #ifdef TARGET_WEB controller_emscripten_keyboard_init(); @@ -83,7 +101,16 @@ static void keyboard_read(OSContPad *pad) { } } +static u32 keyboard_rawkey(void) { + const u32 ret = keyboard_lastkey; + keyboard_lastkey = VK_INVALID; + return ret; +} + struct ControllerAPI controller_keyboard = { + VK_BASE_KEYBOARD, keyboard_init, - keyboard_read + keyboard_read, + keyboard_rawkey, + keyboard_bindkeys, }; diff --git a/src/pc/controller/controller_keyboard.h b/src/pc/controller/controller_keyboard.h index e2c08586..028d2e85 100644 --- a/src/pc/controller/controller_keyboard.h +++ b/src/pc/controller/controller_keyboard.h @@ -4,6 +4,8 @@ #include #include "controller_api.h" +# define VK_BASE_KEYBOARD 0x0000 + #ifdef __cplusplus extern "C" { #endif diff --git a/src/pc/controller/controller_recorded_tas.c b/src/pc/controller/controller_recorded_tas.c index 4f5c1bb0..f05c0558 100644 --- a/src/pc/controller/controller_recorded_tas.c +++ b/src/pc/controller/controller_recorded_tas.c @@ -23,7 +23,14 @@ static void tas_read(OSContPad *pad) { } } +static u32 tas_rawkey(void) { + return VK_INVALID; +} + struct ControllerAPI controller_recorded_tas = { + VK_INVALID, tas_init, - tas_read + tas_read, + tas_rawkey, + NULL, // no rebinding }; diff --git a/src/pc/controller/controller_sdl.c b/src/pc/controller/controller_sdl.c index 48120231..171a31f4 100644 --- a/src/pc/controller/controller_sdl.c +++ b/src/pc/controller/controller_sdl.c @@ -11,9 +11,15 @@ #include #include "controller_api.h" - +#include "controller_sdl.h" #include "../configfile.h" +// mouse buttons are also in the controller namespace (why), just offset 0x100 +#define VK_OFS_SDL_MOUSE 0x0100 +#define VK_BASE_SDL_MOUSE (VK_BASE_SDL_GAMEPAD + VK_OFS_SDL_MOUSE) +#define MAX_JOYBINDS 32 +#define MAX_MOUSEBUTTONS 8 // arbitrary + extern int16_t rightx; extern int16_t righty; @@ -27,6 +33,51 @@ extern u8 newcam_mouse; static bool init_ok; static SDL_GameController *sdl_cntrl; + +static u32 num_joy_binds = 0; +static u32 num_mouse_binds = 0; +static u32 joy_binds[MAX_JOYBINDS][2]; +static u32 mouse_binds[MAX_JOYBINDS][2]; + +static bool joy_buttons[SDL_CONTROLLER_BUTTON_MAX ] = { false }; +static u32 mouse_buttons = 0; +static u32 last_mouse = VK_INVALID; +static u32 last_joybutton = VK_INVALID; + +static inline void controller_add_binds(const u32 mask, const u32 *btns) { + for (u32 i = 0; i < MAX_BINDS; ++i) { + if (btns[i] >= VK_BASE_SDL_GAMEPAD && btns[i] <= VK_BASE_SDL_GAMEPAD + VK_SIZE) { + if (btns[i] >= VK_BASE_SDL_MOUSE && num_joy_binds < MAX_JOYBINDS) { + mouse_binds[num_mouse_binds][0] = btns[i] - VK_BASE_SDL_MOUSE; + mouse_binds[num_mouse_binds][1] = mask; + ++num_mouse_binds; + } else if (num_mouse_binds < MAX_JOYBINDS) { + joy_binds[num_joy_binds][0] = btns[i] - VK_BASE_SDL_GAMEPAD; + joy_binds[num_joy_binds][1] = mask; + ++num_joy_binds; + } + } + } +} + +static void controller_sdl_bind(void) { + bzero(joy_binds, sizeof(joy_binds)); + bzero(mouse_binds, sizeof(mouse_binds)); + num_joy_binds = 0; + num_mouse_binds = 0; + + controller_add_binds(A_BUTTON, configKeyA); + controller_add_binds(B_BUTTON, configKeyB); + controller_add_binds(Z_TRIG, configKeyZ); + controller_add_binds(U_CBUTTONS, configKeyCUp); + controller_add_binds(L_CBUTTONS, configKeyCLeft); + controller_add_binds(D_CBUTTONS, configKeyCDown); + controller_add_binds(R_CBUTTONS, configKeyCRight); + controller_add_binds(L_TRIG, configKeyL); + controller_add_binds(R_TRIG, configKeyR); + controller_add_binds(START_BUTTON, configKeyStart); +} + static void controller_sdl_init(void) { if (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS) != 0) { fprintf(stderr, "SDL init error: %s\n", SDL_GetError()); @@ -39,6 +90,8 @@ static void controller_sdl_init(void) { SDL_GetRelativeMouseState(&mouse_x, &mouse_y); #endif + controller_sdl_bind(); + init_ok = true; } @@ -53,13 +106,16 @@ static void controller_sdl_read(OSContPad *pad) { else SDL_SetRelativeMouseMode(SDL_FALSE); - const u32 mbuttons = SDL_GetRelativeMouseState(&mouse_x, &mouse_y); + u32 mouse = SDL_GetRelativeMouseState(&mouse_x, &mouse_y); + + for (u32 i = 0; i < num_mouse_binds; ++i) + if (mouse & SDL_BUTTON(mouse_binds[i][0])) + pad->button |= mouse_binds[i][1]; + + // remember buttons that changed from 0 to 1 + last_mouse = (mouse_buttons ^ mouse) & mouse; + mouse_buttons = mouse; - if (configMouseA && (mbuttons & SDL_BUTTON(configMouseA))) pad->button |= A_BUTTON; - if (configMouseB && (mbuttons & SDL_BUTTON(configMouseB))) pad->button |= B_BUTTON; - if (configMouseL && (mbuttons & SDL_BUTTON(configMouseL))) pad->button |= L_TRIG; - if (configMouseR && (mbuttons & SDL_BUTTON(configMouseR))) pad->button |= R_TRIG; - if (configMouseZ && (mbuttons & SDL_BUTTON(configMouseZ))) pad->button |= Z_TRIG; #endif SDL_GameControllerUpdate(); @@ -82,12 +138,16 @@ static void controller_sdl_read(OSContPad *pad) { } } - if (SDL_GameControllerGetButton(sdl_cntrl, configJoyStart)) pad->button |= START_BUTTON; - if (SDL_GameControllerGetButton(sdl_cntrl, configJoyZ)) pad->button |= Z_TRIG; - if (SDL_GameControllerGetButton(sdl_cntrl, configJoyL)) pad->button |= L_TRIG; - if (SDL_GameControllerGetButton(sdl_cntrl, configJoyR)) pad->button |= R_TRIG; - if (SDL_GameControllerGetButton(sdl_cntrl, configJoyA)) pad->button |= A_BUTTON; - if (SDL_GameControllerGetButton(sdl_cntrl, configJoyB)) pad->button |= B_BUTTON; + for (u32 i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) { + const bool new = SDL_GameControllerGetButton(sdl_cntrl, i); + const bool pressed = !joy_buttons[i] && new; + joy_buttons[i] = new; + if (pressed) last_joybutton = i; + } + + for (u32 i = 0; i < num_joy_binds; ++i) + if (joy_buttons[joy_binds[i][0]]) + pad->button |= joy_binds[i][1]; int16_t leftx = SDL_GameControllerGetAxis(sdl_cntrl, SDL_CONTROLLER_AXIS_LEFTX); int16_t lefty = SDL_GameControllerGetAxis(sdl_cntrl, SDL_CONTROLLER_AXIS_LEFTY); @@ -127,7 +187,27 @@ static void controller_sdl_read(OSContPad *pad) { } } +static u32 controller_sdl_rawkey(void) { + if (last_joybutton != VK_INVALID) { + const u32 ret = last_joybutton; + last_joybutton = VK_INVALID; + return ret; + } + + for (int i = 0; i < MAX_MOUSEBUTTONS; ++i) { + if (last_mouse & SDL_BUTTON(i)) { + const u32 ret = VK_OFS_SDL_MOUSE + i; + last_mouse = 0; + return ret; + } + } + return VK_INVALID; +} + struct ControllerAPI controller_sdl = { + VK_BASE_SDL_GAMEPAD, controller_sdl_init, - controller_sdl_read + controller_sdl_read, + controller_sdl_rawkey, + controller_sdl_bind, }; diff --git a/src/pc/controller/controller_sdl.h b/src/pc/controller/controller_sdl.h index 02aec8d9..bbe8a62c 100644 --- a/src/pc/controller/controller_sdl.h +++ b/src/pc/controller/controller_sdl.h @@ -3,6 +3,8 @@ #include "controller_api.h" +#define VK_BASE_SDL_GAMEPAD 0x1000 + extern struct ControllerAPI controller_sdl; #endif