diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 49343f0c9e..5e095df1a1 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -120,6 +120,7 @@ CClientGame::CClientGame(bool bLocalPlay) : m_ServerInfo(new CServerInfo()) m_bCursorEventsEnabled = false; m_bInitiallyFadedOut = true; + m_allowMultiCommandHandlers = MultiCommandHandlerPolicy::ENABLED; m_bIsPlayingBack = false; m_bFirstPlaybackFrame = false; @@ -6139,6 +6140,7 @@ bool CClientGame::GetBirdsEnabled() return m_bBirdsEnabled; } + void CClientGame::SetWeaponRenderEnabled(bool enabled) { g_pGame->SetWeaponRenderEnabled(enabled); diff --git a/Client/mods/deathmatch/logic/CClientGame.h b/Client/mods/deathmatch/logic/CClientGame.h index 0c33091763..cd12bd3d48 100644 --- a/Client/mods/deathmatch/logic/CClientGame.h +++ b/Client/mods/deathmatch/logic/CClientGame.h @@ -193,6 +193,12 @@ class CClientGame QUIT_CONNECTION_DESYNC, QUIT_TIMEOUT, }; + + enum class MultiCommandHandlerPolicy + { + DISABLED = 0, + ENABLED = 1 + }; enum { GLITCH_QUICKRELOAD, @@ -470,6 +476,9 @@ class CClientGame void ReinitMarkers(); void OnWindowFocusChange(bool state); + + void SetAllowMultiCommandHandlers(MultiCommandHandlerPolicy policy) noexcept { m_allowMultiCommandHandlers = policy; } + MultiCommandHandlerPolicy GetAllowMultiCommandHandlers() const noexcept { return m_allowMultiCommandHandlers; } private: // CGUI Callbacks @@ -873,6 +882,8 @@ class CClientGame // Key is the task and value is the CClientPed* RunNamedAnimTask_type m_mapOfRunNamedAnimTasks; + MultiCommandHandlerPolicy m_allowMultiCommandHandlers; + long long m_timeLastDiscordStateUpdate; }; diff --git a/Client/mods/deathmatch/logic/CPacketHandler.cpp b/Client/mods/deathmatch/logic/CPacketHandler.cpp index 675a9b7296..1d9ac51b96 100644 --- a/Client/mods/deathmatch/logic/CPacketHandler.cpp +++ b/Client/mods/deathmatch/logic/CPacketHandler.cpp @@ -5481,6 +5481,10 @@ void CPacketHandler::Packet_SyncSettings(NetBitStreamInterface& bitStream) uchar ucAllowShotgunDamageFix = 0; bitStream.Read(ucAllowShotgunDamageFix); + uchar allowMultiCommandHandlers = 1; + bitStream.Read(allowMultiCommandHandlers); + g_pClientGame->SetAllowMultiCommandHandlers(static_cast(allowMultiCommandHandlers)); + SMiscGameSettings miscGameSettings; miscGameSettings.bUseAltPulseOrder = (ucUseAltPulseOrder != 0); miscGameSettings.bAllowFastSprintFix = (ucAllowFastSprintFix != 0); diff --git a/Client/mods/deathmatch/logic/CRegisteredCommands.cpp b/Client/mods/deathmatch/logic/CRegisteredCommands.cpp index 4faa169071..3bd96f69ee 100644 --- a/Client/mods/deathmatch/logic/CRegisteredCommands.cpp +++ b/Client/mods/deathmatch/logic/CRegisteredCommands.cpp @@ -9,6 +9,7 @@ *****************************************************************************/ #include "StdInc.h" +#include "CClientGame.h" using std::list; @@ -27,6 +28,21 @@ bool CRegisteredCommands::AddCommand(CLuaMain* pLuaMain, const char* szKey, cons assert(pLuaMain); assert(szKey); + if (CommandExists(szKey, nullptr)) + { + CClientGame::MultiCommandHandlerPolicy allowMultiHandlers = g_pClientGame->GetAllowMultiCommandHandlers(); + + // If not allowing duplicate handlers, throw error and block + if (allowMultiHandlers == CClientGame::MultiCommandHandlerPolicy::DISABLED) + { + g_pClientGame->GetScriptDebugging()->LogError(pLuaMain->GetVM(), "addCommandHandler: Duplicate command registration blocked for '%s' (multiple handlers disabled)", szKey); + return false; + } + // If allowing with warning (default), log warning and proceed + else if (allowMultiHandlers == CClientGame::MultiCommandHandlerPolicy::ENABLED) + g_pClientGame->GetScriptDebugging()->LogWarning(pLuaMain->GetVM(), "addCommandHandler: Attempt to register duplicate command '%s'", szKey); + } + // Check if we already have this key and handler SCommand* pCommand = GetCommand(szKey, pLuaMain); if (pCommand) @@ -126,7 +142,7 @@ bool CRegisteredCommands::CommandExists(const char* szKey, CLuaMain* pLuaMain) { assert(szKey); - return GetCommand(szKey, pLuaMain) != NULL; + return GetCommand(szKey, pLuaMain) != nullptr; } bool CRegisteredCommands::ProcessCommand(const char* szKey, const char* szArguments) @@ -183,7 +199,7 @@ CRegisteredCommands::SCommand* CRegisteredCommands::GetCommand(const char* szKey } // Doesn't exist - return NULL; + return nullptr; } void CRegisteredCommands::CallCommandHandler(CLuaMain* pLuaMain, const CLuaFunctionRef& iLuaFunction, const char* szKey, const char* szArguments) @@ -204,7 +220,7 @@ void CRegisteredCommands::CallCommandHandler(CLuaMain* pLuaMain, const CLuaFunct while (arg) { Arguments.PushString(arg); - arg = strtok(NULL, " "); + arg = strtok(nullptr, " "); } delete[] szTempArguments; } diff --git a/Server/mods/deathmatch/logic/CGame.cpp b/Server/mods/deathmatch/logic/CGame.cpp index 486ea42963..2e5289eced 100644 --- a/Server/mods/deathmatch/logic/CGame.cpp +++ b/Server/mods/deathmatch/logic/CGame.cpp @@ -4700,9 +4700,10 @@ void CGame::SendSyncSettings(CPlayer* pPlayer) uchar ucAllowFastSprintFix = true; uchar ucAllowDrivebyAnimFix = true; uchar ucAllowShotgunDamageFix = true; + uchar allowMultiCommandHandlers = m_pMainConfig->GetAllowMultiCommandHandlers(); CSyncSettingsPacket packet(weaponTypesUsingBulletSync, ucVehExtrapolateEnabled, sVehExtrapolateBaseMs, sVehExtrapolatePercent, sVehExtrapolateMaxMs, - ucUseAltPulseOrder, ucAllowFastSprintFix, ucAllowDrivebyAnimFix, ucAllowShotgunDamageFix); + ucUseAltPulseOrder, ucAllowFastSprintFix, ucAllowDrivebyAnimFix, ucAllowShotgunDamageFix, allowMultiCommandHandlers); if (pPlayer) pPlayer->Send(packet); else diff --git a/Server/mods/deathmatch/logic/CMainConfig.cpp b/Server/mods/deathmatch/logic/CMainConfig.cpp index affe044153..0131193174 100644 --- a/Server/mods/deathmatch/logic/CMainConfig.cpp +++ b/Server/mods/deathmatch/logic/CMainConfig.cpp @@ -81,6 +81,7 @@ CMainConfig::CMainConfig(CConsole* pConsole) : CXMLConfig(NULL) m_bSyncMapElementData = true; m_elementDataWhitelisted = false; m_checkDuplicateSerials = true; + m_allowMultiCommandHandlers = 1; } bool CMainConfig::Load() @@ -539,6 +540,8 @@ bool CMainConfig::Load() GetBoolean(m_pRootNode, "elementdata_whitelisted", m_elementDataWhitelisted); GetBoolean(m_pRootNode, "check_duplicate_serials", m_checkDuplicateSerials); + GetInteger(m_pRootNode, "allow_multi_command_handlers", m_allowMultiCommandHandlers); + m_allowMultiCommandHandlers = Clamp(0, m_allowMultiCommandHandlers, 2); ApplyNetOptions(); diff --git a/Server/mods/deathmatch/logic/CMainConfig.h b/Server/mods/deathmatch/logic/CMainConfig.h index ffb0b8de8d..ea280c2f17 100644 --- a/Server/mods/deathmatch/logic/CMainConfig.h +++ b/Server/mods/deathmatch/logic/CMainConfig.h @@ -150,6 +150,7 @@ class CMainConfig : public CXMLConfig int GetPlayerTriggeredEventInterval() const { return m_iPlayerTriggeredEventIntervalMs; } int GetMaxPlayerTriggeredEventsPerInterval() const { return m_iMaxPlayerTriggeredEventsPerInterval; } + int GetAllowMultiCommandHandlers() const noexcept { return m_allowMultiCommandHandlers; } private: void RegisterCommand(const char* szName, FCommandHandler* pFunction, bool bRestricted, const char* szConsoleHelpText); @@ -235,4 +236,5 @@ class CMainConfig : public CXMLConfig bool m_elementDataWhitelisted; bool m_checkDuplicateSerials; int m_checkResourceClientFiles; + int m_allowMultiCommandHandlers; }; diff --git a/Server/mods/deathmatch/logic/CRegisteredCommands.cpp b/Server/mods/deathmatch/logic/CRegisteredCommands.cpp index 50706204fe..3f9769f2c8 100644 --- a/Server/mods/deathmatch/logic/CRegisteredCommands.cpp +++ b/Server/mods/deathmatch/logic/CRegisteredCommands.cpp @@ -18,6 +18,9 @@ #include "CClient.h" #include "CConsoleClient.h" #include "CPlayer.h" +#include "CGame.h" +#include "CScriptDebugging.h" +#include "CMainConfig.h" CRegisteredCommands::CRegisteredCommands(CAccessControlListManager* pACLManager) { @@ -35,11 +38,26 @@ bool CRegisteredCommands::AddCommand(CLuaMain* pLuaMain, const char* szKey, cons assert(pLuaMain); assert(szKey); + if (CommandExists(szKey, nullptr)) + { + int allowMultiCommandHandlers = g_pGame->GetConfig()->GetAllowMultiCommandHandlers(); + + if (allowMultiCommandHandlers == 0) + { + g_pGame->GetScriptDebugging()->LogError(pLuaMain->GetVM(), "addCommandHandler: Duplicate command registration blocked for '%s' (multiple handlers disabled)", szKey); + return false; + } + else if (allowMultiCommandHandlers == 1) + // Allow with warning (default behavior) + g_pGame->GetScriptDebugging()->LogWarning(pLuaMain->GetVM(), "Attempt to register duplicate command '%s'", szKey); + // For allowMultiCommandHandlers == 2, allow silently (no action needed) + } + // Check if we already have this key and handler SCommand* pCommand = GetCommand(szKey, pLuaMain); if (pCommand && iLuaFunction == pCommand->iLuaFunction) - return false; + return false; // Create the entry pCommand = new SCommand; @@ -139,7 +157,7 @@ bool CRegisteredCommands::CommandExists(const char* szKey, CLuaMain* pLuaMain) { assert(szKey); - return GetCommand(szKey, pLuaMain) != NULL; + return GetCommand(szKey, pLuaMain) != nullptr; } bool CRegisteredCommands::ProcessCommand(const char* szKey, const char* szArguments, CClient* pClient) @@ -203,7 +221,7 @@ CRegisteredCommands::SCommand* CRegisteredCommands::GetCommand(const char* szKey } // Doesn't exist - return NULL; + return nullptr; } void CRegisteredCommands::CallCommandHandler(CLuaMain* pLuaMain, const CLuaFunctionRef& iLuaFunction, const char* szKey, const char* szArguments, @@ -253,7 +271,7 @@ void CRegisteredCommands::CallCommandHandler(CLuaMain* pLuaMain, const CLuaFunct while (arg) { Arguments.PushString(arg); - arg = strtok(NULL, " "); + arg = strtok(nullptr, " "); } delete[] szTempArguments; diff --git a/Server/mods/deathmatch/logic/packets/CSyncSettingsPacket.cpp b/Server/mods/deathmatch/logic/packets/CSyncSettingsPacket.cpp index a4b959e94d..2ba5ab0ca7 100644 --- a/Server/mods/deathmatch/logic/packets/CSyncSettingsPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CSyncSettingsPacket.cpp @@ -15,7 +15,7 @@ CSyncSettingsPacket::CSyncSettingsPacket(const std::set& weaponTypesUsingBulletSync, uchar ucVehExtrapolateEnabled, short sVehExtrapolateBaseMs, short sVehExtrapolatePercent, short sVehExtrapolateMaxMs, uchar ucUseAltPulseOrder, uchar ucAllowFastSprintFix, - uchar ucAllowDrivebyAnimationFix, uchar ucAllowShotgunDamageFix) + uchar ucAllowDrivebyAnimationFix, uchar ucAllowShotgunDamageFix, uchar allowMultiCommandHandlers) { m_weaponTypesUsingBulletSync = weaponTypesUsingBulletSync; m_ucVehExtrapolateEnabled = ucVehExtrapolateEnabled; @@ -26,6 +26,7 @@ CSyncSettingsPacket::CSyncSettingsPacket(const std::set& weaponType m_ucAllowFastSprintFix = ucAllowFastSprintFix; m_ucAllowDrivebyAnimationFix = ucAllowDrivebyAnimationFix; m_ucAllowShotgunDamageFix = ucAllowShotgunDamageFix; + m_allowMultiCommandHandlers = allowMultiCommandHandlers; } bool CSyncSettingsPacket::Read(NetBitStreamInterface& BitStream) @@ -51,5 +52,7 @@ bool CSyncSettingsPacket::Write(NetBitStreamInterface& BitStream) const BitStream.Write(m_ucAllowFastSprintFix); BitStream.Write(m_ucAllowDrivebyAnimationFix); BitStream.Write(m_ucAllowShotgunDamageFix); + BitStream.Write(m_allowMultiCommandHandlers); + return true; } diff --git a/Server/mods/deathmatch/logic/packets/CSyncSettingsPacket.h b/Server/mods/deathmatch/logic/packets/CSyncSettingsPacket.h index b4c22df18f..66029330c9 100644 --- a/Server/mods/deathmatch/logic/packets/CSyncSettingsPacket.h +++ b/Server/mods/deathmatch/logic/packets/CSyncSettingsPacket.h @@ -19,7 +19,7 @@ class CSyncSettingsPacket final : public CPacket CSyncSettingsPacket(){}; CSyncSettingsPacket(const std::set& weaponTypesUsingBulletSync, uchar ucVehExtrapolateEnabled, short sVehExtrapolateBaseMs, short sVehExtrapolatePercent, short sVehExtrapolateMaxMs, uchar ucUseAltPulseOrder, uchar ucAllowFastSprintFix, - uchar ucAllowDrivebyAnimationFix, uchar ucAllowShotgunDamageFix); + uchar ucAllowDrivebyAnimationFix, uchar ucAllowShotgunDamageFix, uchar allowMultiCommandHandlers); ePacketID GetPacketID() const { return PACKET_ID_SYNC_SETTINGS; }; unsigned long GetFlags() const { return PACKET_HIGH_PRIORITY | PACKET_RELIABLE | PACKET_SEQUENCED; }; @@ -36,4 +36,5 @@ class CSyncSettingsPacket final : public CPacket uchar m_ucAllowFastSprintFix; uchar m_ucAllowDrivebyAnimationFix; uchar m_ucAllowShotgunDamageFix; + uchar m_allowMultiCommandHandlers; }; diff --git a/Server/mods/deathmatch/mtaserver.conf b/Server/mods/deathmatch/mtaserver.conf index 2dd6960ae0..21b736a6bb 100644 --- a/Server/mods/deathmatch/mtaserver.conf +++ b/Server/mods/deathmatch/mtaserver.conf @@ -94,6 +94,11 @@ Available values: special detection (SD) codes ; (e.g. To enable special detection #15 use: 15) --> + + 1 + + + 1 + 1 + diff --git a/Shared/sdk/net/bitstream.h b/Shared/sdk/net/bitstream.h index d8df501756..a22bc57441 100644 --- a/Shared/sdk/net/bitstream.h +++ b/Shared/sdk/net/bitstream.h @@ -433,4 +433,4 @@ struct ISyncStructure virtual ~ISyncStructure() {} virtual bool Read(NetBitStreamInterface& bitStream) = 0; virtual void Write(NetBitStreamInterface& bitStream) const = 0; -}; +}; \ No newline at end of file