diff --git a/Client/mods/deathmatch/logic/CNetAPI.h b/Client/mods/deathmatch/logic/CNetAPI.h index d3ed4cec57..f587d96638 100644 --- a/Client/mods/deathmatch/logic/CNetAPI.h +++ b/Client/mods/deathmatch/logic/CNetAPI.h @@ -34,6 +34,7 @@ enum eServerRPCFunctions KEY_BIND, CURSOR_EVENT, REQUEST_STEALTH_KILL, + REMOVE_ELEMENT_DATA_RPC, }; class CNetAPI diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index e104e13740..744f4a080c 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -1062,8 +1062,28 @@ bool CStaticFunctionDefinitions::SetElementData(CClientEntity& Entity, CStringNa bool CStaticFunctionDefinitions::RemoveElementData(CClientEntity& Entity, CStringName name) { - // TODO - return false; + assert(name); + assert(name->length() <= MAX_CUSTOMDATA_NAME_LENGTH); + + bool isSynced; + CLuaArgument* currentVariable = Entity.GetCustomData(name, false, &isSynced); + if (!currentVariable) + return false; + + if (isSynced && !Entity.IsLocalEntity()) + { + NetBitStreamInterface* pBitStream = g_pNet->AllocateNetBitStream(); + // Write element ID and name for server-side removal handling + pBitStream->Write(Entity.GetID()); + pBitStream->WriteString(name.ToString()); + + // Send RPC and deallocate + g_pClientGame->GetNetAPI()->RPC(REMOVE_ELEMENT_DATA_RPC, pBitStream); + g_pNet->DeallocateNetBitStream(pBitStream); + } + + Entity.DeleteCustomData(name); + return true; } bool CStaticFunctionDefinitions::SetElementMatrix(CClientEntity& Entity, const CMatrix& matrix) diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp index 2fe3401fc7..ab162fbbc5 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp @@ -77,7 +77,7 @@ void CLuaElementDefs::LoadFunctions() {"setElementID", SetElementID}, {"setElementParent", SetElementParent}, {"setElementData", SetElementData}, - // {"removeElementData", RemoveElementData}, TODO Clientside + {"removeElementData", RemoveElementData}, {"setElementMatrix", SetElementMatrix}, {"setElementPosition", SetElementPosition}, {"setElementRotation", SetElementRotation}, @@ -121,7 +121,7 @@ void CLuaElementDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "attach", "attachElements"); lua_classfunction(luaVM, "detach", "detachElements"); lua_classfunction(luaVM, "destroy", "destroyElement"); - + lua_classfunction(luaVM, "removeData", "removeElementData"); // Get functions lua_classfunction(luaVM, "getCollisionsEnabled", "getElementCollisionsEnabled"); lua_classfunction(luaVM, "isWithinColShape", "isElementWithinColShape"); diff --git a/Server/mods/deathmatch/logic/CElement.cpp b/Server/mods/deathmatch/logic/CElement.cpp index c30ddf5640..9c470a0e02 100644 --- a/Server/mods/deathmatch/logic/CElement.cpp +++ b/Server/mods/deathmatch/logic/CElement.cpp @@ -753,7 +753,7 @@ bool CElement::SetCustomData(const CStringName& name, const CLuaArgument& Variab return true; } -bool CElement::DeleteCustomData(const CStringName& name) +bool CElement::DeleteCustomData(const CStringName& name, CPlayer* pClient) { // Grab the old variable SCustomData* pData = m_CustomData.Get(name); @@ -770,7 +770,7 @@ bool CElement::DeleteCustomData(const CStringName& name) Arguments.PushString(name); Arguments.PushArgument(oldVariable); Arguments.PushArgument(CLuaArgument()); // Use nil as the new value to indicate the data has been removed - if (!CallEvent("onElementDataChange", Arguments)) + if (!CallEvent("onElementDataChange", Arguments, pClient)) { // Event was cancelled, restore previous value m_CustomData.Set(name, oldVariable, oldSyncType); diff --git a/Server/mods/deathmatch/logic/CElement.h b/Server/mods/deathmatch/logic/CElement.h index f607861b0f..6a1a5d4a84 100644 --- a/Server/mods/deathmatch/logic/CElement.h +++ b/Server/mods/deathmatch/logic/CElement.h @@ -148,7 +148,7 @@ class CElement bool GetCustomDataBool(const CStringName& name, bool& bOut, bool bInheritData); bool SetCustomData(const CStringName& name, const CLuaArgument& Variable, ESyncType syncType = ESyncType::BROADCAST, CPlayer* pClient = NULL, bool bTriggerEvent = true); - bool DeleteCustomData(const CStringName& name); + bool DeleteCustomData(const CStringName& name, CPlayer* pClient = nullptr); void SendAllCustomData(CPlayer* pPlayer); CXMLNode* OutputToXML(CXMLNode* pNode); diff --git a/Server/mods/deathmatch/logic/CPerfStat.RPCPacketUsage.cpp b/Server/mods/deathmatch/logic/CPerfStat.RPCPacketUsage.cpp index cdebcedcc2..9daf426ce8 100644 --- a/Server/mods/deathmatch/logic/CPerfStat.RPCPacketUsage.cpp +++ b/Server/mods/deathmatch/logic/CPerfStat.RPCPacketUsage.cpp @@ -243,6 +243,7 @@ ADD_ENUM1(CRPCFunctions::PLAYER_WEAPON) ADD_ENUM1(CRPCFunctions::KEY_BIND) ADD_ENUM1(CRPCFunctions::CURSOR_EVENT) ADD_ENUM1(CRPCFunctions::REQUEST_STEALTH_KILL) +ADD_ENUM1(CRPCFunctions::REMOVE_ELEMENT_DATA_RPC) IMPLEMENT_ENUM_END("eRPCFunctions") struct SRPCPacketStat diff --git a/Server/mods/deathmatch/logic/CRPCFunctions.cpp b/Server/mods/deathmatch/logic/CRPCFunctions.cpp index 2965dd3871..66c2c084d4 100644 --- a/Server/mods/deathmatch/logic/CRPCFunctions.cpp +++ b/Server/mods/deathmatch/logic/CRPCFunctions.cpp @@ -19,6 +19,7 @@ #include "CPerfStatManager.h" #include "CKeyBinds.h" #include "CStaticFunctionDefinitions.h" +#include "packets/CElementRPCPacket.h" #include "net/SyncStructures.h" CRPCFunctions* g_pRPCFunctions = NULL; @@ -57,6 +58,7 @@ void CRPCFunctions::AddHandlers() AddHandler(KEY_BIND, KeyBind); AddHandler(CURSOR_EVENT, CursorEvent); AddHandler(REQUEST_STEALTH_KILL, RequestStealthKill); + AddHandler(REMOVE_ELEMENT_DATA_RPC, RemoveElementData); } void CRPCFunctions::AddHandler(unsigned char ucID, pfnRPCHandler Callback) @@ -366,3 +368,72 @@ void CRPCFunctions::RequestStealthKill(NetBitStreamInterface& bitStream) } UNCLOCK("NetServerPulse::RPC", "RequestStealthKill"); } + +void CRPCFunctions::RemoveElementData(NetBitStreamInterface& bitStream) +{ + if (!m_pSourcePlayer->IsJoined()) + return; + + CLOCK("NetServerPulse::RPC", "RemoveElementData"); + + ElementID elementId; + std::string customDataName; + if (!bitStream.Read(elementId) || !bitStream.ReadString(customDataName) || customDataName.empty() || customDataName.length() > MAX_CUSTOMDATA_NAME_LENGTH) + { + UNCLOCK("NetServerPulse::RPC", "RemoveElementData"); + return; + } + + CElement* element = CElementIDs::GetElement(elementId); + if (!element) + { + UNCLOCK("NetServerPulse::RPC", "RemoveElementData"); + return; + } + + const CStringName customDataNameId(customDataName); + ESyncType lastSyncType = ESyncType::BROADCAST; + eCustomDataClientTrust clientChangesMode{}; + element->GetCustomData(customDataNameId, false, &lastSyncType, &clientChangesMode); + + const bool changesAllowed = clientChangesMode == eCustomDataClientTrust::UNSET ? !g_pGame->GetConfig()->IsElementDataWhitelisted() + : clientChangesMode == eCustomDataClientTrust::ALLOW; + if (!changesAllowed) + { + CLogger::ErrorPrintf("Client trying to change protected element data %s (%s)\n", m_pSourcePlayer->GetNick(), customDataName.c_str()); + + CLuaArguments arguments; + arguments.PushElement(element); + arguments.PushString(customDataName.c_str()); + arguments.PushArgument(CLuaArgument()); + m_pSourcePlayer->CallEvent("onPlayerChangesProtectedData", arguments); + UNCLOCK("NetServerPulse::RPC", "RemoveElementData"); + return; + } + + if (element->DeleteCustomData(customDataNameId, m_pSourcePlayer)) + { + if (lastSyncType != ESyncType::LOCAL) + { + CBitStream outBitStream; + std::uint16_t nameLength = static_cast(customDataName.length()); + outBitStream.pBitStream->WriteCompressed(nameLength); + outBitStream.pBitStream->Write(customDataName.c_str(), nameLength); + outBitStream.pBitStream->WriteBit(false); // Unused (was recursive flag) + + if (lastSyncType == ESyncType::BROADCAST) + m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(element, REMOVE_ELEMENT_DATA, *outBitStream.pBitStream), m_pSourcePlayer); + else + m_pPlayerManager->BroadcastOnlySubscribed(CElementRPCPacket(element, REMOVE_ELEMENT_DATA, *outBitStream.pBitStream), element, + customDataNameId.ToCString(), m_pSourcePlayer); + + CPerfStatEventPacketUsage::GetSingleton()->UpdateElementDataUsageRelayed(customDataName.c_str(), m_pPlayerManager->Count(), + outBitStream.pBitStream->GetNumberOfBytesUsed()); + } + + if (lastSyncType == ESyncType::SUBSCRIBE) + m_pPlayerManager->ClearElementData(element, customDataNameId); + } + + UNCLOCK("NetServerPulse::RPC", "RemoveElementData"); +} diff --git a/Server/mods/deathmatch/logic/CRPCFunctions.h b/Server/mods/deathmatch/logic/CRPCFunctions.h index 20736c5adb..fc6a908d1b 100644 --- a/Server/mods/deathmatch/logic/CRPCFunctions.h +++ b/Server/mods/deathmatch/logic/CRPCFunctions.h @@ -50,6 +50,7 @@ class CRPCFunctions DECLARE_RPC(KeyBind); DECLARE_RPC(CursorEvent); DECLARE_RPC(RequestStealthKill); + DECLARE_RPC(RemoveElementData); protected: static CPlayer* m_pSourcePlayer; @@ -66,5 +67,6 @@ class CRPCFunctions KEY_BIND, CURSOR_EVENT, REQUEST_STEALTH_KILL, + REMOVE_ELEMENT_DATA_RPC, }; };