diff --git a/Client/mods/deathmatch/logic/CClientEntity.cpp b/Client/mods/deathmatch/logic/CClientEntity.cpp index 33b74e9b16..3ea7b77bc4 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.cpp +++ b/Client/mods/deathmatch/logic/CClientEntity.cpp @@ -498,14 +498,14 @@ void CClientEntity::SetCustomData(const CStringName& name, const CLuaArgument& V CallEvent("onClientElementDataChange", Arguments, true); } -void CClientEntity::DeleteCustomData(const CStringName& name) +bool CClientEntity::DeleteCustomData(const CStringName& name) { // Grab the old variable - SCustomData* pData = m_pCustomData->Get(name); - if (pData) + auto* data = m_pCustomData->Get(name); + if (data) { CLuaArgument oldVariable; - oldVariable = pData->Variable; + oldVariable = data->Variable; // Delete the custom data m_pCustomData->Delete(name); @@ -516,7 +516,10 @@ void CClientEntity::DeleteCustomData(const CStringName& name) Arguments.PushArgument(oldVariable); Arguments.PushArgument(CLuaArgument()); // Use nil as the new value to indicate the data has been removed CallEvent("onClientElementDataChange", Arguments, true); + return true; } + + return false; } bool CClientEntity::GetMatrix(CMatrix& matrix) const diff --git a/Client/mods/deathmatch/logic/CClientEntity.h b/Client/mods/deathmatch/logic/CClientEntity.h index 4ddd8baefb..723da075e7 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.h +++ b/Client/mods/deathmatch/logic/CClientEntity.h @@ -209,7 +209,7 @@ class CClientEntity : public CClientEntityBase bool GetCustomDataInt(const CStringName& name, int& iOut, bool bInheritData); bool GetCustomDataBool(const CStringName& name, bool& bOut, bool bInheritData); void SetCustomData(const CStringName& name, const CLuaArgument& Variable, bool bSynchronized = true); - void DeleteCustomData(const CStringName& name); + bool DeleteCustomData(const CStringName& name); virtual bool GetMatrix(CMatrix& matrix) const; virtual bool SetMatrix(const CMatrix& matrix); diff --git a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index dcd11a4e98..38db42db2a 100644 --- a/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Client/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -1056,8 +1056,31 @@ 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); + + if (Entity.IsLocalEntity()) + return Entity.DeleteCustomData(name); + + bool isSynced = false; + auto* currentVariable = Entity.GetCustomData(name, false, &isSynced); + if (!currentVariable) + return false; + + if (isSynced) + { + auto bitStream = g_pNet->AllocateNetBitStream(); + // Omitting the value reuses the custom data packet for removals without adding another client-to-server RPC. + bitStream->Write(Entity.GetID()); + std::uint16_t nameLength = static_cast(name->length()); + bitStream->WriteCompressed(nameLength); + bitStream->Write(name.ToCString(), nameLength); + + g_pNet->SendPacket(PACKET_ID_CUSTOM_DATA, bitStream, PACKET_PRIORITY_HIGH, PACKET_RELIABILITY_RELIABLE_ORDERED); + g_pNet->DeallocateNetBitStream(bitStream); + } + + return Entity.DeleteCustomData(name); } 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 0073f4f88b..d5058f3417 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaElementDefs.cpp @@ -78,7 +78,7 @@ void CLuaElementDefs::LoadFunctions() {"setElementID", SetElementID}, {"setElementParent", SetElementParent}, {"setElementData", SetElementData}, - // {"removeElementData", RemoveElementData}, TODO Clientside + {"removeElementData", RemoveElementData}, {"setElementMatrix", SetElementMatrix}, {"setElementPosition", SetElementPosition}, {"setElementRotation", SetElementRotation}, @@ -122,7 +122,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 d6938b5d4c..fc5858903b 100644 --- a/Server/mods/deathmatch/logic/CElement.cpp +++ b/Server/mods/deathmatch/logic/CElement.cpp @@ -752,14 +752,14 @@ bool CElement::SetCustomData(const CStringName& name, const CLuaArgument& Variab return true; } -bool CElement::DeleteCustomData(const CStringName& name) +bool CElement::DeleteCustomData(const CStringName& name, CPlayer* client) { // Grab the old variable - SCustomData* pData = m_CustomData.Get(name); - if (pData) + auto* data = m_CustomData.Get(name); + if (data) { - CLuaArgument oldVariable = pData->Variable; - ESyncType oldSyncType = pData->syncType; + CLuaArgument oldVariable = data->Variable; + ESyncType oldSyncType = data->syncType; // Delete the custom data m_CustomData.Delete(name); @@ -769,7 +769,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, client)) { // 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..521246fbcd 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* client = nullptr); void SendAllCustomData(CPlayer* pPlayer); CXMLNode* OutputToXML(CXMLNode* pNode); diff --git a/Server/mods/deathmatch/logic/CGame.cpp b/Server/mods/deathmatch/logic/CGame.cpp index 6bc5889a55..71a086506d 100644 --- a/Server/mods/deathmatch/logic/CGame.cpp +++ b/Server/mods/deathmatch/logic/CGame.cpp @@ -2712,85 +2712,134 @@ void CGame::Packet_LuaEvent(CLuaEventPacket& Packet) } } -void CGame::Packet_CustomData(CCustomDataPacket& Packet) +void CGame::Packet_CustomData(CCustomDataPacket& packet) { // Got a valid source? - CPlayer* pSourcePlayer = Packet.GetSourcePlayer(); - if (pSourcePlayer) + auto* sourcePlayer = packet.GetSourcePlayer(); + if (sourcePlayer) { // Grab the element - ElementID ID = Packet.GetElementID(); - CElement* pElement = CElementIDs::GetElement(ID); - if (pElement) + ElementID elementId = packet.GetElementID(); + auto* element = CElementIDs::GetElement(elementId); + if (element) { // Change the data - const char* szName = Packet.GetName(); - CLuaArgument& Value = Packet.GetValue(); + const char* name = packet.GetName(); + CLuaArgument& value = packet.GetValue(); + const bool isDelete = packet.IsDelete(); // Ignore if the wrong length - if (strlen(szName) > MAX_CUSTOMDATA_NAME_LENGTH) + if (strlen(name) > MAX_CUSTOMDATA_NAME_LENGTH) { - CLogger::ErrorPrintf("Received oversized custom data name from %s (%s)\n", Packet.GetSourcePlayer()->GetNick(), - *SStringX(szName).Left(MAX_CUSTOMDATA_NAME_LENGTH + 1)); + CLogger::ErrorPrintf("Received oversized custom data name from %s (%s)\n", sourcePlayer->GetNick(), + *SStringX(name).Left(MAX_CUSTOMDATA_NAME_LENGTH + 1)); return; } ESyncType lastSyncType = ESyncType::BROADCAST; eCustomDataClientTrust clientChangesMode{}; - pElement->GetCustomData(szName, false, &lastSyncType, &clientChangesMode); + element->GetCustomData(name, false, &lastSyncType, &clientChangesMode); const bool changesAllowed = clientChangesMode == eCustomDataClientTrust::UNSET ? !m_pMainConfig->IsElementDataWhitelisted() : clientChangesMode == eCustomDataClientTrust::ALLOW; if (!changesAllowed) { - CLogger::ErrorPrintf("Client trying to change protected element data %s (%s)\n", Packet.GetSourcePlayer()->GetNick(), szName); + CLogger::ErrorPrintf("Client trying to change protected element data %s (%s)\n", sourcePlayer->GetNick(), name); CLuaArguments arguments; - arguments.PushElement(pElement); - arguments.PushString(szName); - arguments.PushArgument(Value); - pSourcePlayer->CallEvent("onPlayerChangesProtectedData", arguments); + arguments.PushElement(element); + arguments.PushString(name); + arguments.PushArgument(value); + sourcePlayer->CallEvent("onPlayerChangesProtectedData", arguments); return; } - if (pElement->SetCustomData(szName, Value, lastSyncType, pSourcePlayer)) + if (isDelete) { - if (lastSyncType != ESyncType::LOCAL) + if (element->DeleteCustomData(name, sourcePlayer)) { - // Tell our clients to update their data. Send to everyone but the one we got this packet from. - unsigned short usNameLength = static_cast(strlen(szName)); - CBitStream BitStream; - BitStream.pBitStream->WriteCompressed(usNameLength); - BitStream.pBitStream->Write(szName, usNameLength); - Value.WriteToBitStream(*BitStream.pBitStream); - if (lastSyncType == ESyncType::BROADCAST) - m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pElement, SET_ELEMENT_DATA, *BitStream.pBitStream), pSourcePlayer); - else - m_pPlayerManager->BroadcastOnlySubscribed(CElementRPCPacket(pElement, SET_ELEMENT_DATA, *BitStream.pBitStream), pElement, szName, - pSourcePlayer); + if (lastSyncType != ESyncType::LOCAL) + { + // Tell our clients to remove their data. Send to everyone but the one we got this packet from. + std::uint16_t nameLength = static_cast(strlen(name)); + CBitStream bitStream; + bitStream.pBitStream->WriteCompressed(nameLength); + bitStream.pBitStream->Write(name, nameLength); + bitStream.pBitStream->WriteBit(false); // Unused (was recursive flag) + if (lastSyncType == ESyncType::BROADCAST) + m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(element, REMOVE_ELEMENT_DATA, *bitStream.pBitStream), sourcePlayer); + else + m_pPlayerManager->BroadcastOnlySubscribed(CElementRPCPacket(element, REMOVE_ELEMENT_DATA, *bitStream.pBitStream), element, name, + sourcePlayer); - CPerfStatEventPacketUsage::GetSingleton()->UpdateElementDataUsageRelayed(szName, m_pPlayerManager->Count(), - BitStream.pBitStream->GetNumberOfBytesUsed()); + CPerfStatEventPacketUsage::GetSingleton()->UpdateElementDataUsageRelayed(name, m_pPlayerManager->Count(), + bitStream.pBitStream->GetNumberOfBytesUsed()); + } + + if (lastSyncType == ESyncType::SUBSCRIBE) + m_pPlayerManager->ClearElementData(element, name); + } + else + { + // Restore the element data on the client, because the server cancelled the change in onElementDataChange. + std::uint16_t nameLength = static_cast(strlen(name)); + CBitStream bitStream; + bitStream.pBitStream->WriteCompressed(nameLength); + bitStream.pBitStream->Write(name, nameLength); + + if (auto* serverValue = element->GetCustomData(name, false)) + { + serverValue->WriteToBitStream(*bitStream.pBitStream); + sourcePlayer->Send(CElementRPCPacket(element, SET_ELEMENT_DATA, *bitStream.pBitStream)); + } + else + { + bitStream.pBitStream->WriteBit(false); + sourcePlayer->Send(CElementRPCPacket(element, REMOVE_ELEMENT_DATA, *bitStream.pBitStream)); + } } } else { - // Event was cancelled; sync the authoritative value back to the source player - unsigned short usNameLength = static_cast(strlen(szName)); - CBitStream BitStream; - BitStream.pBitStream->WriteCompressed(usNameLength); - BitStream.pBitStream->Write(szName, usNameLength); - - if (CLuaArgument* pServerValue = pElement->GetCustomData(szName, false)) + if (element->SetCustomData(name, value, lastSyncType, sourcePlayer)) { - pServerValue->WriteToBitStream(*BitStream.pBitStream); - pSourcePlayer->Send(CElementRPCPacket(pElement, SET_ELEMENT_DATA, *BitStream.pBitStream)); + if (lastSyncType != ESyncType::LOCAL) + { + // Tell our clients to update their data. Send to everyone but the one we got this packet from. + std::uint16_t nameLength = static_cast(strlen(name)); + CBitStream bitStream; + bitStream.pBitStream->WriteCompressed(nameLength); + bitStream.pBitStream->Write(name, nameLength); + value.WriteToBitStream(*bitStream.pBitStream); + if (lastSyncType == ESyncType::BROADCAST) + m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(element, SET_ELEMENT_DATA, *bitStream.pBitStream), sourcePlayer); + else + m_pPlayerManager->BroadcastOnlySubscribed(CElementRPCPacket(element, SET_ELEMENT_DATA, *bitStream.pBitStream), element, name, + sourcePlayer); + + CPerfStatEventPacketUsage::GetSingleton()->UpdateElementDataUsageRelayed(name, m_pPlayerManager->Count(), + bitStream.pBitStream->GetNumberOfBytesUsed()); + } } else { - BitStream.pBitStream->WriteBit(false); - pSourcePlayer->Send(CElementRPCPacket(pElement, REMOVE_ELEMENT_DATA, *BitStream.pBitStream)); + // Restore the element data on the client, because the server cancelled the change in onElementDataChange. + std::uint16_t nameLength = static_cast(strlen(name)); + CBitStream bitStream; + bitStream.pBitStream->WriteCompressed(nameLength); + bitStream.pBitStream->Write(name, nameLength); + + if (auto* serverValue = element->GetCustomData(name, false)) + { + serverValue->WriteToBitStream(*bitStream.pBitStream); + sourcePlayer->Send(CElementRPCPacket(element, SET_ELEMENT_DATA, *bitStream.pBitStream)); + } + else + { + bitStream.pBitStream->WriteBit(false); + sourcePlayer->Send(CElementRPCPacket(element, REMOVE_ELEMENT_DATA, *bitStream.pBitStream)); + } } } } diff --git a/Server/mods/deathmatch/logic/CGame.h b/Server/mods/deathmatch/logic/CGame.h index 9d0ace7b1a..0330c4e40c 100644 --- a/Server/mods/deathmatch/logic/CGame.h +++ b/Server/mods/deathmatch/logic/CGame.h @@ -510,7 +510,7 @@ class CGame void Packet_Vehicle_InOut(class CVehicleInOutPacket& Packet); void Packet_VehicleTrailer(class CVehicleTrailerPacket& Packet); void Packet_LuaEvent(class CLuaEventPacket& Packet); - void Packet_CustomData(class CCustomDataPacket& Packet); + void Packet_CustomData(class CCustomDataPacket& packet); void Packet_Voice_Data(class CVoiceDataPacket& Packet); void Packet_Voice_End(class CVoiceEndPacket& Packet); void Packet_CameraSync(class CCameraSyncPacket& Packet); diff --git a/Server/mods/deathmatch/logic/CRPCFunctions.cpp b/Server/mods/deathmatch/logic/CRPCFunctions.cpp index 2965dd3871..c95111be6a 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; diff --git a/Server/mods/deathmatch/logic/packets/CCustomDataPacket.cpp b/Server/mods/deathmatch/logic/packets/CCustomDataPacket.cpp index 064b8f2f99..b9f8955e5d 100644 --- a/Server/mods/deathmatch/logic/packets/CCustomDataPacket.cpp +++ b/Server/mods/deathmatch/logic/packets/CCustomDataPacket.cpp @@ -16,6 +16,7 @@ CCustomDataPacket::CCustomDataPacket() { m_szName = NULL; + m_isDelete = false; } CCustomDataPacket::~CCustomDataPacket() @@ -24,26 +25,28 @@ CCustomDataPacket::~CCustomDataPacket() m_szName = NULL; } -bool CCustomDataPacket::Read(NetBitStreamInterface& BitStream) +bool CCustomDataPacket::Read(NetBitStreamInterface& bitStream) { - unsigned short usNameLength; - if (BitStream.Read(m_ElementID) && BitStream.ReadCompressed(usNameLength) && usNameLength > 0 && usNameLength <= MAX_CUSTOMDATA_NAME_LENGTH) + std::uint16_t nameLength; + if (bitStream.Read(m_ElementID) && bitStream.ReadCompressed(nameLength) && nameLength > 0 && nameLength <= MAX_CUSTOMDATA_NAME_LENGTH) { - m_szName = new char[usNameLength + 1]; - if (BitStream.Read(m_szName, usNameLength)) + m_szName = new char[nameLength + 1]; + if (bitStream.Read(m_szName, nameLength)) { - m_szName[usNameLength] = 0; - if (m_Value.ReadFromBitStream(BitStream)) - { + m_szName[nameLength] = 0; + if (m_Value.ReadFromBitStream(bitStream)) return true; - } + + // Clients leave out the value when requesting deletion, so the existing custom-data packet can also serve as RemoveElementData. + m_isDelete = true; + return true; } } return false; } -bool CCustomDataPacket::Write(NetBitStreamInterface& BitStream) const +bool CCustomDataPacket::Write(NetBitStreamInterface& bitStream) const { return true; } diff --git a/Server/mods/deathmatch/logic/packets/CCustomDataPacket.h b/Server/mods/deathmatch/logic/packets/CCustomDataPacket.h index 922a2cee6c..ae81a12de2 100644 --- a/Server/mods/deathmatch/logic/packets/CCustomDataPacket.h +++ b/Server/mods/deathmatch/logic/packets/CCustomDataPacket.h @@ -23,15 +23,17 @@ class CCustomDataPacket final : public CPacket ePacketID GetPacketID() const { return PACKET_ID_CUSTOM_DATA; }; unsigned long GetFlags() const { return PACKET_HIGH_PRIORITY | PACKET_RELIABLE | PACKET_SEQUENCED; }; - bool Read(NetBitStreamInterface& BitStream); - bool Write(NetBitStreamInterface& BitStream) const; + bool Read(NetBitStreamInterface& bitStream); + bool Write(NetBitStreamInterface& bitStream) const; ElementID GetElementID() { return m_ElementID; } char* GetName() { return m_szName; } CLuaArgument& GetValue() { return m_Value; } + bool IsDelete() const { return m_isDelete; } private: ElementID m_ElementID; char* m_szName; CLuaArgument m_Value; + bool m_isDelete; };