Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -736,4 +736,6 @@ This page lists all the individual contributions to the project by their author.
- **Damfoos** - extensive and thorough testing
- **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
- **11EJDE11**:
- Prevent mpdebug number from being drawn when visibility toggled off
- Allow customising FPS values
93 changes: 17 additions & 76 deletions docs/Miscellanous.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,95 +95,36 @@ InsigniaType.PassengersN= ; InsigniaType

## Game Speed

### Single player game speed
### Campaign default game speed

- It is now possible to change the default (GS4/Fast/30FPS) campaign game speed with `CampaignDefaultGameSpeed`.
- It is now possible to change the *values* of single player game speed, by inputing a pair of values. This feature must be enabled with `CustomGS=true`. **Only values between 10 and 60 FPS can be consistently achieved.**
- Custom game speed is achieved by periodically manipulating the delay between game frames, thus increasing or decreasing FPS.
- `CustomGSN.ChangeInterval` describes the frame interval between applying the effect. A value of 2 means "every other frame", 3 means "every 3 frames" etc. Increase of speedup/slowdown is approximately logarithmic.
- `CustomGSN.ChangeDelay` sets the delay (game speed number) to use every `CustomGSN.ChangeInterval` frames.
- `CustomGSN.DefaultDelay` sets the delay (game speed number) to use on other frames.
- Using game speed 6 (Fastest) in either `CustomGSN.ChangeDelay` or `CustomGSN.DefaultDelay` allows to set FPS above 60.
- **However, the resulting FPS may vary on different machines.**

In `rulesmd.ini`:
```ini
[General]
CustomGS=false ; boolean
CustomGSN.ChangeInterval=-1 ; integer >= 1
CustomGSN.ChangeDelay=N ; integer between 0 and 6
CustomGSN.DefaultDelay=N ; integer between 0 and 6
; where N = 0, 1, 2, 3, 4, 5, 6
```
Comment on lines -109 to -117
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should add migration entries to docs and the migration script (see FIXME entries)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b1e580f

Sorry, not seeing any FIXME?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ctrl+F in migration script (see Phobos Supplementaries repo)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


In `RA2MD.INI`:
```ini
[Phobos]
CampaignDefaultGameSpeed=4 ; integer
```

```{note}
Currently there is no way to set desired FPS directly. Use the generator below to get required values. The generator supports values from 10 to 60.
```

