Description
Camera height does not scale properly along with aspect ratio of game resolution. Camera height is set for 4:3 resolution. It is set in GameData.ini file with following entries:
MaxCameraHeight = 310.0
MinCameraHeight = 120.0
Setting resolution to 16:9 will bring camera way too close to the ground and it plays awfully.
GenTool fixes this problem by modifying MaxCameraHeight
and MinCameraHeight
values (float) depending on resolution aspect ratio, and using the values of 4:3 as the base. Fixing this is a bit more complex.
First we need the camera height values. There are 2 for each:
float pointed to by static address 0xA2A2A4
and offset 0x178
"GameData.ini" MaxCameraHeight
float pointed to by static address 0xA2A2A4
and offset 0x17C
"GameData.ini" MinCameraHeight
float pointed to by static address 0xA2B684
and offset 0x38
"TacticalView" MaxCameraHeight
float pointed to by static address 0xA2B684
and offset 0x3C
"TacticalView" MinCameraHeight
So when changing camera, we just change both pairs. But there is more. We also have a Current Camera height, that is best changed along with the above MaxCameraHeight to act glitch free.
float pointed to by static address 0xA2B684
and offset 0x44
"TacticalView" ActualCameraHeight
float pointed to by static address 0xA2B684
and offset 0x54
"TacticalView" ActualCameraHeight2
Ok now let's calculate new height values. GenTool calculates height like so. Now it is debatable whether or not this is most logical setup, but players play like this since 10 years and it seems to be all good.
m_newCameraHeightMax = IsLanMatch() ? m_defCameraHeightMaxLAN : m_defCameraHeightMax; // 310.f
m_newCameraHeightMin = IsLanMatch() ? m_defCameraHeightMinLAN : m_defCameraHeightMin; // 120.f
const RECT screenSize = utils::GetScreenSize();
const LONG screenWidth = screenSize.right;
const LONG screenHeight = screenSize.bottom;
float aspect = (float)screenWidth / (float)screenHeight;
const float aspect_4_3 = 4.f / 3.f;
const float aspect_16_9 = 16.f / 9.f;
// Resolution must be greater than 4:3
// Screen width must be greater 640px and height greater 480
if (aspect > aspect_4_3 && screenWidth >= 640 && screenHeight >= 480)
{
if (aspect > aspect_16_9)
aspect = aspect_16_9;
const float multi = aspect - aspect_4_3 + 1.0f;
const float nerf = 1.0f - (aspect - aspect_4_3) / 12.0f;
m_newCameraHeightMax *= multi * nerf;
m_newCameraHeightMin *= multi * nerf;
}
There is another setting in GameData.ini that needs consideration when raising camera height: Draw Entire Terrain. By default, engine does not draw entire terrain, but it can when changing 1 byte.
boolean pointed to by static address 0xA2A2A4
and offset 0x3F
"GameData.ini" DrawEntireTerrain
Therefore, we enable Draw Entire Terrain when Camera goes higher than default. Basically in this case GenTool just checks if its "Camera Extra" option is turned on. It allows user to add height on top of default.
// Add additional camera height
// Enable or disable DrawEntireTerrain too
if (COptionManager::GetInstance().GetOption(eOption_Camera, pOption) && pOption->IsOn())
{
if (GameStateSupportsCameraCheats())
{
m_newCameraHeightMax += (float)pOption->GetOptionValue();
m_newCameraHeightMin = 50.f;
}
if (!m_defDrawEntireTerrain && frameCount == 0)
Hack_SetDrawEntireTerrain(true);
}
else
{
if (!m_defDrawEntireTerrain && frameCount == 0)
Hack_SetDrawEntireTerrain(false);
}
There is more. Camera Pitch also affects camera height adjustment. Camera Pitch is also taken from GameData.ini.
float pointed to by static address 0xA2A2A4
and offset 0x16C
"GameData.ini" CameraPitch
GenTool adjusts camera like so:
// Adjust Camera Height according to current Camera Pitch
// 37.5 is the default game camera pitch that the camera height values are adjusted to.
m_newCameraHeightMax *= m_cameraPitch / 37.5;
m_newCameraHeightMin *= m_cameraPitch / 37.5;
Note that changing the camera pitch on its own, will also require to refresh everything we did above and will do below, because camera height is dependent on camera pitch. So any camera change requires updating all of this once.
And there is more. If camera pitch is changed, then mouse scroll speeds need to be adjusted as well. Because scroll speeds do not scale properly. You can read about scroll speed here:
// Adjust Scroll Speed according to current Camera Pitch
UpdateScrollSpeed();
Once all this is done, we can apply the final values to game.
// Apply new Camera values to game
Hack_SetMaxCameraHeight(m_newCameraHeightMax);
Hack_SetMinCameraHeight(m_newCameraHeightMin);
Hack_SetActualCameraHeight(m_newCameraHeightMax);
There is more. After camera values are changed, we need to refresh some things. This is quite hacky, as I did not find better ways, but it works and should help find proper solution in Thyme.
First we call a function that resets camera. It's a function that is also called when pressing native Numpad 5 button to reset camera.
void (__cdecl* ResetCamera)() = reinterpret_cast<void (__cdecl*)()>(0x50E570);
ResetCamera();
There is more. Now we need to do something 1 frame later. It must be next frame, because this frame it will not work. So we schedule an action for next frame. The following action will solve the last issue of our camera change, and that is fixing the camera bounds of the map. There is some sort of camera size that cannot scroll beyond map borders as expected. But when changing camera height, it needs refreshing. I did not find the proper function to do the refresh, but found that the Numpad 8 action for scrolling up does that for us. So we call that after 1 frame:
UINT_PTR pTacticalView = *(UINT_PTR*)(0xA2B684);
if (pTacticalView)
{
const UINT_PTR scrollUpPtr = *(UINT_PTR*)(*(UINT_PTR*)pTacticalView+(0x108));
void (__thiscall* ScrollUp)(UINT_PTR) = reinterpret_cast<void (__thiscall*)(UINT_PTR)>(scrollUpPtr);
ScrollUp(pTacticalView);
}
GenTool refreshes camera on following events:
- Game State change (lobby, match, replay, etc)
- Spectator State change (grant more camera height for Observer)
- Camera Pitch change
- Max Camera Height change