diff --git a/.gitignore b/.gitignore index 2e3de6797..4aaf2032f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ src/assets/*/sequences/*.seq src/assets/*/textures/*.bin src/generated tools/mkrom/mkrom +/.vs diff --git a/README.md b/README.md index 5093ed046..9c5e7de14 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,10 @@ There are minor graphics- and gameplay-related issues, and possibly occasional c * 60 FPS support, including fixes for some framerate-related issues; * fixes for a couple original bugs and crashes; * basic mod support, currently enough to load a few custom levels; -* slightly expanded memory heap size. +* slightly expanded memory heap size; +* experimental uncapped framerate support: + * set `Game.TickRateDivisor` to `0` in `pd.ini` to activate; + * in practice the game will have issues running faster than ~150 FPS, so use VSync or `Video.FramerateLimit` to cap it. Currently only 32-bit platforms are supported, namely x86 Windows and Linux. Note that 32-bit binaries will still work on 64-bit versions of those platforms, diff --git a/port/fast3d/gfx_opengl.cpp b/port/fast3d/gfx_opengl.cpp index eb92631a7..313155bbb 100644 --- a/port/fast3d/gfx_opengl.cpp +++ b/port/fast3d/gfx_opengl.cpp @@ -1161,6 +1161,8 @@ void gfx_opengl_copy_framebuffer(int fb_dst, int fb_src, int left, int top, bool glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[current_framebuffer].fbo); + glReadBuffer(GL_BACK); + glEnable(GL_SCISSOR_TEST); } diff --git a/port/fast3d/gfx_pc.cpp b/port/fast3d/gfx_pc.cpp index 7ba7974a3..de208322b 100644 --- a/port/fast3d/gfx_pc.cpp +++ b/port/fast3d/gfx_pc.cpp @@ -860,12 +860,17 @@ static void import_texture(int i, int tile, bool importReplacement) { if (rdp.tex_lod || !loaded_texture.addr) { // set up miplevel 0; also acts as a catch-all for when .addr is NULL because my texture loader sucks + loaded_texture.addr = rdp.texture_to_load.addr; loaded_texture.line_size_bytes = rdp.texture_tile[tile].line_size_bytes; loaded_texture.full_image_line_size_bytes = rdp.texture_tile[tile].line_size_bytes; loaded_texture.full_size_bytes = loaded_texture.full_image_line_size_bytes * rdp.texture_tile[tile].height; loaded_texture.size_bytes = loaded_texture.line_size_bytes * rdp.texture_tile[tile].height; + if (siz == G_IM_SIZ_32b) { + // HACK: fixup 32-bit LODed texture height + loaded_texture.size_bytes <<= 1; + loaded_texture.full_size_bytes <<= 1; + } loaded_texture.orig_size_bytes = loaded_texture.size_bytes; - loaded_texture.addr = rdp.texture_to_load.addr; } const RawTexMetadata* metadata = &loaded_texture.raw_tex_metadata; diff --git a/port/src/main.c b/port/src/main.c index c104ce5a1..e12e67a3a 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -37,6 +37,8 @@ u8 g_VmShowStats = 0; s32 g_TickRateDiv = 1; +s32 g_SkipIntro = false; + extern s32 g_StageNum; s32 bootGetMemSize(void) @@ -123,7 +125,17 @@ int main(int argc, const char **argv) sysLogPrintf(LOG_NOTE, "rom file at %p - %p", g_RomFile, g_RomFile + g_RomFileSize); g_SndDisabled = sysArgCheck("--no-sound"); + g_StageNum = sysArgGetInt("--boot-stage", STAGE_TITLE); + + if (g_StageNum == STAGE_TITLE && (sysArgCheck("--skip-intro") || g_SkipIntro)) { + // shorthand for --boot-stage 0x26 + g_StageNum = STAGE_CITRAINING; + } else if (g_StageNum < 0x01 || g_StageNum > 0x5d) { + // stage num out of range + g_StageNum = STAGE_TITLE; + } + if (g_StageNum != STAGE_TITLE) { sysLogPrintf(LOG_NOTE, "boot stage set to 0x%02x", g_StageNum); } @@ -139,6 +151,7 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterInt("Game.CenterHUD", &g_HudCenter, 0, 1); configRegisterFloat("Game.ScreenShakeIntensity", &g_ViShakeIntensityMult, 0.f, 10.f); configRegisterInt("Game.TickRateDivisor", &g_TickRateDiv, 0, 10); + configRegisterInt("Game.SkipIntro", &g_SkipIntro, 0, 1); for (s32 j = 0; j < MAX_PLAYERS; ++j) { const s32 i = j + 1; configRegisterFloat(strFmt("Game.Player%d.FovY", i), &g_PlayerExtCfg[j].fovy, 5.f, 175.f); @@ -148,6 +161,6 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterFloat(strFmt("Game.Player%d.MouseAimSpeedY", i), &g_PlayerExtCfg[j].mouseaimspeedy, 0.f, 10.f); configRegisterFloat(strFmt("Game.Player%d.RadialMenuSpeed", i), &g_PlayerExtCfg[j].radialmenuspeed, 0.f, 10.f); configRegisterFloat(strFmt("Game.Player%d.CrosshairSway", i), &g_PlayerExtCfg[j].crosshairsway, 0.f, 10.f); - configRegisterInt(strFmt("Game.Player%d.ClassicCrouch", i), &g_PlayerExtCfg[j].classiccrouch, 0, 1); + configRegisterInt(strFmt("Game.Player%d.CrouchMode", i), &g_PlayerExtCfg[j].crouchmode, 0, CROUCHMODE_TOGGLE_ANALOG); } } diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index 4e428071c..265eb13cf 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -7,6 +7,7 @@ #include "types.h" #include "game/mainmenu.h" #include "game/menu.h" +#include "game/gamefile.h" #include "video.h" #include "input.h" #include "config.h" @@ -723,32 +724,44 @@ struct menudialogdef g_ExtendedVideoMenuDialog = { NULL, }; -static MenuItemHandlerResult menuhandlerFieldOfView(s32 operation, struct menuitem *item, union handlerdata *data) +static MenuItemHandlerResult menuhandlerCrouchMode(s32 operation, struct menuitem *item, union handlerdata *data) { + static const char *opts[] = { + "Hold", + "Analog", + "Toggle", + "Toggle + Analog" + }; + switch (operation) { - case MENUOP_GETSLIDER: - data->slider.value = g_PlayerExtCfg[g_ExtMenuPlayer].fovy + 0.5f; + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = ARRAYCOUNT(opts); break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)opts[data->dropdown.value]; case MENUOP_SET: - if (data->slider.value >= 15) { - g_PlayerExtCfg[g_ExtMenuPlayer].fovy = data->slider.value; - if (g_PlayerExtCfg[g_ExtMenuPlayer].fovzoom) { - g_PlayerExtCfg[g_ExtMenuPlayer].fovzoommult = g_PlayerExtCfg[g_ExtMenuPlayer].fovy / 60.f; - } - } + g_PlayerExtCfg[g_ExtMenuPlayer].crouchmode = data->dropdown.value; break; + case MENUOP_GETSELECTEDINDEX: + data->dropdown.value = g_PlayerExtCfg[g_ExtMenuPlayer].crouchmode; } return 0; } -static MenuItemHandlerResult menuhandlerClassicCrouch(s32 operation, struct menuitem *item, union handlerdata *data) +static MenuItemHandlerResult menuhandlerFieldOfView(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { - case MENUOP_GET: - return g_PlayerExtCfg[g_ExtMenuPlayer].classiccrouch; + case MENUOP_GETSLIDER: + data->slider.value = g_PlayerExtCfg[g_ExtMenuPlayer].fovy + 0.5f; + break; case MENUOP_SET: - g_PlayerExtCfg[g_ExtMenuPlayer].classiccrouch = data->checkbox.value; + if (data->slider.value >= 15) { + g_PlayerExtCfg[g_ExtMenuPlayer].fovy = data->slider.value; + if (g_PlayerExtCfg[g_ExtMenuPlayer].fovzoom) { + g_PlayerExtCfg[g_ExtMenuPlayer].fovzoommult = g_PlayerExtCfg[g_ExtMenuPlayer].fovy / 60.f; + } + } break; } @@ -771,12 +784,12 @@ static MenuItemHandlerResult menuhandlerCrosshairSway(s32 operation, struct menu struct menuitem g_ExtendedGameMenuItems[] = { { - MENUITEMTYPE_CHECKBOX, + MENUITEMTYPE_DROPDOWN, 0, MENUITEMFLAG_LITERAL_TEXT, - (uintptr_t)"Allow Classic Crouch", + (uintptr_t)"Crouch Mode", 0, - menuhandlerClassicCrouch, + menuhandlerCrouchMode, }, { MENUITEMTYPE_SLIDER, diff --git a/src/game/bondmove.c b/src/game/bondmove.c index 45f75ea6c..5e44e837a 100644 --- a/src/game/bondmove.c +++ b/src/game/bondmove.c @@ -1565,31 +1565,43 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i // Handle xbla-style crouch cycling for (i = 0; i < numsamples; i++) { - s32 crouchsample = joyGetButtonsPressedOnSample(i, contpad1, 0xffffffff) & BUTTON_CROUCH_CYCLE; - if (crouchsample) { - if (g_Vars.currentplayer->crouchpos <= 0) { - g_Vars.currentplayer->crouchpos = CROUCHPOS_STAND; - } else { - g_Vars.currentplayer->crouchpos--; - } - } - // handle 1964GEPD style crouch setting - crouchsample = joyGetButtonsPressedOnSample(i, contpad1, c1allowedbuttons) & BUTTON_HALF_CROUCH; - if (crouchsample) { - if (g_Vars.currentplayer->crouchpos == CROUCHPOS_DUCK) { - g_Vars.currentplayer->crouchpos = CROUCHPOS_STAND; - } else { - g_Vars.currentplayer->crouchpos = CROUCHPOS_DUCK; + s32 crouchsample; + if (PLAYER_EXTCFG().crouchmode & CROUCHMODE_TOGGLE) { + // press to toggle crouch position + crouchsample = joyGetButtonsPressedOnSample(i, contpad1, 0xffffffff) & BUTTON_CROUCH_CYCLE; + if (crouchsample) { + if (g_Vars.currentplayer->crouchpos <= 0) { + g_Vars.currentplayer->crouchpos = CROUCHPOS_STAND; + } else { + g_Vars.currentplayer->crouchpos--; + } } - } - - crouchsample = joyGetButtonsPressedOnSample(i, contpad1, c1allowedbuttons) & BUTTON_FULL_CROUCH; - if (crouchsample) { - if (g_Vars.currentplayer->crouchpos == CROUCHPOS_SQUAT) { + crouchsample = joyGetButtonsPressedOnSample(i, contpad1, c1allowedbuttons) & BUTTON_HALF_CROUCH; + if (crouchsample) { + if (g_Vars.currentplayer->crouchpos == CROUCHPOS_DUCK) { + g_Vars.currentplayer->crouchpos = CROUCHPOS_STAND; + } else { + g_Vars.currentplayer->crouchpos = CROUCHPOS_DUCK; + } + } + crouchsample = joyGetButtonsPressedOnSample(i, contpad1, c1allowedbuttons) & BUTTON_FULL_CROUCH; + if (crouchsample) { + if (g_Vars.currentplayer->crouchpos == CROUCHPOS_SQUAT) { + g_Vars.currentplayer->crouchpos = CROUCHPOS_STAND; + } else { + g_Vars.currentplayer->crouchpos = CROUCHPOS_SQUAT; + } + } + } else if (PLAYER_EXTCFG().crouchmode == CROUCHMODE_HOLD) { + // hold to crouch + crouchsample = joyGetButtonsOnSample(i, contpad1, c1allowedbuttons) & (BUTTON_FULL_CROUCH | BUTTON_HALF_CROUCH); + if (!crouchsample) { g_Vars.currentplayer->crouchpos = CROUCHPOS_STAND; - } else { + } else if (crouchsample & BUTTON_FULL_CROUCH) { g_Vars.currentplayer->crouchpos = CROUCHPOS_SQUAT; + } else if (crouchsample & BUTTON_HALF_CROUCH) { + g_Vars.currentplayer->crouchpos = CROUCHPOS_DUCK; } } } @@ -1657,7 +1669,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i #endif // Handle C-button and analog crouch and uncrouch, if enabled - if (PLAYER_EXTCFG().classiccrouch && allowc1buttons) { + if ((PLAYER_EXTCFG().crouchmode & CROUCHMODE_ANALOG) && allowc1buttons) { for (i = 0; i < numsamples; i++) { if (!canmanualzoom && aimonhist[i]) { bool goUp = joyGetButtonsPressedOnSample(i, contpad1, c1allowedbuttons & (U_CBUTTONS)); diff --git a/src/game/cheats.c b/src/game/cheats.c index bfe910cb0..71ac21ca8 100644 --- a/src/game/cheats.c +++ b/src/game/cheats.c @@ -859,6 +859,79 @@ char *cheatGetName(s32 cheat_id) } #endif +#ifndef PLATFORM_N64 + +static MenuItemHandlerResult menuhandlerUnlockEverything(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (operation == MENUOP_SET) { + gamefileUnlockEverything(); + } + return 0; +} + +struct menuitem g_CheatsConfirmUnlockMenuItems[] = { + { + MENUITEMTYPE_LABEL, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Are you sure?\n\nThis will overwrite any progress\nsaved to the current profile.\n", + 0, + NULL, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0x00000082, + 0, + NULL, + }, + { + MENUITEMTYPE_MARQUEE, + 0, + MENUITEMFLAG_SMALLFONT | MENUITEMFLAG_MARQUEE_FADEBOTHSIDES | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Unlocks all cheats, weapons, missions, challenges and combat simulator items.\n", + 0, + NULL, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0x00000082, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CENTRE | MENUITEMFLAG_SELECTABLE_CLOSESDIALOG, + L_OPTIONS_191, // "No" + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CENTRE | MENUITEMFLAG_SELECTABLE_CLOSESDIALOG, + L_OPTIONS_190, // "Yes" + 0, + menuhandlerUnlockEverything, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_CheatsConfirmUnlockMenuDialog = { + MENUDIALOGTYPE_DANGER, + L_OPTIONS_188, // "Warning" + g_CheatsConfirmUnlockMenuItems, + NULL, + 0, + NULL, +}; + +#endif + struct menuitem g_CheatsFunMenuItems[] = { { MENUITEMTYPE_CHECKBOX, @@ -1538,6 +1611,16 @@ struct menuitem g_CheatsMenuItems[] = { 0, cheatMenuHandleTurnOffAllCheats, }, +#ifndef PLATFORM_N64 + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SELECTABLE_OPENSDIALOG, + (uintptr_t)"Unlock Everything\n", + 0, + (void *)&g_CheatsConfirmUnlockMenuDialog, + }, +#endif { MENUITEMTYPE_SEPARATOR, 0, diff --git a/src/game/gamefile.c b/src/game/gamefile.c index d8d36474e..468ac0889 100644 --- a/src/game/gamefile.c +++ b/src/game/gamefile.c @@ -567,3 +567,59 @@ void gamefileGetOverview(char *arg0, char *name, u8 *stage, u8 *difficulty, u32 *difficulty = savebufferReadBits(&buffer, 2); } + +#ifndef PLATFORM_N64 + +// Unlock all of the unlockables. +// These hacks are taken from the original debug mode. +void gamefileUnlockEverything(void) +{ + s32 i, j; + + // unlock all challenges + for (i = 0; i < ARRAYCOUNT(g_MpChallenges); ++i) { + for (j = 0; j < MAX_PLAYERS; ++j) { + g_MpChallenges[i].completions[j] = 0x0f; + } + } + challengeDetermineUnlockedFeatures(); + + // complete all missions in coop + for (i = 0; i < ARRAYCOUNT(g_GameFile.coopcompletions); ++i) { + g_GameFile.coopcompletions[i] = 0x1fffff; + } + + // unlock all guns + for (i = 0; i < ARRAYCOUNT(g_GameFile.weaponsfound); ++i) { + g_GameFile.weaponsfound[i] = 0xff; + } + + // unlock all campaign levels + for (i = 0; i < NUM_SOLOSTAGES; i++) { + for (j = 0; j < 3; j++) { + g_GameFile.besttimes[i][j] = 7; + } + } + + // unlock alternate intro sequence + g_AltTitleUnlocked = true; + + // unlock all firing range challenges + for (i = 0; i < ARRAYCOUNT(g_GameFile.firingrangescores); ++i) { + g_GameFile.firingrangescores[i] = 0xff; + } + + // unlock all device training + gamefileSetFlag(GAMEFILEFLAG_CI_CLOAK_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_DISGUISE_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_XRAY_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_IR_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_RTRACKER_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_DOORDECODER_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_NIGHTVISION_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_CAMSPY_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_ECMMINE_DONE); + gamefileSetFlag(GAMEFILEFLAG_CI_UPLINK_DONE); +} + +#endif diff --git a/src/game/mplayer/mplayer.c b/src/game/mplayer/mplayer.c index b389e6215..3faa30032 100644 --- a/src/game/mplayer/mplayer.c +++ b/src/game/mplayer/mplayer.c @@ -120,7 +120,7 @@ struct mpweapon g_MpWeapons[NUM_MPWEAPONS] = { .mouseaimspeedy = 0.7f, \ .radialmenuspeed = 4.f, \ .crosshairsway = 1.f, \ - .classiccrouch = true, \ + .crouchmode = CROUCHMODE_TOGGLE_ANALOG, \ } struct extplayerconfig g_PlayerExtCfg[MAX_PLAYERS] = { diff --git a/src/include/constants.h b/src/include/constants.h index 2668eee0a..b4d977863 100644 --- a/src/include/constants.h +++ b/src/include/constants.h @@ -4702,6 +4702,11 @@ enum weaponnum { #define MOUSEAIM_CLASSIC 0 // crosshair moves around the screen in aim mode #define MOUSEAIM_LOCKED 1 // crosshair locked to the center of the screen in aim mode +#define CROUCHMODE_HOLD 0 // hold the crouch buttons to keep crouching +#define CROUCHMODE_ANALOG 1 // analog crouch like on n64 +#define CROUCHMODE_TOGGLE 2 // press the crouch buttons to toggle stance +#define CROUCHMODE_TOGGLE_ANALOG (CROUCHMODE_ANALOG | CROUCHMODE_TOGGLE) + #endif #endif diff --git a/src/include/game/gamefile.h b/src/include/game/gamefile.h index 62fa97830..e3f8c6431 100644 --- a/src/include/game/gamefile.h +++ b/src/include/game/gamefile.h @@ -13,5 +13,6 @@ void gamefileLoadDefaults(struct gamefile *file); s32 gamefileLoad(s32 device); s32 gamefileSave(s32 device, s32 filenum, u16 deviceserial); void gamefileGetOverview(char *arg0, char *name, u8 *stage, u8 *difficulty, u32 *time); +void gamefileUnlockEverything(void); #endif diff --git a/src/include/types.h b/src/include/types.h index 171b0eda7..f307b8ada 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -6132,7 +6132,7 @@ struct extplayerconfig { s32 mouseaimmode; f32 mouseaimspeedx; f32 mouseaimspeedy; - s32 classiccrouch; + s32 crouchmode; f32 radialmenuspeed; f32 crosshairsway; };