diff --git a/README.md b/README.md index 1ba69773..0e28a0b2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Please contribute **first** to the [nightly branch](https://github.com/sm64pc/sm * An option to disable drawing distances. (Activate with `make NODRAWINGDISTANCE=1`.) * In-game control binding, currently available on the `testing` branch. * Skip introductory Peach & Lakitu cutscenes with the `--skip-intro` CLI option + * Cheats menu in Options. (Activate with `--cheats`) Please note that if a cheat asks you to press "L" it's referring to the N64 button. Check your bindings and make sure you have the "L" button mapped to a button in your controller. ## Building For building instructions, please refer to the [wiki](https://github.com/sm64pc/sm64pc/wiki). diff --git a/README_es_ES.md b/README_es_ES.md index 7426486c..44a34a1c 100644 --- a/README_es_ES.md +++ b/README_es_ES.md @@ -11,8 +11,9 @@ Ejecuta `./extract_assets.py --clean && make clean` o `make distclean` para borr * Soporte nativo para mandos XInput. En Linux, se ha confirmado que el DualShock 4 funciona sin más. * Cámara analógica y cámara controlada con el ratón. (Se activa con `make BETTERCAMERA=1`.) * Opción para desactivar el límite de distancia de renderizado. (Se activa con `make NODRAWINGDISTANCE=1`.) - * Configurar los controles desde el juego, actualmente solo en la rama `testing`. - * Posibilidad de saltarte la intro con la opción de línea de comandos `--skip-intro`, actualmente solo en las ramas `testing` y `skip-intro`. + * Configurar los controles desde el juego. + * Posibilidad de saltarte la intro con la opción de línea de comandos `--skip-intro` + * Menú de trucos (_cheats_) en _options_. (Se activa con la opción de línea de comandos `--cheats`) Ten en cuenta que si un cheat te pide pulsar el botón "L", se refiere al botón de N64, el cual tendrá que estar asignado a un botón de tu mando. Ve a los ajustes de control y asegúrate de que tienes "L" mapeado a un botón de tu mando. ## Compilar en Windows **No intentes compilar ejecutables para Windows bajo Linux usando `WINDOWS_BUILD=1`. No va a funcionar. Sigue la guía.** diff --git a/bin/segment2.c b/bin/segment2.c index d7e807fd..5d5398aa 100644 --- a/bin/segment2.c +++ b/bin/segment2.c @@ -2107,7 +2107,7 @@ const Gfx dl_hud_img_load_tex_block[] = { gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 0, 0, G_TX_LOADTILE, 0, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD), gsDPLoadSync(), gsDPLoadBlock(G_TX_LOADTILE, 0, 0, 16 * 16 - 1, CALC_DXT(16, G_IM_SIZ_16b_BYTES)), - gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 4, 0, G_TX_RENDERTILE, 0, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD), + gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 4, 0, G_TX_RENDERTILE, 0, G_TX_CLAMP, 4, G_TX_NOLOD, G_TX_CLAMP, 4, G_TX_NOLOD), gsDPSetTileSize(0, 0, 0, (16 - 1) << G_TEXTURE_IMAGE_FRAC, (16 - 1) << G_TEXTURE_IMAGE_FRAC), gsSPEndDisplayList(), }; @@ -2144,7 +2144,7 @@ const Gfx dl_rgba16_load_tex_block[] = { gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 0, 0, G_TX_LOADTILE, 0, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD), gsDPLoadSync(), gsDPLoadBlock(G_TX_LOADTILE, 0, 0, 16 * 16 - 1, CALC_DXT(16, G_IM_SIZ_16b_BYTES)), - gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 4, 0, G_TX_RENDERTILE, 0, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD), + gsDPSetTile(G_IM_FMT_RGBA, G_IM_SIZ_16b, 4, 0, G_TX_RENDERTILE, 0, G_TX_CLAMP, 4, G_TX_NOLOD, G_TX_CLAMP, 4, G_TX_NOLOD), gsDPSetTileSize(0, 0, 0, (16 - 1) << G_TEXTURE_IMAGE_FRAC, (16 - 1) << G_TEXTURE_IMAGE_FRAC), gsSPEndDisplayList(), }; diff --git a/include/text_strings.h.in b/include/text_strings.h.in index 50c47785..9b70acf6 100644 --- a/include/text_strings.h.in +++ b/include/text_strings.h.in @@ -27,6 +27,8 @@ #define TEXT_OPT_NEAREST _("Nearest") #define TEXT_OPT_LINEAR _("Linear") #define TEXT_OPT_MVOLUME _("Master Volume") +#define TEXT_OPT_VSYNC _("Vertical Sync") +#define TEXT_OPT_DOUBLE _("Double") #define TEXT_RESET_WINDOW _("Reset Window") #define TEXT_OPT_UNBOUND _("NONE") diff --git a/src/game/options_menu.c b/src/game/options_menu.c index f8c2af3a..ab7da294 100644 --- a/src/game/options_menu.c +++ b/src/game/options_menu.c @@ -72,7 +72,9 @@ static const u8 optsVideoStr[][32] = { { TEXT_OPT_TEXFILTER }, { TEXT_OPT_NEAREST }, { TEXT_OPT_LINEAR }, - { TEXT_RESET_WINDOW } + { TEXT_RESET_WINDOW }, + { TEXT_OPT_VSYNC }, + { TEXT_OPT_DOUBLE }, }; static const u8 optsAudioStr[][32] = { @@ -115,6 +117,12 @@ static const u8 *filterChoices[] = { optsVideoStr[3], }; +static const u8 *vsyncChoices[] = { + toggleStr[0], + toggleStr[1], + optsVideoStr[6], +}; + enum OptType { OPT_INVALID = 0, OPT_TOGGLE, @@ -181,8 +189,12 @@ static void optmenu_act_exit(UNUSED struct Option *self, s32 arg) { if (!arg) game_exit(); // only exit on A press and not directions } -static void optvide_reset_window(UNUSED struct Option *self, s32 arg) { - if (!arg) configWindow.reset = true;; // Restrict reset to A press and not directions +static void optvideo_reset_window(UNUSED struct Option *self, s32 arg) { + if (!arg) { + // Restrict reset to A press and not directions + configWindow.reset = true; + configWindow.settings_changed = true; + } } /* submenu option lists */ @@ -220,8 +232,9 @@ static struct Option optsControls[] = { static struct Option optsVideo[] = { DEF_OPT_TOGGLE( optsVideoStr[0], &configWindow.fullscreen ), + DEF_OPT_CHOICE( optsVideoStr[5], &configWindow.vsync, vsyncChoices ), DEF_OPT_CHOICE( optsVideoStr[1], &configFiltering, filterChoices ), - DEF_OPT_BUTTON( optsVideoStr[4], optvide_reset_window ), + DEF_OPT_BUTTON( optsVideoStr[4], optvideo_reset_window ), }; static struct Option optsAudio[] = { @@ -233,11 +246,11 @@ static struct Option optsCheats[] = { DEF_OPT_TOGGLE( optsCheatsStr[1], &Cheats.MoonJump ), DEF_OPT_TOGGLE( optsCheatsStr[2], &Cheats.GodMode ), DEF_OPT_TOGGLE( optsCheatsStr[3], &Cheats.InfiniteLives ), - DEF_OPT_TOGGLE( optsCheatsStr[4], &Cheats.SuperSpeed), - DEF_OPT_TOGGLE( optsCheatsStr[5], &Cheats.Responsive), - DEF_OPT_TOGGLE( optsCheatsStr[6], &Cheats.ExitAnywhere), - DEF_OPT_TOGGLE( optsCheatsStr[7], &Cheats.HugeMario), - DEF_OPT_TOGGLE( optsCheatsStr[8], &Cheats.TinyMario), + DEF_OPT_TOGGLE( optsCheatsStr[4], &Cheats.SuperSpeed ), + DEF_OPT_TOGGLE( optsCheatsStr[5], &Cheats.Responsive ), + DEF_OPT_TOGGLE( optsCheatsStr[6], &Cheats.ExitAnywhere ), + DEF_OPT_TOGGLE( optsCheatsStr[7], &Cheats.HugeMario ), + DEF_OPT_TOGGLE( optsCheatsStr[8], &Cheats.TinyMario ), }; @@ -249,7 +262,7 @@ static struct SubMenu menuCamera = DEF_SUBMENU( menuStr[4], optsCamera ); static struct SubMenu menuControls = DEF_SUBMENU( menuStr[5], optsControls ); static struct SubMenu menuVideo = DEF_SUBMENU( menuStr[6], optsVideo ); static struct SubMenu menuAudio = DEF_SUBMENU( menuStr[7], optsAudio ); -static struct SubMenu menuCheats = DEF_SUBMENU( menuStr[9], optsCheats ); +static struct SubMenu menuCheats = DEF_SUBMENU( menuStr[9], optsCheats ); /* main options menu definition */ @@ -261,8 +274,8 @@ static struct Option optsMain[] = { DEF_OPT_SUBMENU( menuStr[6], &menuVideo ), DEF_OPT_SUBMENU( menuStr[7], &menuAudio ), DEF_OPT_BUTTON ( menuStr[8], optmenu_act_exit ), - DEF_OPT_SUBMENU( menuStr[9], &menuCheats ), - + // NOTE: always keep cheats the last option here because of the half-assed way I toggle them + DEF_OPT_SUBMENU( menuStr[9], &menuCheats ) }; static struct SubMenu menuMain = DEF_SUBMENU( menuStr[3], optsMain ); @@ -452,6 +465,17 @@ void optmenu_toggle(void) { #ifndef nosound play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); #endif + + // HACK: hide the last option in main if cheats are disabled + menuMain.numOpts = sizeof(optsMain) / sizeof(optsMain[0]); + if (!Cheats.EnableCheats) { + menuMain.numOpts--; + if (menuMain.select >= menuMain.numOpts) { + menuMain.select = 0; // don't bother + menuMain.scroll = 0; + } + } + currentMenu = &menuMain; optmenu_open = 1; } else { diff --git a/src/pc/cheats.c b/src/pc/cheats.c index 5663d777..adcc4efe 100644 --- a/src/pc/cheats.c +++ b/src/pc/cheats.c @@ -1,2 +1,3 @@ #include "cheats.h" + struct CheatList Cheats; diff --git a/src/pc/cheats.h b/src/pc/cheats.h index 4941a465..eaf71ab4 100644 --- a/src/pc/cheats.h +++ b/src/pc/cheats.h @@ -1,9 +1,11 @@ +#ifndef _CHEATS_H +#define _CHEATS_H + #include -struct CheatList -{ +struct CheatList { bool EnableCheats; - bool MoonJump; + bool MoonJump; bool GodMode; bool InfiniteLives; bool SuperSpeed; @@ -14,3 +16,5 @@ struct CheatList }; extern struct CheatList Cheats; + +#endif // _CHEATS_H diff --git a/src/pc/cliopts.c b/src/pc/cliopts.c index ee9bc13e..727de38b 100644 --- a/src/pc/cliopts.c +++ b/src/pc/cliopts.c @@ -1,4 +1,8 @@ #include "cliopts.h" +#include "configfile.h" +#include "cheats.h" +#include "pc_main.h" + #include #include #include @@ -6,53 +10,48 @@ struct PCCLIOptions gCLIOpts; -void parse_cli_opts(int argc, char* argv[]) -{ - // Initialize options with false values. - gCLIOpts.SkipIntro = 0; - gCLIOpts.FullScreen = 0; - gCLIOpts.ConfigFile = malloc(31); - strncpy(gCLIOpts.ConfigFile, "sm64config.txt", strlen("sm64config.txt")); - gCLIOpts.ConfigFile[strlen("sm64config.txt")] = '\0'; - - // Scan arguments for options - if (argc > 1) - { - int i; - for (i = 1; i < argc; i++) - { - if (strcmp(argv[i], "--skip-intro") == 0) // Skip Peach Intro - gCLIOpts.SkipIntro = 1; - - if (strcmp(argv[i], "--fullscreen") == 0) // Open game in fullscreen - gCLIOpts.FullScreen = 1; - - if (strcmp(argv[i], "--windowed") == 0) // Open game in windowed mode - gCLIOpts.FullScreen = 2; - - if (strcmp(argv[i], "--help") == 0) // Print help - { - printf("Super Mario 64 PC Port\n"); - printf("%-20s\tSkips the Peach and Castle intro when starting a new game.\n", "--skip-intro"); - printf("%-20s\tStarts the game in full screen mode.\n", "--fullscreen"); - printf("%-20s\tStarts the game in windowed mode.\n", "--windowed"); - printf("%-20s\tSaves the configuration file as CONFIGNAME.\n", "--configfile CONFIGNAME"); - exit(0); - } - - if (strncmp(argv[i], "--configfile", strlen("--configfile")) == 0) - { - if (i+1 < argc) - { - if (strlen(argv[i]) > 30) { - fprintf(stderr, "Configuration file supplied has a name too long.\n"); - } else { - memset(gCLIOpts.ConfigFile, 0, 30); - strncpy(gCLIOpts.ConfigFile, argv[i+1], strlen(argv[i+1])); - gCLIOpts.ConfigFile[strlen(argv[i+1])] = '\0'; - } - } - } - } - } +static void print_help(void) { + printf("Super Mario 64 PC Port\n"); + printf("%-20s\tSkips the Peach and Castle intro when starting a new game.\n", "--skip-intro"); + printf("%-20s\tStarts the game in full screen mode.\n", "--fullscreen"); + printf("%-20s\tStarts the game in windowed mode.\n", "--windowed"); + printf("%-20s\tSaves the configuration file as CONFIGNAME.\n", "--configfile CONFIGNAME"); +} + +void parse_cli_opts(int argc, char* argv[]) { + // Initialize options with false values. + memset(&gCLIOpts, 0, sizeof(gCLIOpts)); + strncpy(gCLIOpts.ConfigFile, CONFIGFILE_DEFAULT, sizeof(gCLIOpts.ConfigFile)); + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--skip-intro") == 0) // Skip Peach Intro + gCLIOpts.SkipIntro = 1; + + else if (strcmp(argv[i], "--fullscreen") == 0) // Open game in fullscreen + gCLIOpts.FullScreen = 1; + + else if (strcmp(argv[i], "--windowed") == 0) // Open game in windowed mode + gCLIOpts.FullScreen = 2; + + else if (strcmp(argv[i], "--cheats") == 0) // Enable cheats menu + Cheats.EnableCheats = true; + + // Print help + else if (strcmp(argv[i], "--help") == 0) { + print_help(); + game_exit(); + } + + else if (strcmp(argv[i], "--configfile") == 0) { + if (i+1 < argc) { + const unsigned int arglen = strlen(argv[i+1]); + if (arglen >= sizeof(gCLIOpts.ConfigFile)) { + fprintf(stderr, "Configuration file supplied has a name too long.\n"); + } else { + strncpy(gCLIOpts.ConfigFile, argv[i+1], arglen); + gCLIOpts.ConfigFile[arglen] = '\0'; + } + } + } + } } diff --git a/src/pc/cliopts.h b/src/pc/cliopts.h index 1844c44c..d20dcb4f 100644 --- a/src/pc/cliopts.h +++ b/src/pc/cliopts.h @@ -1,12 +1,14 @@ -#include "sm64.h" +#ifndef _CLIOPTS_H +#define _CLIOPTS_H -struct PCCLIOptions -{ - u8 SkipIntro; - u8 FullScreen; - char * ConfigFile; +struct PCCLIOptions { + unsigned int SkipIntro; + unsigned int FullScreen; + char ConfigFile[1024]; }; extern struct PCCLIOptions gCLIOpts; void parse_cli_opts(int argc, char* argv[]); + +#endif // _CLIOPTS_H diff --git a/src/pc/configfile.c b/src/pc/configfile.c index 0b6fe370..ce9a08ad 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -40,10 +40,11 @@ ConfigWindow configWindow = { .y = SDL_WINDOWPOS_CENTERED, .w = DESIRED_SCREEN_WIDTH, .h = DESIRED_SCREEN_HEIGHT, + .vsync = 1, .reset = false, - .vsync = false, .fullscreen = false, .exiting_fullscreen = false, + .settings_changed = false, }; unsigned int configFiltering = 1; // 0=force nearest, 1=linear, (TODO) 2=three-point unsigned int configMasterVolume = MAX_VOLUME; // 0 - MAX_VOLUME @@ -84,6 +85,7 @@ static const struct ConfigOption options[] = { {.name = "window_y", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.y}, {.name = "window_w", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.w}, {.name = "window_h", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.h}, + {.name = "vsync", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.vsync}, {.name = "texture_filtering", .type = CONFIG_TYPE_UINT, .uintValue = &configFiltering}, {.name = "master_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMasterVolume}, {.name = "key_a", .type = CONFIG_TYPE_BIND, .uintValue = configKeyA}, diff --git a/src/pc/configfile.h b/src/pc/configfile.h index 4343ebdc..cafd272f 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -3,16 +3,19 @@ #include +#define CONFIGFILE_DEFAULT "sm64config.txt" + #define MAX_BINDS 3 #define MAX_VOLUME 127 #define VOLUME_SHIFT 7 typedef struct { unsigned int x, y, w, h; + unsigned int vsync; bool reset; - bool vsync; bool fullscreen; bool exiting_fullscreen; + bool settings_changed; } ConfigWindow; extern ConfigWindow configWindow; diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index 363d9209..e86a100c 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -39,9 +39,12 @@ # define FRAMERATE 30 #endif +static const Uint32 FRAME_TIME = 1000 / FRAMERATE; + static SDL_Window *wnd; static SDL_GLContext ctx = NULL; static int inverted_scancode_table[512]; +static Uint32 frame_start = 0; const SDL_Scancode windows_scancode_table[] = { @@ -110,9 +113,9 @@ static void gfx_sdl_set_fullscreen() { } static void gfx_sdl_reset_dimension_and_pos() { - if (configWindow.exiting_fullscreen) + if (configWindow.exiting_fullscreen) { configWindow.exiting_fullscreen = false; - else if (configWindow.reset) { + } else if (configWindow.reset) { configWindow.x = SDL_WINDOWPOS_CENTERED; configWindow.y = SDL_WINDOWPOS_CENTERED; configWindow.w = DESIRED_SCREEN_WIDTH; @@ -123,29 +126,14 @@ static void gfx_sdl_reset_dimension_and_pos() { configWindow.fullscreen = false; return; } - } else + } else if (!configWindow.settings_changed) { return; + } + configWindow.settings_changed = false; SDL_SetWindowSize(wnd, configWindow.w, configWindow.h); SDL_SetWindowPosition(wnd, configWindow.x, configWindow.y); -} - -static bool test_vsync(void) { - // Even if SDL_GL_SetSwapInterval succeeds, it doesn't mean that VSync actually works. - // A 60 Hz monitor should have a swap interval of 16.67 milliseconds. - // If it takes less than 12 milliseconds, assume that VSync is not working. - // SDL_GetTicks() probably does not offer enough precision for this kind of shit. - Uint32 start, end; - - // do an extra swap, sometimes the first one takes longer (maybe creates buffers?) - SDL_GL_SwapWindow(wnd); - - SDL_GL_SwapWindow(wnd); - start = SDL_GetTicks(); - SDL_GL_SwapWindow(wnd); - end = SDL_GetTicks(); - - return (end - start >= 12); + SDL_GL_SetSwapInterval(configWindow.vsync); // in case vsync changed } static void gfx_sdl_init(void) { @@ -165,11 +153,9 @@ static void gfx_sdl_init(void) { if (gCLIOpts.FullScreen == 1) configWindow.fullscreen = true; - - if (gCLIOpts.FullScreen == 2) + else if (gCLIOpts.FullScreen == 2) configWindow.fullscreen = false; - const char* window_title = #ifndef USE_GLES "Super Mario 64 PC port (OpenGL)"; @@ -183,14 +169,11 @@ static void gfx_sdl_init(void) { SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE ); ctx = SDL_GL_CreateContext(wnd); - SDL_GL_SetSwapInterval(2); + + SDL_GL_SetSwapInterval(configWindow.vsync); gfx_sdl_set_fullscreen(); - configWindow.vsync = test_vsync(); - if (!configWindow.vsync) - printf("Warning: VSync is not enabled or not working. Falling back to timer for synchronization\n"); - for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) { inverted_scancode_table[windows_scancode_table[i]] = i; } @@ -206,8 +189,14 @@ static void gfx_sdl_init(void) { } static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) { - while (1) + Uint32 t; + while (1) { + t = SDL_GetTicks(); run_one_game_iter(); + t = SDL_GetTicks() - t; + if (t < FRAME_TIME && configWindow.vsync <= 1) + SDL_Delay(FRAME_TIME - t); + } } static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) { @@ -275,24 +264,11 @@ static void gfx_sdl_handle_events(void) { } static bool gfx_sdl_start_frame(void) { + frame_start = SDL_GetTicks(); return true; } -static void sync_framerate_with_timer(void) { - // Number of milliseconds a frame should take (30 fps) - const Uint32 FRAME_TIME = 1000 / FRAMERATE; - static Uint32 last_time; - - Uint32 elapsed = SDL_GetTicks() - last_time; - if (elapsed < FRAME_TIME) - SDL_Delay(FRAME_TIME - elapsed); - - last_time = SDL_GetTicks(); -} - static void gfx_sdl_swap_buffers_begin(void) { - if (!configWindow.vsync) - sync_framerate_with_timer(); SDL_GL_SwapWindow(wnd); }