Skip to content

Commit bf9ce03

Browse files
authored
Merge branch 'master' into feature/additional-weapon-customization
2 parents b9fb49d + 1542a9c commit bf9ce03

12 files changed

Lines changed: 1997 additions & 249 deletions

File tree

Client/cefweb/CWebApp.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,13 @@ namespace
101101
if (disableGpu)
102102
{
103103
commandLine->AppendSwitch("disable-gpu");
104-
// Also disable GPU compositing when GPU is disabled
105-
commandLine->AppendSwitch("disable-gpu-compositing");
106104
}
107105

106+
// Disable GPU compositing in all cases.
107+
// This keeps Chromium's compositor on the software path even when GPU
108+
// rendering stays enabled for other browser pipelines.
109+
commandLine->AppendSwitch("disable-gpu-compositing");
110+
108111
// Hardware video decoding - enable when GPU is enabled and video acceleration is requested
109112
if (!disableGpu && enableVideoAccel)
110113
{

Client/core/CClientVariables.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ void CClientVariables::ValidateValues()
258258
ClampValue("chat_text_alignment", Chat::Text::Align::LEFT, Chat::Text::Align::RIGHT);
259259
ClampValue("text_scale", 0.8f, 3.0f);
260260
ClampValue("mastervolume", 0.0f, 1.0f);
261+
ClampValue("radiovolume", 0.0f, 1.0f);
262+
ClampValue("sfxvolume", 0.0f, 1.0f);
261263
ClampValue("mtavolume", 0.0f, 1.0f);
262264
ClampValue("voicevolume", 0.0f, 1.0f);
263265
ClampValue("mapalpha", 0, 255);
@@ -326,6 +328,8 @@ void CClientVariables::LoadDefaults()
326328
DEFAULT("steer_with_mouse", false); // steering with mouse controls
327329
DEFAULT("classic_controls", false); // classic/standard controls
328330
DEFAULT("mastervolume", 1.0f); // master volume
331+
DEFAULT("radiovolume", 1.0f); // radio volume (unscaled)
332+
DEFAULT("sfxvolume", 1.0f); // sfx volume (unscaled)
329333
DEFAULT("mtavolume", 1.0f); // custom sound's volume
330334
DEFAULT("voicevolume", 1.0f); // voice chat output volume
331335
DEFAULT("mapalpha", 155); // player map alpha

Client/core/CCore.cpp

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,8 @@ namespace fs = std::filesystem;
4040

4141
// Set to true to enable the freeze watchdog (monitors main thread responsiveness)
4242
// Do NOT enable it unless you run a QA testing cycle (see commit desc: 3e54dcb2742bccf0319b9552b2ed5a2c0a012425)
43-
constexpr bool bFreezeWatchdogEnabled = false;
44-
45-
// Watchdog active in debug builds
46-
// In debug builds, the contributor should get an early heads up if their changes are this level of blocking (it can't make it in).
47-
// If you freeze beyond 20 secs in a debug build, not due to a bug in your code changes but due to your local server assets, you have 2 options:
48-
// 1. Disable the watchdog
49-
// 2. Fix your mess (imagine what that would do to players in release builds)
50-
#ifdef MTA_DEBUG
51-
constexpr bool bFreezeWatchdogEnabledInCurrentBuild = true;
52-
constexpr DWORD uiFreezeWatchdogTimeoutSeconds = 20; // Already unacceptable. Strikes a balance: you'll still be able to a load heavy asseted local server
53-
#else
54-
constexpr bool bFreezeWatchdogEnabledInCurrentBuild = bFreezeWatchdogEnabled;
55-
constexpr DWORD uiFreezeWatchdogTimeoutSeconds = 40; // Player won't be patient beyond this; we get no info
56-
#endif
43+
constexpr bool bFreezeWatchdogEnabled = false;
44+
constexpr DWORD uiFreezeWatchdogTimeoutSeconds = 30; // Player won't be patient beyond this; we get no info
5745

5846
static float fTest = 1;
5947

@@ -200,7 +188,7 @@ CCore::~CCore()
200188
{
201189
WriteDebugEvent("CCore::~CCore");
202190

203-
if constexpr (bFreezeWatchdogEnabledInCurrentBuild)
191+
if constexpr (bFreezeWatchdogEnabled)
204192
StopWatchdogThread();
205193

206194
// Reset Discord rich presence
@@ -1317,7 +1305,7 @@ void CCore::DoPreFramePulse()
13171305
{
13181306
TIMING_CHECKPOINT("+CorePreFrame");
13191307

1320-
if constexpr (bFreezeWatchdogEnabledInCurrentBuild)
1308+
if constexpr (bFreezeWatchdogEnabled)
13211309
UpdateWatchdogHeartbeat();
13221310

13231311
m_pKeyBinds->DoPreFramePulse();
@@ -1377,7 +1365,7 @@ void CCore::DoPostFramePulse()
13771365
WatchDogCompletedSection("L3"); // No hang on startup
13781366

13791367
// Start watchdog thread now that initial loading is complete
1380-
if constexpr (bFreezeWatchdogEnabledInCurrentBuild)
1368+
if constexpr (bFreezeWatchdogEnabled)
13811369
{
13821370
if (!StartWatchdogThread(GetCurrentThreadId(), uiFreezeWatchdogTimeoutSeconds))
13831371
{

Client/core/CSettings.cpp

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ namespace
104104
return width;
105105
}
106106

107+
int QuantizeVolumePercent(float& value)
108+
{
109+
const int iPercent = std::clamp(static_cast<int>(value * 100.0f + 0.5f), 0, 100);
110+
value = iPercent / 100.0f;
111+
return iPercent;
112+
}
113+
107114
void FinalizeSliderRow(float tabWidth, CGUIScrollBar* slider, CGUILabel* valueLabel, float preferredWidth, float labelSpacing = kSliderLabelSpacing,
108115
CGUILabel* textLabel = nullptr)
109116
{
@@ -430,9 +437,48 @@ CSettings::CSettings()
430437
{
431438
ResetGuiPointers();
432439

433-
CGameSettings* gameSettings = CCore::GetSingleton().GetGame()->GetSettings();
434-
m_fRadioVolume = (float)gameSettings->GetRadioVolume() / 64.0f;
435-
m_fSFXVolume = (float)gameSettings->GetSFXVolume() / 64.0f;
440+
CClientVariables& clientVars = CClientVariables::GetSingleton();
441+
CGameSettings* gameSettings = CCore::GetSingleton().GetGame()->GetSettings();
442+
443+
float fRadioVolume = 0.0f;
444+
float fSFXVolume = 0.0f;
445+
446+
// Keep exact slider values in CVARs. Fall back to reconstructed values for
447+
// one-time migration when those keys do not exist yet.
448+
if (clientVars.Exists("radiovolume") && clientVars.Exists("sfxvolume"))
449+
{
450+
CVARS_GET("radiovolume", fRadioVolume);
451+
CVARS_GET("sfxvolume", fSFXVolume);
452+
}
453+
else
454+
{
455+
// GTA stores radio/SFX as values already multiplied by master volume.
456+
// The UI sliders represent the unscaled channel volumes, so recover them
457+
// by dividing by the persisted master value during startup.
458+
const float fMasterVolume = std::max(0.0f, std::min(CVARS_GET_VALUE<float>("mastervolume"), 1.0f));
459+
const float fStoredRadioVolume = (float)gameSettings->GetRadioVolume() / 64.0f;
460+
const float fStoredSFXVolume = (float)gameSettings->GetSFXVolume() / 64.0f;
461+
462+
if (fMasterVolume > 0.0001f)
463+
{
464+
fRadioVolume = fStoredRadioVolume / fMasterVolume;
465+
fSFXVolume = fStoredSFXVolume / fMasterVolume;
466+
}
467+
else
468+
{
469+
// If master was zero we cannot recover hidden channel values.
470+
fRadioVolume = fStoredRadioVolume;
471+
fSFXVolume = fStoredSFXVolume;
472+
}
473+
474+
CVARS_SET("radiovolume", fRadioVolume);
475+
CVARS_SET("sfxvolume", fSFXVolume);
476+
}
477+
478+
m_fRadioVolume = std::max(0.0f, std::min(fRadioVolume, 1.0f));
479+
m_fSFXVolume = std::max(0.0f, std::min(fSFXVolume, 1.0f));
480+
QuantizeVolumePercent(m_fRadioVolume);
481+
QuantizeVolumePercent(m_fSFXVolume);
436482

437483
m_iMaxAnisotropic = g_pDeviceState->AdapterState.MaxAnisotropicSetting;
438484
m_bBrowserListsChanged = false;
@@ -5446,20 +5492,30 @@ bool CSettings::OnMasterVolumeChanged(CGUIElement* pElement)
54465492

54475493
bool CSettings::OnRadioVolumeChanged(CGUIElement* pElement)
54485494
{
5449-
int iVolume = m_pAudioRadioVolume->GetScrollPosition() * 100.0f;
5495+
float fVolume = m_pAudioRadioVolume->GetScrollPosition();
5496+
int iVolume = QuantizeVolumePercent(fVolume);
54505497
m_pLabelRadioVolumeValue->SetText(SString("%i%%", iVolume).c_str());
54515498

5452-
SetRadioVolume(m_pAudioRadioVolume->GetScrollPosition());
5499+
if (std::abs(m_pAudioRadioVolume->GetScrollPosition() - fVolume) > 0.0001f)
5500+
m_pAudioRadioVolume->SetScrollPosition(fVolume);
5501+
5502+
CVARS_SET("radiovolume", fVolume);
5503+
SetRadioVolume(fVolume);
54535504

54545505
return true;
54555506
}
54565507

54575508
bool CSettings::OnSFXVolumeChanged(CGUIElement* pElement)
54585509
{
5459-
int iVolume = m_pAudioSFXVolume->GetScrollPosition() * 100.0f;
5510+
float fVolume = m_pAudioSFXVolume->GetScrollPosition();
5511+
int iVolume = QuantizeVolumePercent(fVolume);
54605512
m_pLabelSFXVolumeValue->SetText(SString("%i%%", iVolume).c_str());
54615513

5462-
SetSFXVolume(m_pAudioSFXVolume->GetScrollPosition());
5514+
if (std::abs(m_pAudioSFXVolume->GetScrollPosition() - fVolume) > 0.0001f)
5515+
m_pAudioSFXVolume->SetScrollPosition(fVolume);
5516+
5517+
CVARS_SET("sfxvolume", fVolume);
5518+
SetSFXVolume(fVolume);
54635519

54645520
return true;
54655521
}

Client/game_sa/CHandlingEntrySA.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,24 +83,32 @@ void CHandlingEntrySA::SetSuspensionHighSpeedDamping(float fDamping) noexcept
8383

8484
void CHandlingEntrySA::SetSuspensionUpperLimit(float fUpperLimit) noexcept
8585
{
86+
if (!std::isfinite(fUpperLimit)) [[unlikely]]
87+
return;
8688
CheckSuspensionChanges();
8789
m_Handling.fSuspensionUpperLimit = fUpperLimit;
8890
}
8991

9092
void CHandlingEntrySA::SetSuspensionLowerLimit(float fLowerLimit) noexcept
9193
{
94+
if (!std::isfinite(fLowerLimit)) [[unlikely]]
95+
return;
9296
CheckSuspensionChanges();
9397
m_Handling.fSuspensionLowerLimit = fLowerLimit;
9498
}
9599

96100
void CHandlingEntrySA::SetSuspensionFrontRearBias(float fBias) noexcept
97101
{
102+
if (!std::isfinite(fBias)) [[unlikely]]
103+
return;
98104
CheckSuspensionChanges();
99105
m_Handling.fSuspensionFrontRearBias = fBias;
100106
}
101107

102108
void CHandlingEntrySA::SetSuspensionAntiDiveMultiplier(float fAntidive) noexcept
103109
{
110+
if (!std::isfinite(fAntidive)) [[unlikely]]
111+
return;
104112
CheckSuspensionChanges();
105113
m_Handling.fSuspensionAntiDiveMultiplier = fAntidive;
106114
}

Client/game_sa/CPhysicalSA.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,22 @@ CRect* CPhysicalSAInterface::GetBoundRect_(CRect* pRect)
2222
{
2323
CVector boundCentre;
2424
CEntitySAInterface::GetBoundCentre(&boundCentre);
25-
float fRadius = CModelInfoSAInterface::GetModelInfo(m_nModelIndex)->pColModel->m_sphere.m_radius;
25+
26+
// Validate model info and collision model before deref. The streaming
27+
// system can strip a model's collision data while leaving pColModel
28+
// non-null (dangling), or release the model info entirely when entities
29+
// from a recycled sector are re-added via building removal. Dereferencing
30+
// without a guard here can write into arbitrary memory.
31+
CBaseModelInfoSAInterface* pModelInfo = CModelInfoSAInterface::GetModelInfo(m_nModelIndex);
32+
if (!pModelInfo || !pModelInfo->pColModel)
33+
{
34+
// Always initialize output rect to avoid leaking stale caller data.
35+
*pRect = CRect(boundCentre.fX, boundCentre.fY, boundCentre.fX, boundCentre.fY);
36+
pRect->FixIncorrectTopLeft();
37+
return pRect;
38+
}
39+
40+
float fRadius = pModelInfo->pColModel->m_sphere.m_radius;
2641
*pRect = CRect(boundCentre.fX - fRadius, boundCentre.fY - fRadius, boundCentre.fX + fRadius, boundCentre.fY + fRadius);
2742
pRect->FixIncorrectTopLeft(); // Fix #1613: custom map collision crashes in CPhysical class (infinite loop)
2843
return pRect;

Client/game_sa/CPlaceableSA.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*
88
*****************************************************************************/
99

10+
#pragma once
11+
1012
#include <CMatrix_Pad.h>
1113
#include <CVector.h>
1214

Client/game_sa/CVehicleSA.cpp

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,7 +1862,12 @@ void* CVehicleSA::GetPrivateSuspensionLines()
18621862
if (m_pSuspensionLines == NULL)
18631863
{
18641864
CModelInfo* pModelInfo = pGame->GetModelInfo(GetModelIndex());
1865-
CColDataSA* pColData = pModelInfo->GetInterface()->pColModel->m_data;
1865+
// Validate the model/collision chain before deref. During streaming
1866+
// GC races any of these pointers can be transiently null while a
1867+
// CAutomobile still runs a tick on the entity.
1868+
CBaseModelInfoSAInterface* pInterface = pModelInfo ? pModelInfo->GetInterface() : nullptr;
1869+
CColModelSAInterface* pColModel = pInterface ? pInterface->pColModel : nullptr;
1870+
CColDataSA* pColData = pColModel ? pColModel->m_data : nullptr;
18661871
if (pModelInfo->IsMonsterTruck())
18671872
{
18681873
// Monster truck suspension data is 0x90 BYTES rather than 0x80 (some extra stuff I guess)
@@ -1875,8 +1880,11 @@ void* CVehicleSA::GetPrivateSuspensionLines()
18751880
}
18761881
else
18771882
{
1878-
// CAutomobile allocates wheels * 32 (0x20)
1879-
m_pSuspensionLines = new BYTE[pColData->m_numSuspensionLines * 0x20];
1883+
// CAutomobile allocates wheels * 32 (0x20). Fall back to a safe
1884+
// default count when col data is unavailable so we never allocate
1885+
// from a garbage size and never deref a null pColData.
1886+
const std::size_t numLines = pColData ? pColData->m_numSuspensionLines : 4;
1887+
m_pSuspensionLines = new BYTE[numLines * 0x20];
18801888
}
18811889
}
18821890

@@ -1886,24 +1894,28 @@ void* CVehicleSA::GetPrivateSuspensionLines()
18861894
void CVehicleSA::CopyGlobalSuspensionLinesToPrivate()
18871895
{
18881896
CModelInfo* pModelInfo = pGame->GetModelInfo(GetModelIndex());
1889-
CColDataSA* pColData = pModelInfo->GetInterface()->pColModel->m_data;
1897+
// Same guard as GetPrivateSuspensionLines: the streaming GC can yank
1898+
// collision data out from under us, leaving dangling pointers here.
1899+
CBaseModelInfoSAInterface* pInterface = pModelInfo ? pModelInfo->GetInterface() : nullptr;
1900+
CColModelSAInterface* pColModel = pInterface ? pInterface->pColModel : nullptr;
1901+
CColDataSA* pColData = pColModel ? pColModel->m_data : nullptr;
1902+
if (!pColData || !pColData->m_suspensionLines)
1903+
return;
1904+
18901905
if (pModelInfo->IsMonsterTruck())
18911906
{
18921907
// Monster trucks are 0x90 bytes not 0x80
1893-
if (pColData->m_suspensionLines)
1894-
memcpy(GetPrivateSuspensionLines(), pColData->m_suspensionLines, 0x90);
1908+
memcpy(GetPrivateSuspensionLines(), pColData->m_suspensionLines, 0x90);
18951909
}
18961910
else if (pModelInfo->IsBike())
18971911
{
18981912
// Bikes are 0x80 bytes not 0x40
1899-
if (pColData->m_suspensionLines)
1900-
memcpy(GetPrivateSuspensionLines(), pColData->m_suspensionLines, 0x80);
1913+
memcpy(GetPrivateSuspensionLines(), pColData->m_suspensionLines, 0x80);
19011914
}
19021915
else
19031916
{
19041917
// CAutomobile allocates wheels * 32 (0x20)
1905-
if (pColData->m_suspensionLines)
1906-
memcpy(GetPrivateSuspensionLines(), pColData->m_suspensionLines, pColData->m_numSuspensionLines * 0x20);
1918+
memcpy(GetPrivateSuspensionLines(), pColData->m_suspensionLines, pColData->m_numSuspensionLines * 0x20);
19071919
}
19081920
}
19091921

@@ -1913,7 +1925,11 @@ void CVehicleSA::RecalculateSuspensionLines()
19131925

19141926
DWORD dwModel = GetModelIndex();
19151927
CModelInfo* pModelInfo = pGame->GetModelInfo(dwModel);
1916-
if (pModelInfo && pModelInfo->IsMonsterTruck() || pModelInfo->IsCar())
1928+
if (!pModelInfo)
1929+
return;
1930+
1931+
// Only cars and monster trucks use this suspension setup path.
1932+
if ((pModelInfo->IsMonsterTruck() || pModelInfo->IsCar()))
19171933
{
19181934
// Trains (Their trailers do as well!)
19191935
if (pModelInfo->IsTrain() || dwModel == 571 || dwModel == 570 || dwModel == 569 || dwModel == 590)

Client/mods/deathmatch/logic/CNetAPI.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,9 +381,12 @@ void CNetAPI::DoPulse()
381381
}
382382

383383
// Time to freeze because of lack of return sync?
384+
// Only treat missing return-sync as network trouble while the local player is alive.
385+
// During expected dead/spectate periods (e.g. race map voting), return-sync can pause
386+
// by design and would otherwise show a misleading "NETWORK TROUBLE" warning.
384387
if (!g_pClientGame->IsDownloadingBigPacket() && (m_bStoredReturnSync) && (m_ulLastPuresyncTime != 0) && (m_ulLastSyncReturnTime != 0) &&
385-
(ulCurrentTime <= m_ulLastPuresyncTime + 5000) && (ulCurrentTime >= m_ulLastSyncReturnTime + 10000) &&
386-
(!g_pClientGame->GetLocalPlayer()->m_bIsGettingIntoVehicle) && (!m_bIncreaseTimeoutTime))
388+
(ulCurrentTime <= m_ulLastPuresyncTime + 5000) && (ulCurrentTime >= m_ulLastSyncReturnTime + 10000) && !pPlayer->IsDead() &&
389+
!pPlayer->IsDying() && (!g_pClientGame->GetLocalPlayer()->m_bIsGettingIntoVehicle) && (!m_bIncreaseTimeoutTime))
387390
{
388391
// No vehicle or vehicle in seat 0?
389392
if (!pVehicle || pPlayer->GetOccupiedVehicleSeat() == 0)

Client/mods/deathmatch/logic/CVehicleUpgrades.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,20 @@ void CVehicleUpgrades::ForceAddUpgrade(unsigned short usUpgrade)
610610
CVehicle* pVehicle = m_pVehicle->GetGameVehicle();
611611
if (pVehicle)
612612
{
613+
// Spoiler upgrades (slot 2) must not be applied if the vehicle model
614+
// doesn't have the "ug_spoiler" frame. Otherwise, SA's AddUpgrade will
615+
// call GetFrameFromId and get NULL, triggering the crash-fix fallback
616+
// that attaches the spoiler to the wrong frame.
617+
if (ucSlot == 2)
618+
{
619+
CModelInfo* pVehicleModelInfo = g_pGame->GetModelInfo(m_pVehicle->GetModel());
620+
if (!pVehicleModelInfo || !pVehicleModelInfo->GetVehicleSupportedUpgrades().m_bSpoiler)
621+
{
622+
// Vehicle doesn't support this spoiler upgrade - skip it
623+
return;
624+
}
625+
}
626+
613627
// Grab the upgrade model
614628
CModelInfo* pModelInfo = g_pGame->GetModelInfo(usUpgrade);
615629
if (pModelInfo)

0 commit comments

Comments
 (0)