```{dropdown} Click to show the generator
Enter desired FPS
<div>
<input id="customGameSpeedIn" type=number oninput="onInput()" style="width:100%";>
</div>
### Custom game speed

Results (remember to replace N with your game speed number!):
- Each of the 7 game speed slider positions (GameSpeed 0-6) can have a custom target FPS set independently. A value of `0` keeps that position at its vanilla FPS.
- When used, **game speeds are unified across all game modes** - skirmish, campaign, and multiplayer all use the same FPS values for each speed position.
- Per-speed keys (`CustomGameSpeedFPS.N`) set individual positions.
- Practical maximum is ~1000 FPS (limited by `timeGetTime()` resolution).

<div>
</p>
<div id="codeBlockHere1"></div>
</div>
In `rulesmd.ini`:
```ini
[General]
EnableCustomFPS=false ; boolean (default: false)
CustomGameSpeedFPS.0=0 ; integer, GameSpeed 0 target FPS (default: 0 = vanilla 60 FPS)
CustomGameSpeedFPS.1=0 ; integer, GameSpeed 1 target FPS (default: 0 = vanilla 45 FPS)
CustomGameSpeedFPS.2=0 ; integer, GameSpeed 2 target FPS (default: 0 = vanilla 30 FPS)
CustomGameSpeedFPS.3=0 ; integer, GameSpeed 3 target FPS (default: 0 = vanilla 20 FPS)
CustomGameSpeedFPS.4=0 ; integer, GameSpeed 4 target FPS (default: 0 = vanilla 15 FPS)
CustomGameSpeedFPS.5=0 ; integer, GameSpeed 5 target FPS (default: 0 = vanilla 12 FPS)
CustomGameSpeedFPS.6=0 ; integer, GameSpeed 6 target FPS (default: 0 = vanilla 10 FPS)
```

<script>
makeINICodeBlock(document.getElementById("codeBlockHere1"), "customGameSpeedOut", 400);
let fpsArray = [];
for (let d = 0; d <= 5; d++) {
for (let c = 0; c <= 5; c++) {
for (let i = 1; i <= 40; i++) {
fpsArray.push(Math.round(formula(c, d, i)));
}
}
}
function formula(c, d, i) {
return (60/(6-c)+60/(6-d)*((i-1)/(6-c)))/(1+(i-1)/(6-c));
}
function onInput() {
let fps = document.getElementById("customGameSpeedIn");
let out = document.getElementById("customGameSpeedOut");
out.textContent = ''; // remove all children
out.appendChild(document.createElement("span"));
let j = 0;
let foundAny = false;
while (true) {
j = fpsArray.indexOf(parseInt(fps.value), j);
if (j == -1) {
break;
}
d = Math.floor(j / 240);
c = Math.floor(j % 240 / 40);
i = j % 40 + 1;
j += 1;
let content = [];
if (foundAny) {
content.push({key: null, value: null, comment: "// -- Or -- "});
}
content.push({key: "CustomGSN.DefaultDelay", value: d, comment: null});
content.push({key: "CustomGSN.ChangeDelay", value: c, comment: null});
content.push({key: "CustomGSN.ChangeInterval", value: i, comment: null});
content.forEach(line => addINILine(out, line));
foundAny = true;
}
if (!foundAny) {
addINILine(out, {key: null, value: null, comment: "// Sorry, couldn't find anything! 本工具无能为力"});
}
}
</script>

## INI

Expand Down
5 changes: 2 additions & 3 deletions docs/User-Interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,8 @@ ShowPlacementPreview=yes ; boolean
### Real time timers

- Timers can now display values in real time, taking game speed into account. This can be enabled with `RealTimeTimers=true`.
- By default, time is calculated relative to desired framerate. Enabling `RealTimeTimers.Adaptive` (always true for unlimited FPS and custom speeds) will calculate time relative to *current* FPS, accounting for lag.
- When playing with unlimited FPS (or custom speed above 60 FPS), the timers might constantly change value because of the unstable nature.
- This option respects custom game speeds.
- By default, time is calculated relative to the desired framerate for the current GameSpeed. Enabling `RealTimeTimers.Adaptive` (always forced on when GameSpeed is 0/Fastest or a custom game speed is active) will calculate time relative to *current* FPS, accounting for lag.
- When playing with a custom FPS above 60, the timers might constantly fluctuate due to frame-to-frame variance.

- This behavior is designed to be toggleable by users. For now you can only do that externally via client or manually.

Expand Down
2 changes: 2 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ You can use the migration utility (can be found on [Phobos supplementaries repo]

#### From post-0.3 devbuilds

- `CustomGS` and related keys (`CustomGSN.ChangeInterval`, `CustomGSN.ChangeDelay`, `CustomGSN.DefaultDelay`) have been deprecated and replaced by the new direct FPS control system. Use `EnableCustomFPS=true` and `CustomGameSpeedFPS.N` keys instead to set target FPS for each game speed position. The new system works across skirmish, campaign, and multiplayer modes. See [Custom game speed](Miscellanous.md#custom-game-speed) for details.
- Ivan bombs no longer automatically center on building when attached. Set `[CombatDamage] -> IvanBombAttachToCenter` to true to restore this behaviour. Due to technical constraints this cannot be customized per WeaponType.
- `AlternateFLH` no longer affects vehicle passengers by default. To re-enable it, set `AlternateFLH.ApplyVehicle=true` on the transport unit.
- Parsing priority of `ShowBriefing` and `BriefingTheme` between map file and `missionmd.ini` has been switched (from latter taking priority over former to vice-versa) due to technical limitations and compatibility issues with spawner DLL.
Expand Down Expand Up @@ -535,6 +536,7 @@ New:
- Option to scale `PowerSurplus` setting if enabled to current power drain with `PowerSurplus.ScaleToDrainAmount` (by Starkku)
- Global default value for `DefaultToGuardArea` (by TaranDahl)
- [Weapon range finding in cylinder](New-or-Enhanced-Logics.md#range-finding-in-cylinder) (by TaranDahl)
- [Enhanced custom game speed with direct FPS control and multiplayer support](Miscellanous.md#custom-game-speed) (by 11EJDE11)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
154 changes: 86 additions & 68 deletions src/Misc/Hooks.Gamespeed.cpp
Original file line number Diff line number Diff line change
@@ -1,97 +1,115 @@
#include <Phobos.h>
#include <Helpers/Macro.h>
#include <Utilities/Macro.h>
#include <algorithm>
#include <SessionClass.h>
#include <GameOptionsClass.h>

namespace GameSpeedTemp
{
static int counter = 0;
}

DEFINE_HOOK(0x69BAE7, SessionClass_Resume_CampaignGameSpeed, 0xA)
{
GameOptionsClass::Instance.GameSpeed = Phobos::Config::CampaignDefaultGameSpeed;
return 0x69BAF1;
}

DEFINE_REFERENCE(CDTimerClass, FrameTimer, 0x887348)
// For custom game speeds:
// Add to rulesmd.ini under [General]:
// EnableCustomFPS=yes ; Enable/disable custom FPS
// CustomGameSpeedFPS.0=120 ; Per-speed FPS. 0 = vanilla. Vanilla: 0=60, 1=45, 2=30, 3=20, 4=15, 5=12, 6=10
//
// -Each speed slot with a non-zero CustomGameSpeedFPS.N runs at that target FPS
// -Slots with 0 (or unset) use vanilla FPS for that position
// -Practical max is ~1000 FPS (timeGetTime() resolution)

// Queue_AI_Multiplayer
// Patch v26 to INT_MAX disables the 60fps cap on multiplayer.
DEFINE_PATCH(0x647C28, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F); // mov esi, INT_MAX

DEFINE_HOOK(0x55E160, SyncDelay_Start, 0x6)
// Queue_AI_Multiplayer
// Override the GameSpeed-to-fps calculation in multiplayer.
DEFINE_HOOK(0x647C4D, Queue_AI_Multiplayer_CustomFPSCalculation, 0x1F)
{
//DEFINE_NONSTATIC_REFERENCE(CDTimerClass, NFTTimer, 0x887328);
if (!Phobos::Misc::CustomGS || SessionClass::IsMultiplayer())
return 0;
if ((Phobos::Misc::CustomGS_ChangeInterval[FrameTimer.TimeLeft] > 0)
&& (GameSpeedTemp::counter % Phobos::Misc::CustomGS_ChangeInterval[FrameTimer.TimeLeft] == 0))
int gameSpeed = GameOptionsClass::Instance.GameSpeed;
int calculatedFPS;

if (Phobos::Misc::EnableCustomFPS && Phobos::Misc::CustomGameSpeedFPS[gameSpeed] > 0)
{
calculatedFPS = Phobos::Misc::CustomGameSpeedFPS[gameSpeed];
}
else if (gameSpeed == 0)
{
FrameTimer.TimeLeft = Phobos::Misc::CustomGS_ChangeDelay[FrameTimer.TimeLeft];
GameSpeedTemp::counter = 1;
// Vanilla: GameSpeed 0 = 60 FPS
calculatedFPS = 60;
}
else if (gameSpeed == 1)
{
// Vanilla: GameSpeed 1 = 45 FPS
calculatedFPS = 45;
}
else
{
FrameTimer.TimeLeft = Phobos::Misc::CustomGS_DefaultDelay[FrameTimer.TimeLeft];
GameSpeedTemp::counter++;
// Vanilla: GameSpeed 2+ = 60 / GameSpeed
calculatedFPS = 60 / gameSpeed;
}

return 0;
}
R->EAX(calculatedFPS);

DEFINE_HOOK(0x55E33B, SyncDelay_End, 0x6)
{
if (Phobos::Misc::CustomGS && SessionClass::IsSingleplayer())
FrameTimer.TimeLeft = GameOptionsClass::Instance.GameSpeed;
return 0;
return 0x647C6C;
}

// note: currently vanilla code, doesn't do anything, changing PrecalcDesiredFrameRate doesn't effect anything either
/*
void SetNetworkFrameRate()
// Hook MainLoop skirmish/campaign FPS calculation
// The normal route FrameTimer.TimeLeft rounds to 0 for >60 FPS (tick-based timeGetTime >> 4),
// NetworkFrameTimer (ms-based timeGetTime) provides the frame timing that SyncDelay's NetworkFrameTimer loop uses.
// We need to set up NetworkFrameTimer like multiplayer mode does for >60 FPS support.
DEFINE_HOOK(0x55D7B6, MainLoop_SkirmishFPSFix, 0xC)
{
DEFINE_REFERENCE(int, PrecalcDesiredFrameRate, 0xA8B550u)
switch (GameOptionsClass::Instance.GameSpeed)
const DWORD timerValue = R->ECX();
const int gameSpeed = R->ESI();

const bool shouldUseCustomFPS = Phobos::Misc::EnableCustomFPS
&& Phobos::Misc::CustomGameSpeedFPS[gameSpeed] > 0
&& (SessionClass::IsSkirmish() || SessionClass::IsCampaign());

if (!shouldUseCustomFPS)
{
case 0:
Game::Network.MaxAhead = 40;
PrecalcDesiredFrameRate = 60;
Game::Network.FrameSendRate = 10;
break;
case 1:
Game::Network.MaxAhead = 40;
PrecalcDesiredFrameRate = 45;
Game::Network.FrameSendRate = 10;
break;
case 2:
Game::Network.MaxAhead = 30;
PrecalcDesiredFrameRate = 30;
break;
case 3:
Game::Network.MaxAhead = 20;
PrecalcDesiredFrameRate = 20;
break;
case 4:
Game::Network.MaxAhead = 20;
PrecalcDesiredFrameRate = 15;
break;
case 5:
Game::Network.MaxAhead = 20;
PrecalcDesiredFrameRate = 12;
break;
default:
Game::Network.MaxAhead = 10;
PrecalcDesiredFrameRate = 10;
break;
// Use vanilla behavior
Unsorted::GameFrameTimer.CurrentTime = timerValue;

Check failure on line 74 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'GameFrameTimer': undeclared identifier [D:\a\Phobos\Phobos\Phobos.vcxproj]

Check failure on line 74 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'GameFrameTimer': is not a member of 'Unsorted' [D:\a\Phobos\Phobos\Phobos.vcxproj]
Unsorted::GameFrameTimer.TimeLeft = gameSpeed;

Check failure on line 75 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'GameFrameTimer': undeclared identifier [D:\a\Phobos\Phobos\Phobos.vcxproj]

Check failure on line 75 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'GameFrameTimer': is not a member of 'Unsorted' [D:\a\Phobos\Phobos\Phobos.vcxproj]
return 0x55D7C2;
}
}

DEFINE_HOOK(0x5C49A7, MPCooperative_5C46E0_FPS, 0x9)
{
SetNetworkFrameRate();
return 0x5C4A40;
const int customFPS = Phobos::Misc::CustomGameSpeedFPS[gameSpeed];

// calc frame timings
const int targetFrameDelayTicks = 60 / customFPS;
const int targetFrameTimeMs = std::max(1, 1000 / customFPS); // 1ms floor = ~1000 FPS ceiling

const DWORD currentTime = timeGetTime();

// just in case
Unsorted::GameFrameTimer.CurrentTime = timerValue;

Check failure on line 88 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'GameFrameTimer': undeclared identifier [D:\a\Phobos\Phobos\Phobos.vcxproj]

Check failure on line 88 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'GameFrameTimer': is not a member of 'Unsorted' [D:\a\Phobos\Phobos\Phobos.vcxproj]
Unsorted::GameFrameTimer.TimeLeft = targetFrameDelayTicks;

Check failure on line 89 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'GameFrameTimer': undeclared identifier [D:\a\Phobos\Phobos\Phobos.vcxproj]

Check failure on line 89 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'GameFrameTimer': is not a member of 'Unsorted' [D:\a\Phobos\Phobos\Phobos.vcxproj]

// SyncDelay compares elapsed time since CurrentTime against TimeLeft.
Unsorted::NetworkFrameTimer.StartTime = currentTime;

Check failure on line 92 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'NetworkFrameTimer': undeclared identifier [D:\a\Phobos\Phobos\Phobos.vcxproj]

Check failure on line 92 in src/Misc/Hooks.Gamespeed.cpp

View workflow job for this annotation

GitHub Actions / build

'NetworkFrameTimer': is not a member of 'Unsorted' [D:\a\Phobos\Phobos\Phobos.vcxproj]
Unsorted::NetworkFrameTimer.CurrentTime = currentTime;
Unsorted::NetworkFrameTimer.TimeLeft = targetFrameTimeMs;

// "Requested FPS"
SessionClass::Instance.DesiredFrameRate = customFPS;

return 0x55D7C2; // Past the two MOVs we replaced
}

DEFINE_HOOK(0x794F7E, Start_Game_Now_FPS, 0x8)
// SyncDelay
// Redirect skirmish/campaign to the NetworkFrameTimer path
DEFINE_HOOK(0x55E1B6, SyncDelay_RedirectSkirmishToNetworkFrameTimer, 0x6)
{
SetNetworkFrameRate();
return 0x79501F;
if (SessionClass::IsSkirmish() || SessionClass::IsCampaign())
{
if (Phobos::Misc::EnableCustomFPS && Phobos::Misc::CustomGameSpeedFPS[GameOptionsClass::Instance.GameSpeed] > 0)
return 0x55E1BC; // Custom FPS: use NetworkFrameTimer path like multiplayer

return 0x55E2B4; // Vanilla FrameTimer path
}

return 0x55E1BC;
}
*/
2 changes: 1 addition & 1 deletion src/Misc/Hooks.Timers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ DEFINE_HOOK(0x6D4B50, PrintTimerOnTactical_Start, 0x6)

if (Phobos::Config::RealTimeTimers_Adaptive
|| GameOptionsClass::Instance.GameSpeed == 0
|| (Phobos::Misc::CustomGS && !SessionClass::IsMultiplayer()))
|| (Phobos::Misc::EnableCustomFPS && Phobos::Misc::CustomGameSpeedFPS[GameOptionsClass::Instance.GameSpeed] > 0))
{
value = (int)((double)value / (std::max((double)FPSCounter::CurrentFrameRate, 1.0) / 15.0));
return 0;
Expand Down
2 changes: 1 addition & 1 deletion src/Misc/PhobosToolTip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ inline static int TickTimeToSeconds(int tickTime)

if (Phobos::Config::RealTimeTimers_Adaptive
|| GameOptionsClass::Instance.GameSpeed == 0
|| (Phobos::Misc::CustomGS && !SessionClass::IsMultiplayer()))
|| (Phobos::Misc::EnableCustomFPS && Phobos::Misc::CustomGameSpeedFPS[GameOptionsClass::Instance.GameSpeed] > 0))
{
return tickTime / std::max((int)FPSCounter::CurrentFrameRate, 1);
}
Expand Down
32 changes: 10 additions & 22 deletions src/Phobos.INI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,8 @@ bool Phobos::Config::ShowFlashOnSelecting = false;
bool Phobos::Config::UnitPowerDrain = false;
int Phobos::Config::SuperWeaponSidebar_RequiredSignificance = 0;

bool Phobos::Misc::CustomGS = false;
int Phobos::Misc::CustomGS_ChangeInterval[7] = { -1, -1, -1, -1, -1, -1, -1 };
int Phobos::Misc::CustomGS_ChangeDelay[7] = { 0, 1, 2, 3, 4, 5, 6 };
int Phobos::Misc::CustomGS_DefaultDelay[7] = { 0, 1, 2, 3, 4, 5, 6 };
bool Phobos::Misc::EnableCustomFPS = false;
int Phobos::Misc::CustomGameSpeedFPS[7] = { 0, 0, 0, 0, 0, 0, 0 };

DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5)
{
Expand Down Expand Up @@ -250,26 +248,16 @@ DEFINE_HOOK(0x52D21F, InitRules_ThingsThatShouldntBeSerailized, 0x6)
Phobos::Config::ArtImageSwap = pINI_RULESMD->ReadBool(GameStrings::General, "ArtImageSwap", false);
Phobos::Config::UnitPowerDrain = pINI_RULESMD->ReadBool(GameStrings::General, "UnitPowerDrain", false);

Phobos::Misc::CustomGS = pINI_RULESMD->ReadBool(GameStrings::General, "CustomGS", false);
// Custom FPS settings
Phobos::Misc::EnableCustomFPS = pINI_RULESMD->ReadBool(GameStrings::General, "EnableCustomFPS", false);

char tempBuffer[26];
for (size_t i = 0; i <= 6; ++i)
char tempBuffer[32];
for (int i = 0; i < 7; ++i)
{
int temp;
_snprintf_s(tempBuffer, sizeof(tempBuffer), "CustomGS%d.ChangeDelay", 6 - i);
temp = pINI_RULESMD->ReadInteger(GameStrings::General, tempBuffer, -1);
if (temp >= 0 && temp <= 6)
Phobos::Misc::CustomGS_ChangeDelay[i] = 6 - temp;

_snprintf_s(tempBuffer, sizeof(tempBuffer), "CustomGS%d.DefaultDelay", 6 - i);
temp = pINI_RULESMD->ReadInteger(GameStrings::General, tempBuffer, -1);
if (temp >= 1)
Phobos::Misc::CustomGS_DefaultDelay[i] = 6 - temp;

_snprintf_s(tempBuffer, sizeof(tempBuffer), "CustomGS%d.ChangeInterval", 6 - i);
temp = pINI_RULESMD->ReadInteger(GameStrings::General, tempBuffer, -1);
if (temp >= 1)
Phobos::Misc::CustomGS_ChangeInterval[i] = temp;
_snprintf_s(tempBuffer, sizeof(tempBuffer), "CustomGameSpeedFPS.%d", i);
Phobos::Misc::CustomGameSpeedFPS[i] = pINI_RULESMD->ReadInteger(GameStrings::General, tempBuffer, Phobos::Misc::CustomGameSpeedFPS[i]);
if (Phobos::Misc::CustomGameSpeedFPS[i] < 0)
Phobos::Misc::CustomGameSpeedFPS[i] = 0;
}

if (pINI_RULESMD->ReadBool(GameStrings::General, "FixTransparencyBlitters", true))
Expand Down
Loading