diff --git a/CREDITS.md b/CREDITS.md index a5acc7320d..1f3020b9cc 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -756,4 +756,6 @@ This page lists all the individual contributions to the project by their author. - **Dmitry Volkov** - extensive and thorough testing - **Rise of the East community** - extensive playtesting of in-dev features - **11EJDE11** - Prevent mpdebug number from being drawn when visibility toggled off -- **CnCRazer** - Wall overlay unit sell exploit fix +- **RAZER**: + - Wall overlay unit sell exploit fix + - Multiplayer gamespeed fix for RealTimeTimers diff --git a/docs/Whats-New.md b/docs/Whats-New.md index cfe6320211..b4fad7dd45 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -638,6 +638,7 @@ Phobos fixes: - Fixed an issue where parasites that have infected infantry do not provide a refund when the infected infantry enters a Grinding building (by NetsuNegi) - Fixed the issue that `PassengerDeletion` dont consider passenger's passenger, parasite and hijacker (by NetsuNegi) - Fixed the issue that power output of building on tooltip won't consider power enhancer (by NetsuNegi) +- `RealTimeTimers` now support independent gamespeed index values for Multiplayer and Skirmish (by RAZER) Fixes / interactions with other extensions: diff --git a/src/Misc/Hooks.Timers.cpp b/src/Misc/Hooks.Timers.cpp index df650e5b11..68e5f9028d 100644 --- a/src/Misc/Hooks.Timers.cpp +++ b/src/Misc/Hooks.Timers.cpp @@ -16,35 +16,76 @@ DEFINE_HOOK(0x6D4B50, PrintTimerOnTactical_Start, 0x6) REF_STACK(int, value, STACK_OFFSET(0, 0x4)); TimerValueTemp::oldValue = value; + const bool isMP = SessionClass::IsMultiplayer(); + + // In SP/Skirmish, GameSpeed 0 is unlimited (no frame delay) so use adaptive FPS path. + // In MP, GameSpeed 0 is 60 FPS (valid fixed speed), so it must not enter this path. if (Phobos::Config::RealTimeTimers_Adaptive - || GameOptionsClass::Instance.GameSpeed == 0 - || (Phobos::Misc::CustomGS && !SessionClass::IsMultiplayer())) + || (!isMP && GameOptionsClass::Instance.GameSpeed == 0) + || (Phobos::Misc::CustomGS && !isMP)) { value = (int)((double)value / (std::max((double)FPSCounter::CurrentFrameRate, 1.0) / 15.0)); return 0; } - switch (GameOptionsClass::Instance.GameSpeed) + const int gs = GameOptionsClass::Instance.GameSpeed; + + if (isMP) + { + // MP GameSpeed indices (0-6): 60, 45, 30, 20, 15, 12, 10 FPS + // MP has an extra 45 FPS option (index 1) that SP/Skirmish does not. + switch (gs) + { + case 0: // 60 FPS + value = value / 4; + break; + case 1: // 45 FPS + value = value / 3; + break; + case 2: // 30 FPS + value = value / 2; + break; + case 3: // 20 FPS + value = (value * 3) / 4; + break; + case 4: // 15 FPS + break; + case 5: // 12 FPS + value = (value * 5) / 4; + break; + case 6: // 10 FPS + value = (value * 3) / 2; + break; + default: + break; + } + } + else { - case 1: // 60 FPS - value = value / 4; - break; - case 2: // 30 FPS - value = value / 2; - break; - case 3: // 20 FPS - value = (value * 3) / 4; - break; - case 4: // 15 FPS - break; - case 5: // 12 FPS - value = (value * 5) / 4; - break; - case 6: // 10 FPS - value = (value * 3) / 2; - break; - default: - break; + // SP/Skirmish GameSpeed indices (1-6): 60, 30, 20, 15, 12, 10 FPS + // Index 0 (unlimited) is already handled above via the adaptive path. + switch (gs) + { + case 1: // 60 FPS + value = value / 4; + break; + case 2: // 30 FPS + value = value / 2; + break; + case 3: // 20 FPS + value = (value * 3) / 4; + break; + case 4: // 15 FPS + break; + case 5: // 12 FPS + value = (value * 5) / 4; + break; + case 6: // 10 FPS + value = (value * 3) / 2; + break; + default: + break; + } } return 0;