diff --git a/Makefile.port b/Makefile.port index 1beb99b97..125c5cbd6 100644 --- a/Makefile.port +++ b/Makefile.port @@ -116,14 +116,23 @@ else endif VERSION_HASH := $(shell git rev-parse --short HEAD) +VERSION_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) VERSION_TARGET := $(TARGET_PLATFORM) VERSION_ROMID := $(ROMID) +ifeq ($(VERSION_BRANCH),) + VERSION_BRANCH := port-custom +endif +ifeq ($(VERSION_BRANCH),HEAD) + VERSION_BRANCH := port-custom +endif + DEFINES := \ VERSION=$(VERSION) \ VERSION_HASH=\"$(VERSION_HASH)\" \ VERSION_TARGET=\"$(VERSION_TARGET)\" \ VERSION_ROMID=\"$(VERSION_ROMID)\" \ + VERSION_BRANCH=\"$(VERSION_BRANCH)\" \ MATCHING=0 \ PAL=$(IS_PAL) \ PIRACYCHECKS=0 \ @@ -297,6 +306,9 @@ $(G_DIR)/tiles/%.h: $(A_DIR)/tiles/%.json $(BIN): $(O_FILES) $(LD) $(COMMON_CFLAGS) $(OPT_LVL) -o $(BIN) $^ $(LDFLAGS) +ifneq ($(CV2PDB),) + $(CV2PDB) $(BIN) +endif $(B_DIR)/%.o: %.c @mkdir -p $(dir $@) diff --git a/README.md b/README.md index 9c5e7de14..3451264ff 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ There are minor graphics- and gameplay-related issues, and possibly occasional c * fixes for a couple original bugs and crashes; * basic mod support, currently enough to load a few custom levels; * slightly expanded memory heap size; -* experimental uncapped framerate support: +* experimental high framerate support (up to 240 FPS): * 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. + * in practice the game will have issues running faster than ~165 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, @@ -44,6 +44,8 @@ Latest [automatic builds](https://github.com/fgsfdsfgs/perfect_dark/actions) for * [i686-windows](https://nightly.link/fgsfdsfgs/perfect_dark/workflows/c-cpp/port/pd-i686-windows.zip) * [i686-linux](https://nightly.link/fgsfdsfgs/perfect_dark/workflows/c-cpp/port/pd-i686-linux.zip) +If you are looking for netplay builds (the `port-net` branch), see [this link](https://github.com/fgsfdsfgs/perfect_dark/blob/port-net/README.md#download). + ## Running You must already have a Perfect Dark ROM to run the game, as specified above. @@ -97,7 +99,7 @@ Controls can be rebound in `pd.ini`. Default control scheme is as follows: ### Linux -1. Ensure you have gcc, g++ and 32-bit versions of SDL2, libGL and ZLib installed on your system. +1. Ensure you have gcc, g++ (version 10.0+) and 32-bit versions of SDL2 (version 2.0.12+), libGL and ZLib installed on your system. * On a 64-bit system you also need to have `gcc-multilib` and `g++-multilib` (or your distro's analogues) installed. 2. Run the following command in the `perfect_dark` directory: * On a 64-bit system: ```make -f Makefile.port TARGET_PLATFORM=i686-linux``` diff --git a/port/fast3d/gfx_api.h b/port/fast3d/gfx_api.h index 1f53f0ed4..3bf8427ad 100644 --- a/port/fast3d/gfx_api.h +++ b/port/fast3d/gfx_api.h @@ -31,7 +31,7 @@ extern float gfx_current_native_aspect; // The aspect ratio of the above mode extern bool gfx_framebuffers_enabled; void gfx_init(struct GfxWindowManagerAPI* wapi, struct GfxRenderingAPI* rapi, const char* game_name, - bool start_in_fullscreen, uint32_t width, uint32_t height, + bool start_in_fullscreen, bool start_maximized, uint32_t width, uint32_t height, uint32_t posX, uint32_t posY); void gfx_destroy(void); struct GfxRenderingAPI* gfx_get_current_rendering_api(void); diff --git a/port/fast3d/gfx_opengl.cpp b/port/fast3d/gfx_opengl.cpp index d446f111f..86efd96bc 100644 --- a/port/fast3d/gfx_opengl.cpp +++ b/port/fast3d/gfx_opengl.cpp @@ -396,25 +396,17 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad if (cc_features.opt_blur) { // blur filter, used for menu backgrounds + // used to be two for loops from 0 to 4, but apparently intel drivers crashed trying to unroll it + // used to have a const weight array, but apparently drivers for the GT620 don't like const array initializers append_line(fs_buf, &fs_len, R"( - const vec4 blurOffsets = vec4(-1.5, -0.5, +0.5, +1.5); - const mat4 blurWeights = mat4( - 0.009947, 0.009641, 0.008778, 0.007509, - 0.009641, 0.009345, 0.008508, 0.007278, - 0.008778, 0.008508, 0.007747, 0.006626, - 0.007509, 0.007278, 0.006626, 0.005668 - ); - lowp vec4 hookTexture2D(in sampler2D tex, in vec2 texCoord, in vec2 texSize) { - lowp vec4 color = vec4(0.0); - lowp float wacc = 0.0; - for (int y = 0; y < 4; ++y) { - for (int x = 0; x < 4; ++x) { - vec2 ofs = vec2(blurOffsets[x], blurOffsets[y]) / texSize; - wacc += blurWeights[x][y]; - color += texture2D(tex, texCoord + ofs) * blurWeights[x][y]; - } + lowp vec4 hookTexture2D(in sampler2D t, in vec2 uv, in vec2 tsize) { + lowp vec4 cw = vec4(0.0); + for (int i = 0; i < 16; ++i) { + vec2 xy = vec2(float(i & 3), float(i >> 2)); + lowp float w = 0.009947 - length(xy) * 0.001; + cw += vec4(texture2D(t, uv + (vec2(-1.5) + xy) / tsize).rgb * w, w); } - return vec4(color.rgb / wacc, 1.0); + return vec4(cw.rgb / cw.a, 1.0); })" ); } else if (current_filter_mode == FILTER_THREE_POINT) { @@ -589,6 +581,7 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &max_length); char error_log[1024]; glGetShaderInfoLog(vertex_shader, max_length, &max_length, &error_log[0]); + sysLogPrintf(LOG_ERROR, "Failed to compile this vertex shader (ID %llx, %x):\n%s", shader_id0, shader_id1, vs_buf); sysFatalError("Vertex shader compilation failed:\n%s", error_log); } @@ -601,6 +594,7 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &max_length); char error_log[1024]; glGetShaderInfoLog(fragment_shader, max_length, &max_length, &error_log[0]); + sysLogPrintf(LOG_ERROR, "Failed to compile this fragment shader (ID %llx, %x):\n%s", shader_id0, shader_id1, fs_buf); sysFatalError("Fragment shader compilation failed:\n%s", error_log); } diff --git a/port/fast3d/gfx_pc.cpp b/port/fast3d/gfx_pc.cpp index a5c997a31..cdd0d3e3c 100644 --- a/port/fast3d/gfx_pc.cpp +++ b/port/fast3d/gfx_pc.cpp @@ -2699,10 +2699,10 @@ extern "C" void gfx_get_dimensions(uint32_t* width, uint32_t* height, int32_t* p } extern "C" void gfx_init(struct GfxWindowManagerAPI* wapi, struct GfxRenderingAPI* rapi, const char* game_name, - bool start_in_fullscreen, uint32_t width, uint32_t height, uint32_t posX, uint32_t posY) { + bool start_in_fullscreen, bool start_maximized, uint32_t width, uint32_t height, uint32_t posX, uint32_t posY) { gfx_wapi = wapi; gfx_rapi = rapi; - gfx_wapi->init(game_name, rapi->get_name(), start_in_fullscreen, width, height, posX, posY); + gfx_wapi->init(game_name, rapi->get_name(), start_in_fullscreen, start_maximized, width, height, posX, posY); gfx_rapi->init(); gfx_rapi->update_framebuffer_parameters(0, width, height, 1, false, true, true, true); gfx_current_dimensions.internal_mul = 1; diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index 274bf1230..8e9dcfaf7 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -36,6 +36,7 @@ static bool vsync_enabled = true; static int window_width = DESIRED_SCREEN_WIDTH; static int window_height = DESIRED_SCREEN_HEIGHT; static bool fullscreen_state; +static bool maximized_state; static bool is_running = true; static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen); @@ -76,6 +77,18 @@ static void set_fullscreen(bool on, bool call_callback) { } } +static void set_maximize_window(bool on) { + if (maximized_state == on) { + return; + } + maximized_state = on; + if (on) { + SDL_MaximizeWindow(wnd); + } else { + SDL_RestoreWindow (wnd); + } +} + static void gfx_sdl_get_active_window_refresh_rate(uint32_t* refresh_rate) { int display_in_use = SDL_GetWindowDisplayIndex(wnd); @@ -84,7 +97,7 @@ static void gfx_sdl_get_active_window_refresh_rate(uint32_t* refresh_rate) { *refresh_rate = mode.refresh_rate; } -static void gfx_sdl_init(const char* game_name, const char* gfx_api_name, bool start_in_fullscreen, uint32_t width, +static void gfx_sdl_init(const char* game_name, const char* gfx_api_name, bool start_in_fullscreen, bool start_maximized, uint32_t width, uint32_t height, int32_t posX, int32_t posY) { window_width = width; window_height = height; @@ -111,6 +124,7 @@ static void gfx_sdl_init(const char* game_name, const char* gfx_api_name, bool s } const Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL; + wnd = SDL_CreateWindow(title, posX, posY, window_width, window_height, flags); if (!wnd) { sysFatalError("Could not open SDL window:\n%s", SDL_GetError()); @@ -181,6 +195,7 @@ static void gfx_sdl_init(const char* game_name, const char* gfx_api_name, bool s } #endif + set_maximize_window(start_maximized); set_fullscreen(start_in_fullscreen, false); } @@ -196,6 +211,10 @@ static void gfx_sdl_set_fullscreen(bool enable) { set_fullscreen(enable, true); } +static void gfx_sdl_set_maximize_window(bool enable) { + set_maximize_window(enable); +} + static void gfx_sdl_set_cursor_visibility(bool visible) { if (visible) { SDL_ShowCursor(SDL_ENABLE); @@ -291,7 +310,7 @@ static void gfx_sdl_set_target_fps(int fps) { } static bool gfx_sdl_can_disable_vsync(void) { - return false; + return true; } static void *gfx_sdl_get_window_handle(void) { @@ -302,9 +321,13 @@ static void gfx_sdl_set_window_title(const char *title) { SDL_SetWindowTitle(wnd, title); } -static void gfx_sdl_set_swap_interval(int interval) { - SDL_GL_SetSwapInterval(interval); - vsync_enabled = (interval != 0); +static bool gfx_sdl_set_swap_interval(int interval) { + const bool success = SDL_GL_SetSwapInterval(interval) >= 0; + vsync_enabled = success && (interval != 0); + if (!success) { + sysLogPrintf(LOG_WARNING, "SDL: failed to set vsync %d: %s", interval, SDL_GetError()); + } + return success; } struct GfxWindowManagerAPI gfx_sdl = { @@ -312,6 +335,7 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_close, gfx_sdl_set_fullscreen_changed_callback, gfx_sdl_set_fullscreen, + gfx_sdl_set_maximize_window, gfx_sdl_get_active_window_refresh_rate, gfx_sdl_set_cursor_visibility, gfx_sdl_get_dimensions, diff --git a/port/fast3d/gfx_window_manager_api.h b/port/fast3d/gfx_window_manager_api.h index f4070f782..44eda4160 100644 --- a/port/fast3d/gfx_window_manager_api.h +++ b/port/fast3d/gfx_window_manager_api.h @@ -5,11 +5,12 @@ #include struct GfxWindowManagerAPI { - void (*init)(const char* game_name, const char* gfx_api_name, bool start_in_fullscreen, uint32_t width, + void (*init)(const char* game_name, const char* gfx_api_name, bool start_in_fullscreen, bool start_maximized, uint32_t width, uint32_t height, int32_t posX, int32_t posY); void (*close)(void); void (*set_fullscreen_changed_callback)(void (*on_fullscreen_changed)(bool is_now_fullscreen)); void (*set_fullscreen)(bool enable); + void (*set_maximize)(bool enable); void (*get_active_window_refresh_rate)(uint32_t* refresh_rate); void (*set_cursor_visibility)(bool visible); void (*get_dimensions)(uint32_t* width, uint32_t* height, int32_t* posX, int32_t* posY); @@ -22,7 +23,7 @@ struct GfxWindowManagerAPI { bool (*can_disable_vsync)(void); void *(*get_window_handle)(void); void (*set_window_title)(const char *); - void (*set_swap_interval)(int); + bool (*set_swap_interval)(int); }; #endif diff --git a/port/include/system.h b/port/include/system.h index 005dbaf48..94158802a 100644 --- a/port/include/system.h +++ b/port/include/system.h @@ -24,6 +24,7 @@ u64 sysGetMicroseconds(void); void sysFatalError(const char *fmt, ...) __attribute__((noreturn)); +s32 sysLogIsOpen(void); void sysLogPrintf(s32 level, const char *fmt, ...); void sysGetExecutablePath(char *outPath, const u32 outLen); diff --git a/port/include/video.h b/port/include/video.h index ffd2c6728..105f78699 100644 --- a/port/include/video.h +++ b/port/include/video.h @@ -4,6 +4,13 @@ #include #include +// maximum framerate; if the game runs faster than this, things will break +#if PAL +#define VIDEO_MAX_FPS 200 +#else +#define VIDEO_MAX_FPS 240 +#endif + s32 videoInit(void); void videoStartFrame(void); void videoSubmitCommands(Gfx *cmds); @@ -20,6 +27,8 @@ s32 videoGetWidth(void); s32 videoGetHeight(void); f32 videoGetAspect(void); s32 videoGetFullscreen(void); +s32 videoGetMaximizeWindow(void); +void videoSetMaximizeWindow(s32 fs); u32 videoGetTextureFilter(void); u32 videoGetTextureFilter2D(void); diff --git a/port/src/config.c b/port/src/config.c index 5bce419e1..b37c2e955 100644 --- a/port/src/config.c +++ b/port/src/config.c @@ -16,6 +16,7 @@ typedef enum { CFG_NONE, CFG_S32, CFG_F32, + CFG_U32, CFG_STR } configtype; @@ -27,6 +28,7 @@ struct configentry { union { struct { f32 min_f32, max_f32; }; struct { s32 min_s32, max_s32; }; + struct { u32 min_u32, max_u32; }; u32 max_str; }; } settings[CONFIG_MAX_SETTINGS]; @@ -38,11 +40,15 @@ static inline s32 configClampInt(s32 val, s32 min, s32 max) return (val < min) ? min : ((val > max) ? max : val); } -static inline f32 configClampFloat(f32 val, f32 min, f32 max) +static inline u32 configClampUInt(u32 val, u32 min, u32 max) { return (val < min) ? min : ((val > max) ? max : val); } +static inline f32 configClampFloat(f32 val, f32 min, f32 max) +{ + return (val < min) ? min : ((val > max) ? max : val); +} static inline struct configentry *configFindEntry(const char *key) { @@ -101,6 +107,17 @@ void configRegisterInt(const char *key, s32 *var, s32 min, s32 max) } } +void configRegisterUInt(const char* key, u32* var, u32 min, u32 max) +{ + struct configentry* cfg = configFindOrAddEntry(key); + if (cfg) { + cfg->type = CFG_U32; + cfg->ptr = var; + cfg->min_u32 = min; + cfg->max_u32 = max; + } +} + void configRegisterFloat(const char *key, f32 *var, f32 min, f32 max) { struct configentry *cfg = configFindOrAddEntry(key); @@ -129,21 +146,29 @@ static void configSetFromString(const char *key, const char *val) s32 tmp_s32; f32 tmp_f32; + u32 tmp_u32; switch (cfg->type) { case CFG_S32: - tmp_s32 = atoi(val); + tmp_s32 = strtol(val, NULL, 0); if (cfg->min_s32 < cfg->max_s32) { - tmp_f32 = configClampInt(tmp_s32, cfg->min_s32, cfg->max_s32); + tmp_s32 = configClampInt(tmp_s32, cfg->min_s32, cfg->max_s32); } *(s32 *)cfg->ptr = tmp_s32; break; case CFG_F32: - tmp_f32 = atof(val); + tmp_f32 = strtof(val, NULL); if (cfg->min_f32 < cfg->max_f32) { tmp_f32 = configClampFloat(tmp_f32, cfg->min_f32, cfg->max_f32); } *(f32 *)cfg->ptr = tmp_f32; break; + case CFG_U32: + tmp_u32 = strtoul(val, NULL, 0); + if (cfg->min_u32 < cfg->max_u32) { + tmp_u32 = configClampUInt(tmp_u32, cfg->min_u32, cfg->max_u32); + } + *(u32*)cfg->ptr = tmp_u32; + break; case CFG_STR: strncpy(cfg->ptr, val, cfg->max_str ? cfg->max_str - 1 : 4096); break; @@ -167,6 +192,12 @@ static void configSaveEntry(struct configentry *cfg, FILE *f) } fprintf(f, "%s=%f\n", cfg->key + cfg->seclen + 1, *(f32 *)cfg->ptr); break; + case CFG_U32: + if (cfg->min_u32 < cfg->max_u32) { + *(u32*)cfg->ptr = configClampUInt(*(u32*)cfg->ptr, cfg->min_u32, cfg->max_u32); + } + fprintf(f, "%s=%u\n", cfg->key + cfg->seclen + 1, *(u32 *)cfg->ptr); + break; case CFG_STR: fprintf(f, "%s=%s\n", cfg->key + cfg->seclen + 1, (char *)cfg->ptr); break; diff --git a/port/src/crash.c b/port/src/crash.c index 77a8228ed..dc0536589 100644 --- a/port/src/crash.c +++ b/port/src/crash.c @@ -10,7 +10,8 @@ #include "system.h" #include "platform.h" -#define CRASH_MAX_MSG 4096 +#define CRASH_LOG_FNAME "pd.crash.log" +#define CRASH_MAX_MSG 8192 #define CRASH_MAX_SYM 256 #define CRASH_MAX_FRAMES 32 #define CRASH_MSG(...) \ @@ -79,8 +80,8 @@ static void crashStackTrace(char *msg, PEXCEPTION_POINTERS exinfo) if (SymGetLineFromAddr64(process, (uintptr_t)exinfo->ExceptionRecord->ExceptionAddress, &disp, &line)) { CRASH_MSG(": %s:%lu+%lu", line.FileName, line.LineNumber, disp); } - CRASH_MSG("\nMODULE: %p\n", crashGetModuleBase(exinfo->ExceptionRecord->ExceptionAddress)); - CRASH_MSG("MAIN MODULE: %p\n", crashGetModuleBase(crashInit)); + CRASH_MSG("\nMODULE: [%p]\n", crashGetModuleBase(exinfo->ExceptionRecord->ExceptionAddress)); + CRASH_MSG("MAIN MODULE: [%p]\n", crashGetModuleBase(crashInit)); CRASH_MSG("\nBACKTRACE:\n"); char symbuf[sizeof(SYMBOL_INFO) + CRASH_MAX_SYM * sizeof(TCHAR)]; @@ -99,6 +100,10 @@ static void crashStackTrace(char *msg, PEXCEPTION_POINTERS exinfo) if (SymFromAddr(process, stackframe.AddrPC.Offset, &disp64, sym)) { CRASH_MSG(": %s+%llu", sym->Name, disp64); + } else if (process, stackframe.AddrPC.Offset) { + const uintptr_t modbase = (uintptr_t)crashGetModuleBase((void *)(uintptr_t)stackframe.AddrPC.Offset); + const uintptr_t modofs = (uintptr_t)stackframe.AddrPC.Offset - modbase; + CRASH_MSG(": [%p]+%p", (void *)modbase, (void *)modofs); } if(SymGetLineFromAddr64(process, stackframe.AddrPC.Offset, &disp, &line)) { @@ -135,6 +140,15 @@ static long __stdcall crashHandler(PEXCEPTION_POINTERS exinfo) crashStackTrace(msg, exinfo); + // open log file for the crash dump if one hasn't been opened yet + if (!sysLogIsOpen()) { + FILE *f = fopen(CRASH_LOG_FNAME, "wb"); + if (f) { + fprintf(f, "Crash!\n\n%s", msg); + fclose(f); + } + } + sysFatalError("Crash!\n\n%s", msg); return EXCEPTION_CONTINUE_EXECUTION; diff --git a/port/src/input.c b/port/src/input.c index 3fa43d24f..92930a246 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -851,6 +851,10 @@ void inputRumble(s32 idx, f32 strength, f32 time) return; } + if (padsCfg[idx].rumbleScale <= 0.f) { + return; + } + if (padsCfg[idx].rumbleOn) { strength *= padsCfg[idx].rumbleScale; if (strength <= 0.f) { diff --git a/port/src/main.c b/port/src/main.c index 0d7ce63cf..2cf5cffef 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -39,6 +39,8 @@ s32 g_TickRateDiv = 1; s32 g_SkipIntro = false; +s32 g_FileAutoSelect = -1; + extern s32 g_StageNum; s32 bootGetMemSize(void) @@ -143,6 +145,11 @@ int main(int argc, const char **argv) sysLogPrintf(LOG_NOTE, "boot stage set to 0x%02x", g_StageNum); } + g_FileAutoSelect = sysArgGetInt("--profile", -1); + if (g_FileAutoSelect >= 0) { + sysLogPrintf(LOG_NOTE, "player profile set to %d", g_FileAutoSelect); + } + mainProc(); return 0; @@ -156,6 +163,7 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterInt("Game.TickRateDivisor", &g_TickRateDiv, 0, 10); configRegisterInt("Game.SkipIntro", &g_SkipIntro, 0, 1); configRegisterInt("Game.DisableMpDeathMusic", &g_MusicDisableMpDeath, 0, 1); + configRegisterInt("Game.GEMuzzleFlashes", &g_BgunGeMuzzleFlashes, 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); @@ -167,5 +175,8 @@ PD_CONSTRUCTOR static void gameConfigInit(void) configRegisterFloat(strFmt("Game.Player%d.CrosshairSway", i), &g_PlayerExtCfg[j].crosshairsway, 0.f, 10.f); configRegisterInt(strFmt("Game.Player%d.CrouchMode", i), &g_PlayerExtCfg[j].crouchmode, 0, CROUCHMODE_TOGGLE_ANALOG); configRegisterInt(strFmt("Game.Player%d.ExtendedControls", i), &g_PlayerExtCfg[j].extcontrols, 0, 1); + configRegisterUInt(strFmt("Game.Player%d.CrosshairColour", i), &g_PlayerExtCfg[j].crosshaircolour, 0, 0xFFFFFFFF); + configRegisterUInt(strFmt("Game.Player%d.CrosshairSize", i), &g_PlayerExtCfg[j].crosshairsize, 0, 4); + configRegisterInt(strFmt("Game.Player%d.CrosshairHealth", i), &g_PlayerExtCfg[j].crosshairhealth, 0, CROSSHAIR_HEALTH_ON_WHITE); } } diff --git a/port/src/optionsmenu.c b/port/src/optionsmenu.c index e77e95322..e8085b564 100644 --- a/port/src/optionsmenu.c +++ b/port/src/optionsmenu.c @@ -652,6 +652,19 @@ static MenuItemHandlerResult menuhandlerFullScreen(s32 operation, struct menuite return 0; } +static MenuItemHandlerResult menuhandlerMaximizeWindow(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GET: + return videoGetMaximizeWindow(); + case MENUOP_SET: + videoSetMaximizeWindow(data->checkbox.value); + break; + } + + return 0; +} + static MenuItemHandlerResult menuhandlerTexFilter(s32 operation, struct menuitem *item, union handlerdata *data) { switch (operation) { @@ -678,6 +691,19 @@ static MenuItemHandlerResult menuhandlerTexFilter2D(s32 operation, struct menuit return 0; } +static MenuItemHandlerResult menuhandlerGeMuzzleFlashes(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GET: + return g_BgunGeMuzzleFlashes; + case MENUOP_SET: + g_BgunGeMuzzleFlashes = data->checkbox.value; + break; + } + + return 0; +} + static MenuItemHandlerResult menuhandlerCenterHUD(s32 operation, struct menuitem *item, union handlerdata *data) { static const char *opts[] = { @@ -735,6 +761,14 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerFullScreen, }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Maximize Window", + 0, + menuhandlerMaximizeWindow, + }, { MENUITEMTYPE_CHECKBOX, 0, @@ -751,6 +785,14 @@ struct menuitem g_ExtendedVideoMenuItems[] = { 0, menuhandlerTexFilter2D, }, + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"GE64-style Muzzle Flashes", + 0, + menuhandlerGeMuzzleFlashes, + }, { MENUITEMTYPE_DROPDOWN, 0, @@ -903,6 +945,202 @@ static MenuItemHandlerResult menuhandlerCrosshairSway(s32 operation, struct menu return 0; } +static MenuItemHandlerResult menuhandlerCrosshairR(s32 operation, struct menuitem* item, union handlerdata* data) +{ + u32 newColor; + + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour >> 24) & 0xFF; + break; + + case MENUOP_SET: + newColor = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFFFF) | data->slider.value << 24; + g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour = newColor; + break; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerCrosshairG(s32 operation, struct menuitem* item, union handlerdata* data) +{ + u32 newColor; + + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour >> 16) & 0xFF; + break; + + case MENUOP_SET: + newColor = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFF00FFFF) | data->slider.value << 16; + g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour = newColor; + break; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerCrosshairB(s32 operation, struct menuitem* item, union handlerdata* data) +{ + u32 newColor; + + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour >> 8) & 0xFF; + break; + + case MENUOP_SET: + newColor = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFF00FF) | data->slider.value << 8; + g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour = newColor; + break; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerCrosshairA(s32 operation, struct menuitem* item, union handlerdata* data) +{ + u32 newColor; + + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFF; + break; + + case MENUOP_SET: + newColor = (g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour & 0xFFFFFF00) | data->slider.value; + g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour = newColor; + break; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerCrosshairColorPreview(s32 operation, struct menuitem* item, union handlerdata* data) +{ + if (operation == MENUOP_GETCOLOUR) { + data->label.colour1 = g_PlayerExtCfg[g_ExtMenuPlayer].crosshaircolour; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerCrosshairSize(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = g_PlayerExtCfg[g_ExtMenuPlayer].crosshairsize; + break; + case MENUOP_SET: + g_PlayerExtCfg[g_ExtMenuPlayer].crosshairsize = data->slider.value; + break; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerCrosshairHealth(s32 operation, struct menuitem *item, union handlerdata *data) +{ + static const char *opts[] = { + "Off", + "On (Green)", + "On (White)" + }; + + switch (operation) { + case MENUOP_GETOPTIONCOUNT: + data->dropdown.value = ARRAYCOUNT(opts); + break; + case MENUOP_GETOPTIONTEXT: + return (intptr_t)opts[data->dropdown.value]; + case MENUOP_SET: + g_PlayerExtCfg[g_ExtMenuPlayer].crosshairhealth = data->dropdown.value; + break; + case MENUOP_GETSELECTEDINDEX: + data->dropdown.value = g_PlayerExtCfg[g_ExtMenuPlayer].crosshairhealth; + } + + return 0; +} + +struct menuitem g_ExtendedGameCrosshairColourMenuItems[] = { + { + MENUITEMTYPE_SLIDER, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, + (uintptr_t)"Red", + 255, + menuhandlerCrosshairR, + }, + { + MENUITEMTYPE_SLIDER, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, + (uintptr_t)"Green", + 255, + menuhandlerCrosshairG, + }, + { + MENUITEMTYPE_SLIDER, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, + (uintptr_t)"Blue", + 255, + menuhandlerCrosshairB, + }, + { + MENUITEMTYPE_SLIDER, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, + (uintptr_t)"Alpha", + 255, + menuhandlerCrosshairA, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_COLORBOX, + 0, + 0, + 0, + 0, + menuhandlerCrosshairColorPreview, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CLOSESDIALOG, + L_OPTIONS_213, // "Back" + 0, + NULL, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_ExtendedGameCrosshairColourMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + (uintptr_t)"Crosshair Colour", + g_ExtendedGameCrosshairColourMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT, + NULL, +}; + struct menuitem g_ExtendedGameMenuItems[] = { { MENUITEMTYPE_DROPDOWN, @@ -928,6 +1166,30 @@ struct menuitem g_ExtendedGameMenuItems[] = { 20, menuhandlerCrosshairSway, }, + { + MENUITEMTYPE_SLIDER, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SLIDER_WIDE, + (uintptr_t)"Crosshair Size", + 4, + menuhandlerCrosshairSize, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT | MENUITEMFLAG_SELECTABLE_OPENSDIALOG, + (uintptr_t)"Crosshair Colour\n", + 0, + (void*)&g_ExtendedGameCrosshairColourMenuDialog, + }, + { + MENUITEMTYPE_DROPDOWN, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Crosshair Colour by Health", + 0, + menuhandlerCrosshairHealth, + }, { MENUITEMTYPE_SEPARATOR, 0, diff --git a/port/src/pdsched.c b/port/src/pdsched.c index b2e87c685..c62f8b424 100644 --- a/port/src/pdsched.c +++ b/port/src/pdsched.c @@ -105,6 +105,7 @@ bool g_SchedIsFirstTask = true; s32 g_PrevFrameFb = -1; s32 g_BlurFb = -1; s32 g_BlurFbCapTimer = -1; +bool g_BlurFbDirty = true; void schedSetCrashEnable1(bool enable) { @@ -396,8 +397,12 @@ void schedConsiderScreenshot(void) if (g_BlurFbCapTimer == 0) { videoCopyFramebuffer(g_BlurFb, 0, -1, -1); g_BlurFbCapTimer = -1; + g_BlurFbDirty = false; } else if (g_BlurFbCapTimer > 0) { --g_BlurFbCapTimer; + } else if (g_BlurFbCapTimer < 0) { + // no blur requested this frame, mark blur fb dirty + g_BlurFbDirty = true; } if (g_MenuData.screenshottimer >= 2) { diff --git a/port/src/romdata.c b/port/src/romdata.c index 08629ecf4..effc67e65 100644 --- a/port/src/romdata.c +++ b/port/src/romdata.c @@ -64,6 +64,13 @@ enum loadsource { SRC_EXTERNAL }; +struct romfilepatch { + u32 ofs; + u32 len; + const char *src; + const char *dst; +}; + struct romfile { u8 **segstart; u8 **segend; @@ -73,9 +80,21 @@ struct romfile { preprocessfunc preprocess; s32 source; // enum loadsource s32 preprocessed; + const struct romfilepatch *patches; + u32 numpatches; }; -static struct romfile fileSlots[ROMDATA_MAX_FILES]; +/* patches for individual files; applied on file load, before preprocFuncs, but */ +/* after unzip; only applied when loading from a ROM file */ +static const struct romfilepatch filePatches[] = { + /* FILE_USETUPLUE: fixes Jon's double "if what" in Infiltration outro */ + { 0x92a2, 1, "\x6c", "\x99" }, + { 0x92b0, 1, "\x6c", "\x99" }, +}; + +static struct romfile fileSlots[ROMDATA_MAX_FILES] = { + [FILE_USETUPLUE] = { .patches = &filePatches[0], .numpatches = 2 }, +}; #define ROMSEG_START(n) _ ## n ## SegmentRomStart #define ROMSEG_END(n) _ ## n ## SegmentRomEnd @@ -409,6 +428,8 @@ u8 *romdataFileLoad(s32 fileNum, u32 *outSize) fileSlots[fileNum].data = out; fileSlots[fileNum].size = size; fileSlots[fileNum].source = SRC_EXTERNAL; + // external file; do not apply patches to this + fileSlots[fileNum].numpatches = 0; } } // tried and failed, fall back to ROM @@ -435,6 +456,15 @@ void romdataFilePreprocess(s32 fileNum, s32 loadType, u8 *data, u32 size) if (data && size /* && !fileSlots[fileNum].preprocessed*/) { if (loadType && loadType < (u32)ARRAYCOUNT(filePreprocFuncs) && filePreprocFuncs[loadType]) { + // apply patches + for (u32 i = 0; i < fileSlots[fileNum].numpatches; ++i) { + const struct romfilepatch *p = &fileSlots[fileNum].patches[i]; + if (!memcmp(data + p->ofs, p->src, p->len)) { + memcpy(data + p->ofs, p->dst, p->len); + sysLogPrintf(LOG_NOTE, "file %d (%s) patched at offset 0x%x", fileNum, fileSlots[fileNum].name, p->ofs); + } + } + // then preprocess filePreprocFuncs[loadType](data, size); // fileSlots[fileNum].preprocessed = 1; } diff --git a/port/src/system.c b/port/src/system.c index d01e88060..538dd1e87 100644 --- a/port/src/system.c +++ b/port/src/system.c @@ -11,6 +11,7 @@ #include "system.h" #define LOG_FNAME "pd.log" +#define CRASHLOG_FNAME "pd.crash.log" #define USEC_IN_SEC 1000000ULL static u64 startTick = 0; @@ -19,6 +20,24 @@ static char logPath[2048]; static s32 sysArgc; static const char **sysArgv; +static inline void sysLogSetPath(const char *fname) +{ + // figure out where the log is and clear it + // try working dir first + snprintf(logPath, sizeof(logPath), "./%s", fname); + FILE *f = fopen(logPath, "wb"); + if (!f) { + // try home dir + sysGetHomePath(logPath, sizeof(logPath) - 1); + strncat(logPath, "/", sizeof(logPath) - 1); + strncat(logPath, fname, sizeof(logPath) - 1); + f = fopen(logPath, "wb"); + } + if (f) { + fclose(f); + } +} + void sysInitArgs(s32 argc, const char **argv) { sysArgc = argc; @@ -30,23 +49,11 @@ void sysInit(void) startTick = sysGetMicroseconds(); if (sysArgCheck("--log")) { - // figure out where the log is and clear it - // try working dir first - snprintf(logPath, sizeof(logPath), "./%s", LOG_FNAME); - FILE *f = fopen(logPath, "wb"); - if (!f) { - // try home dir - sysGetHomePath(logPath, sizeof(logPath) - 1); - strncat(logPath, "/" LOG_FNAME, sizeof(logPath) - 1); - f = fopen(logPath, "wb"); - } - if (f) { - fclose(f); - } + sysLogSetPath(LOG_FNAME); } #ifdef VERSION_HASH - sysLogPrintf(LOG_NOTE, "version: " VERSION_HASH " (" VERSION_TARGET ")"); + sysLogPrintf(LOG_NOTE, "version: " VERSION_BRANCH " " VERSION_HASH " (" VERSION_TARGET ")"); #endif char timestr[256]; @@ -96,6 +103,11 @@ u64 sysGetMicroseconds(void) return ((u64)tv.tv_sec * USEC_IN_SEC + (u64)tv.tv_usec) - startTick; } +s32 sysLogIsOpen(void) +{ + return (logPath[0] != '\0'); +} + void sysLogPrintf(s32 level, const char *fmt, ...) { static const char *prefix[3] = { diff --git a/port/src/video.c b/port/src/video.c index 075546218..18da94fc0 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -21,6 +21,7 @@ static s32 vidWidth = 640; static s32 vidHeight = 480; static s32 vidFramebuffers = true; static s32 vidFullscreen = false; +static s32 vidMaximize = false; static s32 vidVsync = 1; static s32 vidMSAA = 1; static s32 vidFramerateLimit = 0; @@ -44,9 +45,17 @@ s32 videoInit(void) gfx_framebuffers_enabled = (bool)vidFramebuffers; gfx_msaa_level = vidMSAA; - gfx_init(wmAPI, renderingAPI, "PD", vidFullscreen, vidWidth, vidHeight, 100, 100); + gfx_init(wmAPI, renderingAPI, "PD", vidFullscreen, vidMaximize, vidWidth, vidHeight, 100, 100); + + if (!wmAPI->set_swap_interval(vidVsync)) { + vidVsync = 0; + } + + if (vidVsync == 0 && vidFramerateLimit == 0) { + // cap FPS if there's no vsync to prevent the game from exploding + vidFramerateLimit = VIDEO_MAX_FPS; + } - wmAPI->set_swap_interval(vidVsync); wmAPI->set_target_fps(vidFramerateLimit); // disabled because vsync is on renderingAPI->set_texture_filter((enum FilteringMode)texFilter); @@ -139,6 +148,11 @@ s32 videoGetFullscreen(void) return vidFullscreen; } +s32 videoGetMaximizeWindow(void) +{ + return vidMaximize; +} + f32 videoGetAspect(void) { return gfx_current_dimensions.aspect_ratio; @@ -168,6 +182,14 @@ void videoSetFullscreen(s32 fs) } } +void videoSetMaximizeWindow(s32 fs) +{ + if (fs != vidMaximize) { + vidMaximize = !!fs; + wmAPI->set_maximize(vidMaximize); + } +} + void videoSetTextureFilter(u32 filter) { if (filter > FILTER_THREE_POINT) filter = FILTER_THREE_POINT; @@ -224,11 +246,12 @@ void videoFreeCachedTexture(const void *texptr) PD_CONSTRUCTOR static void videoConfigInit(void) { configRegisterInt("Video.DefaultFullscreen", &vidFullscreen, 0, 1); + configRegisterInt("Video.DefaultMaximize", &vidMaximize, 0, 1); configRegisterInt("Video.DefaultWidth", &vidWidth, 0, 32767); configRegisterInt("Video.DefaultHeight", &vidHeight, 0, 32767); configRegisterInt("Video.VSync", &vidVsync, -1, 10); configRegisterInt("Video.FramebufferEffects", &vidFramebuffers, 0, 1); - configRegisterInt("Video.FramerateLimit", &vidFramerateLimit, 0, 10000); + configRegisterInt("Video.FramerateLimit", &vidFramerateLimit, 0, VIDEO_MAX_FPS); configRegisterInt("Video.MSAA", &vidMSAA, 1, 16); configRegisterInt("Video.TextureFilter", &texFilter, 0, 2); configRegisterInt("Video.TextureFilter2D", &texFilter2D, 0, 1); diff --git a/src/game/activemenu.c b/src/game/activemenu.c index 917504112..bc984de99 100644 --- a/src/game/activemenu.c +++ b/src/game/activemenu.c @@ -90,19 +90,8 @@ MenuItemHandlerResult amPickTargetMenuList(s32 operation, struct menuitem *item, switch (operation) { case MENUOP_GETOPTIONCOUNT: if (g_AmMenus[g_AmIndex].prevallbots) { - // All bots: num bot opponents + all human players - s32 count = 0; - s32 i; - struct chrdata *playerchr = g_Vars.currentplayer->prop->chr; - - for (i = PLAYERCOUNT(); i < g_MpNumChrs; i++) { - if (g_MpAllChrPtrs[i]->team != playerchr->team) { - count++; - } - } - - count += PLAYERCOUNT(); - data->list.value = count; + // All bots + data->list.value = g_MpNumChrs; } else { // Single bot: All except the bot itself data->list.value = g_MpNumChrs - 1; @@ -125,9 +114,7 @@ MenuItemHandlerResult amPickTargetMenuList(s32 operation, struct menuitem *item, chrindex++; if (g_AmMenus[g_AmIndex].prevallbots) { - if (playerchr == g_MpAllChrPtrs[chrindex] || playerchr->team != g_MpAllChrPtrs[chrindex]->team) { - numremaining--; - } + numremaining--; } else { if (botchr != g_MpAllChrPtrs[chrindex]) { numremaining--; @@ -139,7 +126,9 @@ MenuItemHandlerResult amPickTargetMenuList(s32 operation, struct menuitem *item, g_AmMenus[g_AmIndex].prevallbots = false; for (i = 0; i < g_Vars.currentplayer->numaibuddies; i++) { - botApplyAttack(g_MpAllChrPtrs[g_Vars.currentplayer->aibuddynums[i]], g_MpAllChrPtrs[chrindex]->prop); + if (g_Vars.currentplayer->aibuddynums[i] != chrindex) { + botApplyAttack(g_MpAllChrPtrs[g_Vars.currentplayer->aibuddynums[i]], g_MpAllChrPtrs[chrindex]->prop); + } } } else { botApplyAttack(botchr, g_MpAllChrPtrs[chrindex]->prop); @@ -167,9 +156,7 @@ MenuItemHandlerResult amPickTargetMenuList(s32 operation, struct menuitem *item, chrindex++; if (g_AmMenus[g_AmIndex].prevallbots) { - if (playerchr == g_MpAllChrPtrs[chrindex] || playerchr->team != g_MpAllChrPtrs[chrindex]->team) { - numremaining--; - } + numremaining--; } else { if (botchr != g_MpAllChrPtrs[chrindex]) { numremaining--; @@ -1275,7 +1262,8 @@ Gfx *amRender(Gfx *gdl) #endif #ifndef PLATFORM_N64 - if (PLAYERCOUNT() == 1) { + const s32 playercount = PLAYERCOUNT(); + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); } #endif diff --git a/src/game/bondeyespy.c b/src/game/bondeyespy.c index 1250f1f0a..cd15f48e3 100644 --- a/src/game/bondeyespy.c +++ b/src/game/bondeyespy.c @@ -1098,7 +1098,13 @@ void eyespyProcessInput(bool allowbuttons) if (g_Vars.currentplayer->eyespy->bobactive || ABS(g_Vars.currentplayer->eyespy->vel.y) < 0.1f) { g_Vars.currentplayer->eyespy->bobactive = true; g_Vars.currentplayer->eyespy->bobtimer += g_Vars.lvupdate60; +#ifdef PLATFORM_N64 g_Vars.currentplayer->eyespy->vel.y += 0.025f * g_Vars.currentplayer->eyespy->bobdir; +#else + // HACK: how do I scale this properly? + const f32 scale = (g_Vars.lvupdate60freal <= 1.1f) ? 0.0055f : 0.0125f; + g_Vars.currentplayer->eyespy->vel.y += scale * g_Vars.lvupdate60freal * g_Vars.currentplayer->eyespy->bobdir; +#endif if (g_Vars.currentplayer->eyespy->bobtimer > TICKS(120)) { g_Vars.currentplayer->eyespy->bobtimer = 0; diff --git a/src/game/bondgun.c b/src/game/bondgun.c index b63ab0d2c..67825c973 100644 --- a/src/game/bondgun.c +++ b/src/game/bondgun.c @@ -190,6 +190,10 @@ char var800700bc[][10] = { { 'x','x','x' }, // "xxx" }; +#ifndef PLATFORM_N64 +s32 g_BgunGeMuzzleFlashes = false; +#endif + #if !MATCHING || VERSION >= VERSION_NTSC_1_0 void bgunRumble(s32 handnum, s32 weaponnum) { @@ -1911,7 +1915,17 @@ void bgun0f09a6f8(struct handweaponinfo *info, s32 handnum, struct hand *hand, s if (func->flags & FUNCFLAG_NOMUZZLEFLASH) { hand->flashon = false; } else { +#ifdef PLATFORM_N64 hand->flashon = true; +#else + if (g_BgunGeMuzzleFlashes) { + if (func->type == INVENTORYFUNCTYPE_SHOOT_SINGLE || (hand->shotstotake & 1)) { + hand->flashon = true; + } + } else { + hand->flashon = true; + } +#endif } bgunStartSlide(handnum); @@ -12783,7 +12797,7 @@ Gfx *bgunDrawHud(Gfx *gdl) gdl = text0f153628(gdl); #ifndef PLATFORM_N64 - if (playercount < 2) { + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPExtraGeometryModeEXT(gdl++, G_ASPECT_MODE_EXT, g_HudAlignModeR); } #endif @@ -13037,7 +13051,7 @@ Gfx *bgunDrawHud(Gfx *gdl) } #ifndef PLATFORM_N64 - if (playercount < 2) { + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPExtraGeometryModeEXT(gdl++, G_ASPECT_MODE_EXT, g_HudAlignModeL); } #endif @@ -13077,7 +13091,7 @@ Gfx *bgunDrawHud(Gfx *gdl) } #ifndef PLATFORM_N64 - if (playercount < 2) { + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPExtraGeometryModeEXT(gdl++, G_ASPECT_MODE_EXT, g_HudAlignModeR); } #endif diff --git a/src/game/bondview.c b/src/game/bondview.c index 2a086c614..af101fa37 100644 --- a/src/game/bondview.c +++ b/src/game/bondview.c @@ -289,6 +289,14 @@ Gfx *bviewDrawMotionBlur(Gfx *gdl, u32 colour, u32 alpha) if (!videoFramebuffersSupported()) { return gdl; } + + // capture fb at the end of this frame + g_BlurFbCapTimer = 0; + + // don't render first blur frame as we haven't captured the fb yet + if (g_BlurFbDirty) { + return gdl; + } #endif mainOverrideVariable("sfxxx", &sfxxx); @@ -312,7 +320,6 @@ Gfx *bviewDrawMotionBlur(Gfx *gdl, u32 colour, u32 alpha) viewleft << 2, viewtop << 2, viewleft, viewtop, (viewleft + viewwidth) << 2, (viewtop + viewheight) << 2, viewleft + viewwidth, viewtop + viewheight, 0, videoGetNativeWidth(), videoGetNativeHeight()); - g_BlurFbCapTimer = 0; #endif return gdl; @@ -521,6 +528,14 @@ Gfx *bviewDrawZoomBlur(Gfx *gdl, u32 colour, s32 alpha, f32 arg3, f32 arg4) if (!videoFramebuffersSupported()) { return gdl; } + + // capture fb at the end of this frame + g_BlurFbCapTimer = 0; + + // don't render first blur frame as we haven't captured the fb yet + if (g_BlurFbDirty) { + return gdl; + } #endif strcpy(var800a41c0, "stretchBlurGfx"); @@ -550,7 +565,6 @@ Gfx *bviewDrawZoomBlur(Gfx *gdl, u32 colour, s32 alpha, f32 arg3, f32 arg4) left << 2, top << 2, viewleft, viewtop, right << 2, bottom << 2, viewleft + viewwidth, viewtop + viewheight, 0, videoGetNativeWidth(), videoGetNativeHeight()); - g_BlurFbCapTimer = 0; #endif return gdl; diff --git a/src/game/bot.c b/src/game/bot.c index 0658a1ce3..826bc2a51 100644 --- a/src/game/bot.c +++ b/src/game/bot.c @@ -260,6 +260,17 @@ void botSpawn(struct chrdata *chr, u8 respawning) chr->aibot->moveratex = 0; chr->aibot->moveratey = 0; func0f02e9a0(chr, 0); + +#ifndef PLATFORM_N64 + if ((g_MpSetup.options & MPOPTION_SPAWNWITHWEAPON) + && g_MpSetup.weapons[0] != MPWEAPON_NONE + && g_MpSetup.weapons[0] != MPWEAPON_DISABLED) { + struct mpweapon *mpweapon = &g_MpWeapons[g_MpSetup.weapons[0]]; + botinvGiveSingleWeapon(chr, mpweapon->weaponnum); + botactGiveAmmoByType(aibot, mpweapon->priammotype - 1, mpweapon->priammoqty / 2); + botinvSwitchToWeapon(chr, mpweapon->weaponnum, FUNC_PRIMARY); + } +#endif } } diff --git a/src/game/filemgr.c b/src/game/filemgr.c index 6d7184cf1..aac819ad1 100644 --- a/src/game/filemgr.c +++ b/src/game/filemgr.c @@ -2592,6 +2592,16 @@ MenuItemHandlerResult filemgrChooseAgentListMenuHandler(s32 operation, struct me } } } + +#ifndef PLATFORM_N64 + if (g_FileAutoSelect >= 0 && g_FileLists[0] && g_FileLists[0]->numfiles > 0) { + const u32 tmp = data->list.value; + data->list.value = g_FileAutoSelect; + g_FileAutoSelect = -1; + filemgrChooseAgentListMenuHandler(MENUOP_SET, item, data); + data->list.value = tmp; + } +#endif break; case MENUOP_GETOPTIONCOUNT: data->list.value = g_FileLists[0]->numfiles + 1; diff --git a/src/game/game_0b0fd0.c b/src/game/game_0b0fd0.c index 8c7953ccf..719959461 100644 --- a/src/game/game_0b0fd0.c +++ b/src/game/game_0b0fd0.c @@ -398,9 +398,11 @@ u16 weaponGetFileNum(s32 weaponnum) } if (weapon) { +#ifdef PLATFORM_N64 if (PLAYERCOUNT() >= 2) { return weapon->lo_model; } +#endif return weapon->hi_model; } diff --git a/src/game/hudmsg.c b/src/game/hudmsg.c index 650906509..6cbf73408 100644 --- a/src/game/hudmsg.c +++ b/src/game/hudmsg.c @@ -190,7 +190,7 @@ Gfx *hudmsgRenderMissionTimer(Gfx *gdl, u32 alpha) y = timery; #ifndef PLATFORM_N64 - if (PLAYERCOUNT() < 2) { + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPExtraGeometryModeEXT(gdl++, G_ASPECT_MODE_EXT, g_HudAlignModeL); } #endif @@ -282,7 +282,7 @@ Gfx *hudmsgRenderZoomRange(Gfx *gdl, u32 alpha) y2 = y + textheight; #ifndef PLATFORM_N64 - if (playercount == 1) { + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); } #endif @@ -1450,7 +1450,8 @@ Gfx *hudmsgsRender(Gfx *gdl) } #ifndef PLATFORM_N64 - if (playercount < 2 && msg->state >= HUDMSGSTATE_FADINGIN) { + const bool doaspectfix = (playercount < 2) || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL); + if (doaspectfix && msg->state >= HUDMSGSTATE_FADINGIN) { if (msg->alignh == HUDMSGALIGN_SCREENLEFT || msg->alignh == HUDMSGALIGN_LEFT) { gSPExtraGeometryModeEXT(gdl++, G_ASPECT_MODE_EXT, g_HudAlignModeL); } else if (msg->alignh == HUDMSGALIGN_RIGHT) { diff --git a/src/game/menu.c b/src/game/menu.c index b63ad6315..46e743aca 100644 --- a/src/game/menu.c +++ b/src/game/menu.c @@ -1178,6 +1178,9 @@ bool menuIsItemFocusable(struct menuitem *item, struct menudialog *dialog, s32 a case MENUITEMTYPE_METER: case MENUITEMTYPE_MARQUEE: case MENUITEMTYPE_CONTROLLER: +#ifndef PLATFORM_N64 + case MENUITEMTYPE_COLORBOX: +#endif return false; case MENUITEMTYPE_10: case MENUITEMTYPE_14: diff --git a/src/game/menuitem.c b/src/game/menuitem.c index 11e53274d..0f5d68b6b 100644 --- a/src/game/menuitem.c +++ b/src/game/menuitem.c @@ -2109,6 +2109,36 @@ Gfx *menuitemMeterRender(Gfx *gdl, struct menurendercontext *context) return gdl; } +#ifndef PLATFORM_N64 + +// Draws a colored box which fills the background of the menu item. +Gfx* menuitemColorBoxRender(Gfx *gdl, struct menurendercontext *context) +{ + u32 width = context->width; + u32 height = context->height; + u32 colour1; + s32 x1; + s32 x2; + + x1 = context->x; + x2 = x1 + width; + + union handlerdata data; + if (context->item->handlervoid) { + context->item->handlervoid(MENUOP_GETCOLOUR, context->item, &data); + } + + colour1 = data.label.colour1; + + gdl = textSetPrimColour(gdl, colour1); + gDPFillRectangleScaled(gdl++, x1, context->y, x2, context->y + height); + gdl = text0f153838(gdl); + + return gdl; +} + +#endif + Gfx *menuitemSelectableRender(Gfx *gdl, struct menurendercontext *context) { u32 leftcolour; @@ -4349,6 +4379,11 @@ Gfx *menuitemRender(Gfx *gdl, struct menurendercontext *context) case MENUITEMTYPE_CAROUSEL: return menuitemCarouselRender(gdl, context); case MENUITEMTYPE_MODEL: return menuitemModelRender(gdl, context); case MENUITEMTYPE_CONTROLLER: return menuitemControllerRender(gdl, context); + +#ifndef PLATFORM_N64 + case MENUITEMTYPE_COLORBOX: return menuitemColorBoxRender(gdl, context); +#endif + } return gdl; diff --git a/src/game/mplayer/mplayer.c b/src/game/mplayer/mplayer.c index d91f60a4e..9aca1a2a2 100644 --- a/src/game/mplayer/mplayer.c +++ b/src/game/mplayer/mplayer.c @@ -122,6 +122,9 @@ struct mpweapon g_MpWeapons[NUM_MPWEAPONS] = { .crosshairsway = 1.f, \ .crouchmode = CROUCHMODE_TOGGLE_ANALOG, \ .extcontrols = true, \ + .crosshaircolour = 0x00ff0028, \ + .crosshairsize = 2, \ + .crosshairhealth = CROSSHAIR_HEALTH_OFF, \ } struct extplayerconfig g_PlayerExtCfg[MAX_PLAYERS] = { @@ -2717,6 +2720,11 @@ struct mptrack g_MpTracks[] = { /*0x27*/ { MUSIC_SKEDARRUINS, 120, L_MISC_163, SOLOSTAGEINDEX_SKEDARRUINS }, // "Skedar Ruins" /*0x28*/ { MUSIC_SKEDARRUINS_X, 120, L_MISC_164, SOLOSTAGEINDEX_SKEDARRUINS }, // "Skedar Ruins X" /*0x29*/ { MUSIC_CREDITS, 120, L_MISC_165, SOLOSTAGEINDEX_SKEDARRUINS }, // "End Credits" +#if VERSION < VERSION_PAL_BETA + /*0x2a*/ { MUSIC_SKEDARRUINS_KING,120, L_MISC_261, SOLOSTAGEINDEX_SKEDARRUINS }, // "Skedar Warrior" (Skedar Leader) +#else + /*0x2a*/ { MUSIC_SKEDARRUINS_KING,120, L_MISC_041, SOLOSTAGEINDEX_SKEDARRUINS }, // "E R R O R" (can't find a good approximation for Skedar Leader) +#endif }; bool mpIsTrackUnlocked(s32 tracknum) diff --git a/src/game/mplayer/scenarios/capturethecase.inc b/src/game/mplayer/scenarios/capturethecase.inc index b701a69c2..0e82d542a 100644 --- a/src/game/mplayer/scenarios/capturethecase.inc +++ b/src/game/mplayer/scenarios/capturethecase.inc @@ -60,6 +60,16 @@ struct menuitem g_CtcOptionsMenuItems[] = { MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption, }, +#ifndef PLATFORM_N64 + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LOCKABLEMINOR | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Spawn With Weapon", + MPOPTION_SPAWNWITHWEAPON, + menuhandlerMpCheckboxOption, + }, +#endif { MENUITEMTYPE_CHECKBOX, 0, diff --git a/src/game/mplayer/scenarios/combat.inc b/src/game/mplayer/scenarios/combat.inc index 74f4c6ad3..7d096a55c 100644 --- a/src/game/mplayer/scenarios/combat.inc +++ b/src/game/mplayer/scenarios/combat.inc @@ -55,6 +55,16 @@ struct menuitem g_MpCombatOptionsMenuItems[] = { MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption, }, +#ifndef PLATFORM_N64 + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LOCKABLEMINOR | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Spawn With Weapon", + MPOPTION_SPAWNWITHWEAPON, + menuhandlerMpCheckboxOption, + }, +#endif { MENUITEMTYPE_SEPARATOR, 0, diff --git a/src/game/mplayer/scenarios/hackthatmac.inc b/src/game/mplayer/scenarios/hackthatmac.inc index d065929ec..1ca58458a 100644 --- a/src/game/mplayer/scenarios/hackthatmac.inc +++ b/src/game/mplayer/scenarios/hackthatmac.inc @@ -58,6 +58,16 @@ struct menuitem g_HtmOptionsMenuItems[] = { MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption, }, +#ifndef PLATFORM_N64 + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LOCKABLEMINOR | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Spawn With Weapon", + MPOPTION_SPAWNWITHWEAPON, + menuhandlerMpCheckboxOption, + }, +#endif { MENUITEMTYPE_CHECKBOX, 0, diff --git a/src/game/mplayer/scenarios/holdthebriefcase.inc b/src/game/mplayer/scenarios/holdthebriefcase.inc index 53de09f69..fe9cc0bd6 100644 --- a/src/game/mplayer/scenarios/holdthebriefcase.inc +++ b/src/game/mplayer/scenarios/holdthebriefcase.inc @@ -58,6 +58,16 @@ struct menuitem g_HtbOptionsMenuItems[] = { MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption, }, +#ifndef PLATFORM_N64 + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LOCKABLEMINOR | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Spawn With Weapon", + MPOPTION_SPAWNWITHWEAPON, + menuhandlerMpCheckboxOption, + }, +#endif { MENUITEMTYPE_CHECKBOX, 0, @@ -417,7 +427,8 @@ Gfx *htbRenderHud(Gfx *gdl) #endif #ifndef PLATFORM_N64 - if (PLAYERCOUNT() == 1) { + const s32 playercount = PLAYERCOUNT(); + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); } #endif diff --git a/src/game/mplayer/scenarios/kingofthehill.inc b/src/game/mplayer/scenarios/kingofthehill.inc index ed53d37f8..120024f19 100644 --- a/src/game/mplayer/scenarios/kingofthehill.inc +++ b/src/game/mplayer/scenarios/kingofthehill.inc @@ -79,6 +79,16 @@ struct menuitem g_KohOptionsMenuItems[] = { MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption, }, +#ifndef PLATFORM_N64 + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LOCKABLEMINOR | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Spawn With Weapon", + MPOPTION_SPAWNWITHWEAPON, + menuhandlerMpCheckboxOption, + }, +#endif { MENUITEMTYPE_CHECKBOX, 0, @@ -583,7 +593,8 @@ Gfx *kohRenderHud(Gfx *gdl) } #ifndef PLATFORM_N64 - if (PLAYERCOUNT() == 1) { + const s32 playercount = PLAYERCOUNT(); + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); } #endif diff --git a/src/game/mplayer/scenarios/popacap.inc b/src/game/mplayer/scenarios/popacap.inc index 4e499c87e..7da8ed521 100644 --- a/src/game/mplayer/scenarios/popacap.inc +++ b/src/game/mplayer/scenarios/popacap.inc @@ -56,6 +56,16 @@ struct menuitem g_PacOptionsMenuItems[] = { MPOPTION_NOAUTOAIM, menuhandlerMpCheckboxOption, }, +#ifndef PLATFORM_N64 + { + MENUITEMTYPE_CHECKBOX, + 0, + MENUITEMFLAG_LOCKABLEMINOR | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Spawn With Weapon", + MPOPTION_SPAWNWITHWEAPON, + menuhandlerMpCheckboxOption, + }, +#endif { MENUITEMTYPE_CHECKBOX, 0, @@ -312,7 +322,8 @@ Gfx *pacRenderHud(Gfx *gdl) sprintf(text, "%d:%02d", mins, secs); #ifndef PLATFORM_N64 - if (PLAYERCOUNT() == 1) { + const s32 playercount = PLAYERCOUNT(); + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); } #endif diff --git a/src/game/player.c b/src/game/player.c index b1ab68c40..5a18b139e 100644 --- a/src/game/player.c +++ b/src/game/player.c @@ -1100,8 +1100,21 @@ void playerSpawn(void) g_Vars.currentplayer->prop->chr->blurnumtimesdied = 0; } } else { - bgunEquipWeapon2(HAND_LEFT, g_DefaultWeapons[HAND_LEFT]); - bgunEquipWeapon2(HAND_RIGHT, g_DefaultWeapons[HAND_RIGHT]); +#ifndef PLATFORM_N64 + if ((g_MpSetup.options & MPOPTION_SPAWNWITHWEAPON) + && g_MpSetup.weapons[0] != MPWEAPON_NONE + && g_MpSetup.weapons[0] != MPWEAPON_DISABLED) { + struct mpweapon *mpweapon = &g_MpWeapons[g_MpSetup.weapons[0]]; + invGiveSingleWeapon(mpweapon->weaponnum); + bgunEquipWeapon2(HAND_LEFT, WEAPON_NONE); + bgunEquipWeapon2(HAND_RIGHT, mpweapon->weaponnum); + bgunSetAmmoQuantity(mpweapon->priammotype, mpweapon->priammoqty / 2); + } else +#endif + { + bgunEquipWeapon2(HAND_LEFT, g_DefaultWeapons[HAND_LEFT]); + bgunEquipWeapon2(HAND_RIGHT, g_DefaultWeapons[HAND_RIGHT]); + } #if VERSION >= VERSION_NTSC_1_0 if (g_Vars.currentplayer->model00d4 == NULL diff --git a/src/game/propobj.c b/src/game/propobj.c index c3bf9b5bc..2ca4c6a98 100644 --- a/src/game/propobj.c +++ b/src/game/propobj.c @@ -20997,7 +20997,7 @@ Gfx *countdownTimerRender(Gfx *gdl) ms = (s32)floorf(value60 * 1.6666666269302f) - mins * 6000 - secs * 100; #ifndef PLATFORM_N64 - if (playercount == 1) { + if (playercount < 2 || (playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL)) { gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); } #endif diff --git a/src/game/radar.c b/src/game/radar.c index e963747ba..a98ae6764 100644 --- a/src/game/radar.c +++ b/src/game/radar.c @@ -312,6 +312,11 @@ Gfx *radarRender(Gfx *gdl) } else if (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL && playernum == 1) { g_RadarY -= 8; } +#ifndef PLATFORM_N64 + if (optionsGetScreenSplit() == SCREENSPLIT_HORIZONTAL) { + gSPExtraGeometryModeEXT(gdl++, G_ASPECT_MODE_EXT, g_HudAlignModeR); + } +#endif } else if (playercount >= 3) { if (playernum >= 2) { g_RadarY -= 8; diff --git a/src/game/sight.c b/src/game/sight.c index 8bf2f4e6c..cc55ffc59 100644 --- a/src/game/sight.c +++ b/src/game/sight.c @@ -23,9 +23,58 @@ #include #include "video.h" +#define SIGHT_COLOUR ((PLAYER_EXTCFG().crosshairhealth >= CROSSHAIR_HEALTH_ON_GREEN) ? sightGetCrosshairHealthColor(g_Vars.currentplayer->bondhealth, g_Vars.currentplayer->prop->chr->cshield * 0.125f) : PLAYER_EXTCFG().crosshaircolour) +#define SIGHT_SCALE PLAYER_EXTCFG().crosshairsize + +static u32 sightGetCrosshairHealthColor(float health, float shield) +{ + const float ratio = MAX(0.0f, MIN(health + shield, 2.0f)); + + int red = 0; + int green = 0; + int blue = 0; + if (ratio < 0.2f) { + // Red (critical health level) + red = 255; + green = 0; + blue = 0; + } else if (ratio < 0.6f) { + // Red-yellow + red = 255; + green = 255 * ((ratio - 0.2f) / 0.4f); + blue = 0; + } else if (ratio < 1.0f) { + if (PLAYER_EXTCFG().crosshairhealth == CROSSHAIR_HEALTH_ON_GREEN) { + // Yellow-green + red = 255 * ((ratio - 0.6f) / 0.4f); + green = 255; + blue = 0; + } else { + // Yellow-white + red = 255; + green = 255; + blue = 255 * ((ratio - 0.6f) / 0.4f); + } + } else { + if (PLAYER_EXTCFG().crosshairhealth == CROSSHAIR_HEALTH_ON_GREEN) { + // Green-cyan (overheal via shield) + red = 0; + green = 255; + blue = 255 * (ratio - 1.0f); + } else { + // White-green (overheal via shield) + red = 255 * (2.0f - ratio); + green = 255; + blue = 255 * (2.0f - ratio); + } + } + + return (red << 24) + (green << 16) + (blue << 8) + (PLAYER_EXTCFG().crosshaircolour & 0xff); +} + static inline f32 sightGetScaleX(void) { - return (videoGetAspect() / (4.f / 3.f)); + return (videoGetAspect() / SCREEN_ASPECT); } static inline s32 sightGetAdjustedX(const f32 x) @@ -36,6 +85,8 @@ static inline s32 sightGetAdjustedX(const f32 x) #else +#define SIGHT_COLOUR 0x00ff0028 +#define SIGHT_SCALE 2 #define sightGetScaleX() 1.f #define sightGetAdjustedX(x) (x) @@ -459,7 +510,7 @@ Gfx *sightDrawAimer(Gfx *gdl, s32 x, s32 y, s32 radius, s32 cornergap, u32 colou s32 viewright = viewleft + viewwidth - 1; s32 viewbottom = viewtop + viewheight - 1; - gdl = textSetPrimColour(gdl, 0x00ff0028); + gdl = textSetPrimColour(gdl, SIGHT_COLOUR); #ifndef PLATFORM_N64 x = sightGetAdjustedX(x); @@ -619,7 +670,7 @@ Gfx *sightDrawDelayedAimer(Gfx *gdl, s32 x, s32 y, s32 radius, s32 cornergap, u3 boxx = xpos; boxy = ypos; - gdl = textSetPrimColour(gdl, 0x00ff0028); + gdl = textSetPrimColour(gdl, SIGHT_COLOUR); // Fill a 3x3 box at the live crosshair gDPHudRectangle(gdl++, x - 1, y - 1, x + 1, y - 1); @@ -677,7 +728,7 @@ Gfx *sightDrawDefault(Gfx *gdl, bool sighton) // SIGHTTRACKTYPE_NONE is used for unarmed, but this appears to be // unreachable. The aimer is never drawn when unarmed. if (sighton) { - colour = 0x00ff0028; + colour = SIGHT_COLOUR; radius = 8; cornergap = 5; gdl = sightDrawAimer(gdl, x, y, radius, cornergap, colour); @@ -687,7 +738,7 @@ Gfx *sightDrawDefault(Gfx *gdl, bool sighton) // For most guns, render the aimer if holding R if (sighton) { if (g_Vars.currentplayer->lookingatprop.prop == NULL) { - colour = 0x00ff0028; + colour = SIGHT_COLOUR; radius = 8; cornergap = 5; } else { @@ -716,7 +767,7 @@ Gfx *sightDrawDefault(Gfx *gdl, bool sighton) s32 texty; if (g_Vars.currentplayer->lookingatprop.prop == NULL) { - colour = 0x00ff0028; + colour = SIGHT_COLOUR; radius = 8; cornergap = 5; } else { @@ -761,7 +812,7 @@ Gfx *sightDrawDefault(Gfx *gdl, bool sighton) if (sighton) { if (g_Vars.currentplayer->lookingatprop.prop == NULL) { - colour = 0x00ff0028; + colour = SIGHT_COLOUR; radius = 8; cornergap = 5; } else { @@ -832,7 +883,7 @@ Gfx *sightDrawDefault(Gfx *gdl, bool sighton) if (sighton) { if (g_Vars.currentplayer->lookingatprop.prop == NULL) { - colour = 0x00ff0028; + colour = SIGHT_COLOUR; radius = 8; cornergap = 5; } else { @@ -1252,7 +1303,7 @@ Gfx *sightDrawZoom(Gfx *gdl, bool sighton) if (showzoomrange) { gdl = text0f153628(gdl); - gdl = textSetPrimColour(gdl, 0x00ff0028); + gdl = textSetPrimColour(gdl, SIGHT_COLOUR); if (frac < 0.2f) { cornerwidth *= 0.2f; @@ -1436,7 +1487,7 @@ Gfx *sightDrawMaian(Gfx *gdl, bool sighton) gSPTri4(gdl++, 0, 4, 5, 5, 3, 6, 7, 6, 1, 4, 7, 2); gdl = func0f0d49c8(gdl); - gdl = textSetPrimColour(gdl, 0x00ff0028); + gdl = textSetPrimColour(gdl, SIGHT_COLOUR); // Draw border over inner points gDPHudRectangle(gdl++, x - 4, y - 4, x - 4, y + 4); // left @@ -1464,20 +1515,25 @@ Gfx *sightDrawTarget(Gfx *gdl) mainOverrideVariable("sout", &var80070f9c); mainOverrideVariable("sin", &var80070fa0); - gdl = textSetPrimColour(gdl, 0x00ff0028); + gdl = textSetPrimColour(gdl, SIGHT_COLOUR); #ifndef PLATFORM_N64 gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); + if (SIGHT_SCALE == 0) { + // Draw single rectangle to preserve intended opacity + gDPHudRectangle(gdl++, x, y, x, y); + } else #endif - - gDPHudRectangle(gdl++, x + 2, y + 0, x + 6, y + 0); - gDPHudRectangle(gdl++, x + 2, y + 0, x + 4, y + 0); - gDPHudRectangle(gdl++, x - 6, y + 0, x - 2, y + 0); - gDPHudRectangle(gdl++, x - 4, y + 0, x - 2, y + 0); - gDPHudRectangle(gdl++, x + 0, y + 2, x + 0, y + 6); - gDPHudRectangle(gdl++, x + 0, y + 2, x + 0, y + 4); - gDPHudRectangle(gdl++, x + 0, y - 6, x + 0, y - 2); - gDPHudRectangle(gdl++, x + 0, y - 4, x + 0, y - 2); + { + gDPHudRectangle(gdl++, x + 1 * SIGHT_SCALE, y + 0 * SIGHT_SCALE, x + 3 * SIGHT_SCALE, y + 0 * SIGHT_SCALE); + gDPHudRectangle(gdl++, x + 1 * SIGHT_SCALE, y + 0 * SIGHT_SCALE, x + 2 * SIGHT_SCALE, y + 0 * SIGHT_SCALE); + gDPHudRectangle(gdl++, x - 3 * SIGHT_SCALE, y + 0 * SIGHT_SCALE, x - 1 * SIGHT_SCALE, y + 0 * SIGHT_SCALE); + gDPHudRectangle(gdl++, x - 2 * SIGHT_SCALE, y + 0 * SIGHT_SCALE, x - 1 * SIGHT_SCALE, y + 0 * SIGHT_SCALE); + gDPHudRectangle(gdl++, x + 0 * SIGHT_SCALE, y + 1 * SIGHT_SCALE, x + 0 * SIGHT_SCALE, y + 3 * SIGHT_SCALE); + gDPHudRectangle(gdl++, x + 0 * SIGHT_SCALE, y + 1 * SIGHT_SCALE, x + 0 * SIGHT_SCALE, y + 2 * SIGHT_SCALE); + gDPHudRectangle(gdl++, x + 0 * SIGHT_SCALE, y - 3 * SIGHT_SCALE, x + 0 * SIGHT_SCALE, y - 1 * SIGHT_SCALE); + gDPHudRectangle(gdl++, x + 0 * SIGHT_SCALE, y - 2 * SIGHT_SCALE, x + 0 * SIGHT_SCALE, y - 1 * SIGHT_SCALE); + } #ifndef PLATFORM_N64 gSPClearExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); @@ -1526,6 +1582,13 @@ Gfx *sightDraw(Gfx *gdl, bool sighton, s32 sight) sight = SIGHT_DEFAULT; } +#ifndef PLATFORM_N64 + if (g_Vars.currentplayer->bondhealth <= 0.0f) { + // Hide crosshair during death animation + sight = SIGHT_NONE; + } +#endif + sightTick(sighton); switch (sight) { diff --git a/src/game/texreset.c b/src/game/texreset.c index 4bf444113..1daf8c737 100644 --- a/src/game/texreset.c +++ b/src/game/texreset.c @@ -139,6 +139,9 @@ void texReset(void) // get backend texture settings g_TexFilter2D = videoGetTextureFilter2D() ? G_TF_BILERP : G_TF_POINT; + + // notify blur code that the blur framebuffer is probably full of garbage + g_BlurFbDirty = true; #endif g_TexWords = mempAlloc(ALIGN16(g_TexNumConfigs * 4), MEMPOOL_STAGE); diff --git a/src/game/title.c b/src/game/title.c index 23f48290f..db1a78531 100644 --- a/src/game/title.c +++ b/src/game/title.c @@ -372,6 +372,8 @@ Gfx *titleRenderLegal(Gfx *gdl) elem->textptr = VERSION_HASH " (" VERSION_TARGET ")"; } else if (elem->textid == L_OPTIONS_083) { elem->textptr = VERSION_ROMID; + } else if (elem->textid == L_OPTIONS_082) { + elem->textptr = "Perfect Dark (" VERSION_BRANCH ")"; } #endif break; diff --git a/src/include/constants.h b/src/include/constants.h index 1e03e90b8..9cd1e9d2c 100644 --- a/src/include/constants.h +++ b/src/include/constants.h @@ -1692,6 +1692,10 @@ #define MENUITEMTYPE_CONTROLLER 0x19 #define MENUITEMTYPE_END 0x1a +#ifndef PLATFORM_N64 +#define MENUITEMTYPE_COLORBOX 0x1b +#endif + #define MENUMODELFLAG_HASSCALE 0x01 #define MENUMODELFLAG_HASPOSITION 0x02 #define MENUMODELFLAG_HASROTATION 0x04 @@ -2885,6 +2889,7 @@ #define MPOPTION_HTM_SHOWONRADAR 0x00040000 #define MPOPTION_PAC_HIGHLIGHTTARGET 0x00080000 #define MPOPTION_PAC_SHOWONRADAR 0x00100000 +#define MPOPTION_SPAWNWITHWEAPON 0x00200000 #define MPPAUSEMODE_UNPAUSED 0 #define MPPAUSEMODE_PAUSED 1 @@ -4714,6 +4719,10 @@ enum weaponnum { #define CROUCHMODE_TOGGLE 2 // press the crouch buttons to toggle stance #define CROUCHMODE_TOGGLE_ANALOG (CROUCHMODE_ANALOG | CROUCHMODE_TOGGLE) +#define CROSSHAIR_HEALTH_OFF 0 +#define CROSSHAIR_HEALTH_ON_GREEN 1 +#define CROSSHAIR_HEALTH_ON_WHITE 2 + #endif #endif diff --git a/src/include/data.h b/src/include/data.h index 0b93a521f..e513c6f10 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -545,8 +545,11 @@ extern u32 g_HudAlignModeR; extern s32 g_PrevFrameFb; extern s32 g_BlurFb; extern s32 g_BlurFbCapTimer; +extern bool g_BlurFbDirty; extern s32 g_TickRateDiv; extern s32 g_MusicDisableMpDeath; +extern s32 g_BgunGeMuzzleFlashes; +extern s32 g_FileAutoSelect; #define PLAYER_EXTCFG() g_PlayerExtCfg[g_Vars.currentplayerstats->mpindex & 3] #define PLAYER_DEFAULT_FOV (PLAYER_EXTCFG().fovy) diff --git a/src/include/game/menuitem.h b/src/include/game/menuitem.h index 27747bf71..79b868989 100644 --- a/src/include/game/menuitem.h +++ b/src/include/game/menuitem.h @@ -32,6 +32,10 @@ Gfx *menuitemLabelRender(Gfx *gdl, struct menurendercontext *context); Gfx *menuitemMeterRender(Gfx *gdl, struct menurendercontext *context); +#ifndef PLATFORM_N64 +Gfx *menuitemColorBoxRender(Gfx *gdl, struct menurendercontext *context); +#endif + Gfx *menuitemSelectableRender(Gfx *gdl, struct menurendercontext *context); bool menuitemSelectableTick(struct menuitem *item, struct menuinputs *inputs, u32 tickflags); diff --git a/src/include/types.h b/src/include/types.h index 67a8eddaf..0d772d798 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -6139,6 +6139,9 @@ struct extplayerconfig { f32 radialmenuspeed; f32 crosshairsway; s32 extcontrols; + u32 crosshaircolour; + u32 crosshairsize; + s32 crosshairhealth; }; #endif