diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index c8c126b6b..6c7ec0eeb 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -11,7 +11,7 @@ #include #include "DestroyableComponent.h" -#include "EchoSyncSkill.h" +#include "SkillMessages.h" #include "PhantomPhysicsComponent.h" #include "QuickBuildComponent.h" #include "eReplicaComponentType.h" @@ -201,7 +201,8 @@ void BehaviorContext::UpdatePlayerSyncs(float deltaTime) { } if (this->skillUId != 0 && !clientInitalized) { - EchoSyncSkill echo; + GameMessages::EchoSyncSkill echo; + echo.target = this->originator; echo.bDone = true; echo.uiSkillHandle = this->skillUId; echo.uiBehaviorHandle = entry.handle; @@ -211,12 +212,7 @@ void BehaviorContext::UpdatePlayerSyncs(float deltaTime) { echo.sBitStream.assign(reinterpret_cast(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); - RakNet::BitStream message; - BitStreamUtils::WriteHeader(message, ServiceType::CLIENT, MessageType::Client::GAME_MSG); - message.Write(this->originator); - echo.Serialize(message); - - Game::server->Send(message, UNASSIGNED_SYSTEM_ADDRESS, true); + echo.Send(UNASSIGNED_SYSTEM_ADDRESS); } this->syncEntries.erase(this->syncEntries.begin() + i); @@ -268,7 +264,8 @@ bool BehaviorContext::CalculateUpdate(const float deltaTime) { } // Echo sync - EchoSyncSkill echo; + GameMessages::EchoSyncSkill echo; + echo.target = this->originator; echo.bDone = true; echo.uiBehaviorHandle = entry.handle; @@ -282,14 +279,7 @@ bool BehaviorContext::CalculateUpdate(const float deltaTime) { if (!clientInitalized) { echo.sBitStream.assign(reinterpret_cast(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); - // Write message - RakNet::BitStream message; - - BitStreamUtils::WriteHeader(message, ServiceType::CLIENT, MessageType::Client::GAME_MSG); - message.Write(this->originator); - echo.Serialize(message); - - Game::server->Send(message, UNASSIGNED_SYSTEM_ADDRESS, true); + echo.Send(UNASSIGNED_SYSTEM_ADDRESS); } ExecuteUpdates(); diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index a6fc452e2..56f54a437 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -15,6 +15,7 @@ #include "GameMessages.h" #include "InventoryMessages.h" #include "Database.h" +#include "SkillMessages.h" #include "SkillComponent.h" #include "Character.h" #include "EntityManager.h" @@ -1324,14 +1325,25 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) { // The client stores one acquiredSkillsInfo entry per skillID, tagged with the slotID // it was originally added with. Always send RemoveSkill to clear that entry, then // re-add with the surviving slot so the client shows it in the correct place. - GameMessages::SendRemoveSkill(m_Parent, skillId); + GameMessages::RemoveSkill removeSkill; + removeSkill.target = m_Parent->GetObjectID(); + removeSkill.skillID = skillId; + removeSkill.SendToClient(m_Parent->GetSystemAddress()); if (surviving != m_Skills.end()) { - GameMessages::SendAddSkill(m_Parent, skillId, surviving->first); + GameMessages::AddSkill addSkill; + addSkill.target = m_Parent->GetObjectID(); + addSkill.skillID = skillId; + addSkill.slotID = surviving->first; + addSkill.SendToClient(m_Parent->GetSystemAddress()); } if (slot == BehaviorSlot::Primary) { m_Skills.insert_or_assign(BehaviorSlot::Primary, 1); - GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary); + GameMessages::AddSkill addSkill; + addSkill.target = m_Parent->GetObjectID(); + addSkill.skillID = 1; + addSkill.slotID = BehaviorSlot::Primary; + addSkill.SendToClient(m_Parent->GetSystemAddress()); } } @@ -1784,7 +1796,10 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) { return pair.first != slot && pair.second == old; }); if (!usedElsewhere) { - GameMessages::SendRemoveSkill(m_Parent, old); + GameMessages::RemoveSkill removeSkill; + removeSkill.target = m_Parent->GetObjectID(); + removeSkill.skillID = old; + removeSkill.SendToClient(m_Parent->GetSystemAddress()); } } @@ -1795,7 +1810,11 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) { return pair.first != slot && pair.second == skillId; }); if (!alreadyKnown) { - GameMessages::SendAddSkill(m_Parent, skillId, slot); + GameMessages::AddSkill addSkill; + addSkill.target = m_Parent->GetObjectID(); + addSkill.skillID = skillId; + addSkill.slotID = slot; + addSkill.SendToClient(m_Parent->GetSystemAddress()); } m_Skills.insert_or_assign(slot, skillId); diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 1d4eb29d6..c2771816f 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -19,8 +19,7 @@ #include "BaseCombatAIComponent.h" #include "ScriptComponent.h" #include "BuffComponent.h" -#include "EchoStartSkill.h" -#include "DoClientProjectileImpact.h" +#include "SkillMessages.h" #include "CDClientManager.h" #include "CDSkillBehaviorTable.h" #include "ServiceType.h" @@ -301,7 +300,8 @@ SkillExecutionResult SkillComponent::CalculateBehavior( if (!clientInitalized) { // Echo start skill - EchoStartSkill start; + GameMessages::EchoStartSkill start; + start.target = this->m_Parent->GetObjectID(); start.iCastType = castType; start.skillID = skillId; @@ -322,14 +322,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior( start.sBitStream.assign(reinterpret_cast(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); - // Write message - RakNet::BitStream message; - - BitStreamUtils::WriteHeader(message, ServiceType::CLIENT, MessageType::Client::GAME_MSG); - message.Write(this->m_Parent->GetObjectID()); - start.Serialize(message); - - Game::server->Send(message, UNASSIGNED_SYSTEM_ADDRESS, true); + start.Send(UNASSIGNED_SYSTEM_ADDRESS); } context->ExecuteUpdates(); @@ -447,20 +440,15 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry) behavior->Calculate(entry.context, bitStream, entry.branchContext); - DoClientProjectileImpact projectileImpact; + GameMessages::DoClientProjectileImpact projectileImpact; + projectileImpact.target = this->m_Parent->GetObjectID(); projectileImpact.sBitStream.assign(reinterpret_cast(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); projectileImpact.i64OwnerID = this->m_Parent->GetObjectID(); projectileImpact.i64OrgID = entry.id; projectileImpact.i64TargetID = entry.branchContext.target; - RakNet::BitStream message; - - BitStreamUtils::WriteHeader(message, ServiceType::CLIENT, MessageType::Client::GAME_MSG); - message.Write(this->m_Parent->GetObjectID()); - projectileImpact.Serialize(message); - - Game::server->Send(message, UNASSIGNED_SYSTEM_ADDRESS, true); + projectileImpact.Send(UNASSIGNED_SYSTEM_ADDRESS); entry.context->ExecuteUpdates(); } diff --git a/dGame/dGameMessages/CMakeLists.txt b/dGame/dGameMessages/CMakeLists.txt index 23a4c08e0..7c189ec03 100644 --- a/dGame/dGameMessages/CMakeLists.txt +++ b/dGame/dGameMessages/CMakeLists.txt @@ -10,6 +10,7 @@ set(DGAME_DGAMEMESSAGES_SOURCES "PetMessages.cpp" "PropertyMessages.cpp" "RacingMessages.cpp" + "SkillMessages.cpp" "TradeMessages.cpp" "VendorMessages.cpp") diff --git a/dGame/dGameMessages/DoClientProjectileImpact.h b/dGame/dGameMessages/DoClientProjectileImpact.h deleted file mode 100644 index 16d326fad..000000000 --- a/dGame/dGameMessages/DoClientProjectileImpact.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef __DOCLIENTPROJECTILEIMPACT__H__ -#define __DOCLIENTPROJECTILEIMPACT__H__ - -#include "dCommonVars.h" - -/* Tell a client local projectile to impact */ -class DoClientProjectileImpact { -public: - DoClientProjectileImpact() { - i64OrgID = LWOOBJID_EMPTY; - i64OwnerID = LWOOBJID_EMPTY; - i64TargetID = LWOOBJID_EMPTY; - } - - DoClientProjectileImpact(std::string _sBitStream, LWOOBJID _i64OrgID = LWOOBJID_EMPTY, LWOOBJID _i64OwnerID = LWOOBJID_EMPTY, LWOOBJID _i64TargetID = LWOOBJID_EMPTY) { - i64OrgID = _i64OrgID; - i64OwnerID = _i64OwnerID; - i64TargetID = _i64TargetID; - sBitStream = _sBitStream; - } - - DoClientProjectileImpact(RakNet::BitStream& stream) : DoClientProjectileImpact() { - Deserialize(stream); - } - - ~DoClientProjectileImpact() { - } - - void Serialize(RakNet::BitStream& stream) { - stream.Write(MessageType::Game::DO_CLIENT_PROJECTILE_IMPACT); - - stream.Write(i64OrgID != LWOOBJID_EMPTY); - if (i64OrgID != LWOOBJID_EMPTY) stream.Write(i64OrgID); - - stream.Write(i64OwnerID != LWOOBJID_EMPTY); - if (i64OwnerID != LWOOBJID_EMPTY) stream.Write(i64OwnerID); - - stream.Write(i64TargetID != LWOOBJID_EMPTY); - if (i64TargetID != LWOOBJID_EMPTY) stream.Write(i64TargetID); - - uint32_t sBitStreamLength = sBitStream.length(); - stream.Write(sBitStreamLength); - for (uint32_t k = 0; k < sBitStreamLength; k++) { - stream.Write(sBitStream[k]); - } - - } - - bool Deserialize(RakNet::BitStream& stream) { - bool i64OrgIDIsDefault{}; - stream.Read(i64OrgIDIsDefault); - if (i64OrgIDIsDefault != 0) stream.Read(i64OrgID); - - bool i64OwnerIDIsDefault{}; - stream.Read(i64OwnerIDIsDefault); - if (i64OwnerIDIsDefault != 0) stream.Read(i64OwnerID); - - bool i64TargetIDIsDefault{}; - stream.Read(i64TargetIDIsDefault); - if (i64TargetIDIsDefault != 0) stream.Read(i64TargetID); - - uint32_t sBitStreamLength{}; - stream.Read(sBitStreamLength); - if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; - for (uint32_t k = 0; k < sBitStreamLength; k++) { - unsigned char character; - stream.Read(character); - sBitStream.push_back(character); - } - - - return true; - } - - LWOOBJID i64OrgID; - LWOOBJID i64OwnerID; - LWOOBJID i64TargetID; - std::string sBitStream; -}; - -#endif //!__DOCLIENTPROJECTILEIMPACT__H__ diff --git a/dGame/dGameMessages/EchoStartSkill.h b/dGame/dGameMessages/EchoStartSkill.h deleted file mode 100644 index f2229f889..000000000 --- a/dGame/dGameMessages/EchoStartSkill.h +++ /dev/null @@ -1,131 +0,0 @@ -#ifndef __ECHOSTARTSKILL__H__ -#define __ECHOSTARTSKILL__H__ - -#include "dCommonVars.h" -#include "NiPoint3.h" -#include "NiQuaternion.h" -#include "MessageType/Game.h" - -/* Same as start skill but with different network options. An echo down to other clients that need to play the skill. */ -class EchoStartSkill { -public: - EchoStartSkill() { - bUsedMouse = false; - fCasterLatency = 0.0f; - iCastType = 0; - lastClickedPosit = NiPoint3Constant::ZERO; - optionalTargetID = LWOOBJID_EMPTY; - originatorRot = QuatUtils::IDENTITY; - uiSkillHandle = 0; - } - - EchoStartSkill(LWOOBJID _optionalOriginatorID, std::string _sBitStream, TSkillID _skillID, bool _bUsedMouse = false, float _fCasterLatency = 0.0f, int32_t _iCastType = 0, NiPoint3 _lastClickedPosit = NiPoint3Constant::ZERO, LWOOBJID _optionalTargetID = LWOOBJID_EMPTY, NiQuaternion _originatorRot = QuatUtils::IDENTITY, uint32_t _uiSkillHandle = 0) { - bUsedMouse = _bUsedMouse; - fCasterLatency = _fCasterLatency; - iCastType = _iCastType; - lastClickedPosit = _lastClickedPosit; - optionalOriginatorID = _optionalOriginatorID; - optionalTargetID = _optionalTargetID; - originatorRot = _originatorRot; - sBitStream = _sBitStream; - skillID = _skillID; - uiSkillHandle = _uiSkillHandle; - } - - EchoStartSkill(RakNet::BitStream& stream) : EchoStartSkill() { - Deserialize(stream); - } - - ~EchoStartSkill() { - } - - void Serialize(RakNet::BitStream& stream) { - stream.Write(MessageType::Game::ECHO_START_SKILL); - - stream.Write(bUsedMouse); - - stream.Write(fCasterLatency != 0.0f); - if (fCasterLatency != 0.0f) stream.Write(fCasterLatency); - - stream.Write(iCastType != 0); - if (iCastType != 0) stream.Write(iCastType); - - stream.Write(lastClickedPosit != NiPoint3Constant::ZERO); - if (lastClickedPosit != NiPoint3Constant::ZERO) stream.Write(lastClickedPosit); - - stream.Write(optionalOriginatorID); - - stream.Write(optionalTargetID != LWOOBJID_EMPTY); - if (optionalTargetID != LWOOBJID_EMPTY) stream.Write(optionalTargetID); - - stream.Write(originatorRot != QuatUtils::IDENTITY); - if (originatorRot != QuatUtils::IDENTITY) stream.Write(originatorRot); - - uint32_t sBitStreamLength = sBitStream.length(); - stream.Write(sBitStreamLength); - for (uint32_t k = 0; k < sBitStreamLength; k++) { - stream.Write(sBitStream[k]); - } - - stream.Write(skillID); - - stream.Write(uiSkillHandle != 0); - if (uiSkillHandle != 0) stream.Write(uiSkillHandle); - } - - bool Deserialize(RakNet::BitStream& stream) { - stream.Read(bUsedMouse); - - bool fCasterLatencyIsDefault{}; - stream.Read(fCasterLatencyIsDefault); - if (fCasterLatencyIsDefault != 0) stream.Read(fCasterLatency); - - bool iCastTypeIsDefault{}; - stream.Read(iCastTypeIsDefault); - if (iCastTypeIsDefault != 0) stream.Read(iCastType); - - bool lastClickedPositIsDefault{}; - stream.Read(lastClickedPositIsDefault); - if (lastClickedPositIsDefault != 0) stream.Read(lastClickedPosit); - - stream.Read(optionalOriginatorID); - - bool optionalTargetIDIsDefault{}; - stream.Read(optionalTargetIDIsDefault); - if (optionalTargetIDIsDefault != 0) stream.Read(optionalTargetID); - - bool originatorRotIsDefault{}; - stream.Read(originatorRotIsDefault); - if (originatorRotIsDefault != 0) stream.Read(originatorRot); - - uint32_t sBitStreamLength{}; - stream.Read(sBitStreamLength); - if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; - for (uint32_t k = 0; k < sBitStreamLength; k++) { - unsigned char character; - stream.Read(character); - sBitStream.push_back(character); - } - - stream.Read(skillID); - - bool uiSkillHandleIsDefault{}; - stream.Read(uiSkillHandleIsDefault); - if (uiSkillHandleIsDefault != 0) stream.Read(uiSkillHandle); - - return true; - } - - bool bUsedMouse; - float fCasterLatency; - int32_t iCastType; - NiPoint3 lastClickedPosit; - LWOOBJID optionalOriginatorID; - LWOOBJID optionalTargetID; - NiQuaternion originatorRot = QuatUtils::IDENTITY; - std::string sBitStream; - TSkillID skillID; - uint32_t uiSkillHandle; -}; - -#endif //!__ECHOSTARTSKILL__H__ diff --git a/dGame/dGameMessages/EchoSyncSkill.h b/dGame/dGameMessages/EchoSyncSkill.h deleted file mode 100644 index 63c23b3e0..000000000 --- a/dGame/dGameMessages/EchoSyncSkill.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef __ECHOSYNCSKILL__H__ -#define __ECHOSYNCSKILL__H__ - -#include - -#include "BitStream.h" -#include "MessageType/Game.h" - - -/* Message to synchronize a skill cast */ -class EchoSyncSkill { -public: - EchoSyncSkill() { - bDone = false; - } - - EchoSyncSkill(std::string _sBitStream, uint32_t _uiBehaviorHandle, uint32_t _uiSkillHandle, bool _bDone = false) { - bDone = _bDone; - sBitStream = _sBitStream; - uiBehaviorHandle = _uiBehaviorHandle; - uiSkillHandle = _uiSkillHandle; - } - - EchoSyncSkill(RakNet::BitStream& stream) : EchoSyncSkill() { - Deserialize(stream); - } - - ~EchoSyncSkill() { - } - - void Serialize(RakNet::BitStream& stream) { - stream.Write(MessageType::Game::ECHO_SYNC_SKILL); - - stream.Write(bDone); - uint32_t sBitStreamLength = sBitStream.length(); - stream.Write(sBitStreamLength); - for (uint32_t k = 0; k < sBitStreamLength; k++) { - stream.Write(sBitStream[k]); - } - - stream.Write(uiBehaviorHandle); - stream.Write(uiSkillHandle); - } - - bool Deserialize(RakNet::BitStream& stream) { - stream.Read(bDone); - - uint32_t sBitStreamLength{}; - stream.Read(sBitStreamLength); - if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; - for (unsigned int k = 0; k < sBitStreamLength; k++) { - unsigned char character; - stream.Read(character); - sBitStream.push_back(character); - } - - stream.Read(uiBehaviorHandle); - stream.Read(uiSkillHandle); - - return true; - } - - bool bDone{}; - std::string sBitStream{}; - uint32_t uiBehaviorHandle{}; - uint32_t uiSkillHandle{}; -}; - -#endif //!__ECHOSYNCSKILL__H__ diff --git a/dGame/dGameMessages/GameMessageDecoder.cpp b/dGame/dGameMessages/GameMessageDecoder.cpp index 95fbc1437..6681c0d53 100644 --- a/dGame/dGameMessages/GameMessageDecoder.cpp +++ b/dGame/dGameMessages/GameMessageDecoder.cpp @@ -6,12 +6,7 @@ #include #include "GameMessages.h" -#include "StartSkill.h" -#include "SyncSkill.h" -#include "EchoStartSkill.h" -#include "EchoSyncSkill.h" -#include "RequestServerProjectileImpact.h" -#include "DoClientProjectileImpact.h" +#include "SkillMessages.h" #include "master/MessageCapture.h" namespace { diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 77bf73da1..9523f353f 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -26,15 +26,11 @@ #include "CDSkillBehaviorTable.h" #include "SkillComponent.h" #include "RacingControlComponent.h" -#include "RequestServerProjectileImpact.h" -#include "SyncSkill.h" -#include "StartSkill.h" -#include "EchoStartSkill.h" -#include "EchoSyncSkill.h" #include "ActivityMessages.h" #include "BuildingMessages.h" #include "RacingMessages.h" #include "MissionMessages.h" +#include "SkillMessages.h" #include "TradeMessages.h" #include "VendorMessages.h" #include "EffectsMessages.h" @@ -157,6 +153,12 @@ namespace { { CLIENT_TRADE_CANCEL, []() { return std::make_unique(); } }, { CLIENT_TRADE_ACCEPT, []() { return std::make_unique(); } }, { CLIENT_TRADE_UPDATE, []() { return std::make_unique(); } }, + + // Skills + { SELECT_SKILL, []() { return std::make_unique(); } }, + { START_SKILL, []() { return std::make_unique(); } }, + { SYNC_SKILL, []() { return std::make_unique(); } }, + { REQUEST_SERVER_PROJECTILE_IMPACT, []() { return std::make_unique(); } }, }; }; @@ -207,18 +209,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System switch (messageID) { - // Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client. - case MessageType::Game::SELECT_SKILL: { - auto var = entity->GetVar(u"dlu_first_time_load"); - if (var) { - entity->SetVar(u"dlu_first_time_load", false); - InventoryComponent* inventoryComponent = entity->GetComponent(); - - if (inventoryComponent) inventoryComponent->FixInvisibleItems(); - } - break; - } - case MessageType::Game::PLAYER_LOADED: { GameMessages::SendPlayerReady(entity, sysAddr); entity->SetPlayerReadyForUpdates(); @@ -371,113 +361,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System GameMessages::SendResurrect(entity); break; } - case MessageType::Game::REQUEST_SERVER_PROJECTILE_IMPACT: - { - auto message = RequestServerProjectileImpact(); - - message.Deserialize(inStream); - - auto* skill_component = entity->GetComponent(); - - if (skill_component != nullptr) { - auto bs = RakNet::BitStream(reinterpret_cast(&message.sBitStream[0]), message.sBitStream.size(), false); - - skill_component->SyncPlayerProjectile(message.i64LocalID, bs, message.i64TargetID); - } - - break; - } - - case MessageType::Game::START_SKILL: { - StartSkill startSkill = StartSkill(); - startSkill.Deserialize(inStream); // inStream replaces &bitStream - - if (startSkill.skillID == 1561 || startSkill.skillID == 1562 || startSkill.skillID == 1541) return; - - MissionComponent* comp = entity->GetComponent(); - if (comp) { - comp->Progress(eMissionTaskType::USE_SKILL, startSkill.skillID); - } - - CDSkillBehaviorTable* skillTable = CDClientManager::GetTable(); - unsigned int behaviorId = skillTable->GetSkillByID(startSkill.skillID).behaviorID; - - bool success = false; - - if (behaviorId > 0) { - auto bs = RakNet::BitStream(reinterpret_cast(&startSkill.sBitStream[0]), startSkill.sBitStream.size(), false); - - auto* const skillComponent = entity->GetComponent(); - - if (skillComponent) success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID); - - if (success && entity->GetCharacter()) { - DestroyableComponent* destComp = entity->GetComponent(); - destComp->SetImagination(destComp->GetImagination() - skillTable->GetSkillByID(startSkill.skillID).imaginationcost); - } - } - - if (Game::server->GetZoneID() == 1302) { - break; - } - - if (success) { - //Broadcast our startSkill: - RakNet::BitStream bitStreamLocal; - BitStreamUtils::WriteHeader(bitStreamLocal, ServiceType::CLIENT, MessageType::Client::GAME_MSG); - bitStreamLocal.Write(entity->GetObjectID()); - - EchoStartSkill echoStartSkill; - echoStartSkill.bUsedMouse = startSkill.bUsedMouse; - echoStartSkill.fCasterLatency = startSkill.fCasterLatency; - echoStartSkill.iCastType = startSkill.iCastType; - echoStartSkill.lastClickedPosit = startSkill.lastClickedPosit; - echoStartSkill.optionalOriginatorID = startSkill.optionalOriginatorID; - echoStartSkill.optionalTargetID = startSkill.optionalTargetID; - echoStartSkill.originatorRot = startSkill.originatorRot; - echoStartSkill.sBitStream = startSkill.sBitStream; - echoStartSkill.skillID = startSkill.skillID; - echoStartSkill.uiSkillHandle = startSkill.uiSkillHandle; - echoStartSkill.Serialize(bitStreamLocal); - - Game::server->Send(bitStreamLocal, entity->GetSystemAddress(), true); - } - } break; - - case MessageType::Game::SYNC_SKILL: { - RakNet::BitStream bitStreamLocal; - BitStreamUtils::WriteHeader(bitStreamLocal, ServiceType::CLIENT, MessageType::Client::GAME_MSG); - bitStreamLocal.Write(entity->GetObjectID()); - - SyncSkill sync = SyncSkill(inStream); // inStream replaced &bitStream - - std::ostringstream buffer; - - for (unsigned int k = 0; k < sync.sBitStream.size(); k++) { - char s; - s = sync.sBitStream.at(k); - buffer << std::setw(2) << std::hex << std::setfill('0') << static_cast(s) << " "; - } - - if (usr != nullptr) { - auto bs = RakNet::BitStream(reinterpret_cast(&sync.sBitStream[0]), sync.sBitStream.size(), false); - - auto* const skillComponent = entity->GetComponent(); - - if (skillComponent) skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, bs); - } - - EchoSyncSkill echo = EchoSyncSkill(); - echo.bDone = sync.bDone; - echo.sBitStream = sync.sBitStream; - echo.uiBehaviorHandle = sync.uiBehaviorHandle; - echo.uiSkillHandle = sync.uiSkillHandle; - - echo.Serialize(bitStreamLocal); - - Game::server->Send(bitStreamLocal, sysAddr, true); - } break; - case MessageType::Game::REQUEST_SMASH_PLAYER: entity->Smash(entity->GetObjectID()); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 9d06d0c1f..7b7a83363 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -658,58 +658,6 @@ void GameMessages::SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPo SEND_PACKET; } -void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, BehaviorSlot slotID) { - int AICombatWeight = 0; - bool bFromSkillSet = false; - int castType = 0; - float fTimeSecs = -1.0f; - int iTimesCanCast = -1; - bool temporary = true; - - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::ADD_SKILL); - - bitStream.Write(AICombatWeight != 0); - if (AICombatWeight != 0) bitStream.Write(AICombatWeight); - - bitStream.Write(bFromSkillSet); - - bitStream.Write(castType != 0); - if (castType != 0) bitStream.Write(castType); - - bitStream.Write(fTimeSecs != -1.0f); - if (fTimeSecs != -1.0f) bitStream.Write(fTimeSecs); - - bitStream.Write(iTimesCanCast != -1); - if (iTimesCanCast != -1) bitStream.Write(iTimesCanCast); - - bitStream.Write(skillID); - - bitStream.Write(slotID != BehaviorSlot::Invalid); - if (slotID != BehaviorSlot::Invalid) bitStream.Write(slotID); - - bitStream.Write(temporary); - - SystemAddress sysAddr = entity->GetSystemAddress(); - SEND_PACKET; -} - -void GameMessages::SendRemoveSkill(Entity* entity, TSkillID skillID) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::REMOVE_SKILL); - bitStream.Write(false); - bitStream.Write(skillID); - - SystemAddress sysAddr = entity->GetSystemAddress(); - SEND_PACKET; -} - void GameMessages::SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response) { CBITSTREAM; CMSGHEADER; @@ -2196,6 +2144,12 @@ namespace GameMessages { SEND_PACKET; } + void NetGameMsg::BroadcastExcept(const SystemAddress& excluded) const { + CBITSTREAM; + WritePacket(bitStream); + Game::server->Send(bitStream, excluded, true); + } + void NetGameMsg::Send(const SystemAddress& sysAddr) const { CBITSTREAM; WritePacket(bitStream); diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 8d0653c5d..1478cac01 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -87,6 +87,10 @@ namespace GameMessages { // nothing is delivered (RakNet rejects a non-broadcast send without an address). void SendToClient(const SystemAddress& sysAddr) const; + // Sends the message to every client except excluded (a RakNet broadcast with that address left out). + // Used to echo a client's message to everyone else. + void BroadcastExcept(const SystemAddress& excluded) const; + // Writes the complete client packet (CLIENT/GAME_MSG header, target, msgId, then Serialize()) into bitStream. // This is exactly what Send(sysAddr) puts on the wire; tests use it to compare bytes without a server. void WritePacket(RakNet::BitStream& bitStream) const; @@ -169,9 +173,6 @@ namespace GameMessages { void SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme); void SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPoint3& position, const NiQuaternion& rotation); - void SendAddSkill(Entity* entity, TSkillID skillID, BehaviorSlot slotID); - void SendRemoveSkill(Entity* entity, TSkillID skillID); - void SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response); void SendMatchUpdate(Entity* entity, const SystemAddress& sysAddr, std::string data, eMatchUpdate type); diff --git a/dGame/dGameMessages/RequestServerProjectileImpact.h b/dGame/dGameMessages/RequestServerProjectileImpact.h deleted file mode 100644 index 181583995..000000000 --- a/dGame/dGameMessages/RequestServerProjectileImpact.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef __REQUESTSERVERPROJECTILEIMPACT__H__ -#define __REQUESTSERVERPROJECTILEIMPACT__H__ - -#include "dCommonVars.h" -#include "MessageType/Game.h" - -/* Notifying the server that a locally owned projectile impacted. Sent to the caster of the projectile - should always be the local char. */ -class RequestServerProjectileImpact { -public: - RequestServerProjectileImpact() { - i64LocalID = LWOOBJID_EMPTY; - i64TargetID = LWOOBJID_EMPTY; - } - - RequestServerProjectileImpact(std::string _sBitStream, LWOOBJID _i64LocalID = LWOOBJID_EMPTY, LWOOBJID _i64TargetID = LWOOBJID_EMPTY) { - i64LocalID = _i64LocalID; - i64TargetID = _i64TargetID; - sBitStream = _sBitStream; - } - - RequestServerProjectileImpact(RakNet::BitStream& stream) : RequestServerProjectileImpact() { - Deserialize(stream); - } - - ~RequestServerProjectileImpact() { - } - - void Serialize(RakNet::BitStream& stream) { - stream.Write(MessageType::Game::REQUEST_SERVER_PROJECTILE_IMPACT); - - stream.Write(i64LocalID != LWOOBJID_EMPTY); - if (i64LocalID != LWOOBJID_EMPTY) stream.Write(i64LocalID); - - stream.Write(i64TargetID != LWOOBJID_EMPTY); - if (i64TargetID != LWOOBJID_EMPTY) stream.Write(i64TargetID); - - uint32_t sBitStreamLength = sBitStream.length(); - stream.Write(sBitStreamLength); - for (uint32_t k = 0; k < sBitStreamLength; k++) { - stream.Write(sBitStream[k]); - } - - } - - bool Deserialize(RakNet::BitStream& stream) { - bool i64LocalIDIsDefault{}; - stream.Read(i64LocalIDIsDefault); - if (i64LocalIDIsDefault != 0) stream.Read(i64LocalID); - - bool i64TargetIDIsDefault{}; - stream.Read(i64TargetIDIsDefault); - if (i64TargetIDIsDefault != 0) stream.Read(i64TargetID); - - uint32_t sBitStreamLength{}; - stream.Read(sBitStreamLength); - if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; - for (uint32_t k = 0; k < sBitStreamLength; k++) { - unsigned char character; - stream.Read(character); - sBitStream.push_back(character); - } - - - return true; - } - - LWOOBJID i64LocalID; - LWOOBJID i64TargetID; - std::string sBitStream; -}; - -#endif //!__REQUESTSERVERPROJECTILEIMPACT__H__ diff --git a/dGame/dGameMessages/SkillMessages.cpp b/dGame/dGameMessages/SkillMessages.cpp new file mode 100644 index 000000000..b1929c358 --- /dev/null +++ b/dGame/dGameMessages/SkillMessages.cpp @@ -0,0 +1,265 @@ +#include "SkillMessages.h" + +#include "BehaviorSlot.h" +#include "BitStreamUtils.h" +#include "CDClientManager.h" +#include "CDSkillBehaviorTable.h" +#include "DestroyableComponent.h" +#include "dServer.h" +#include "eMissionTaskType.h" +#include "Entity.h" +#include "Game.h" +#include "InventoryComponent.h" +#include "MissionComponent.h" +#include "SkillComponent.h" + +namespace GameMessages { + void AddSkill::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, AICombatWeight, 0); + bitStream.Write(bFromSkillSet); + BitStreamUtils::WriteOptional(bitStream, castType, 0); + BitStreamUtils::WriteOptional(bitStream, fTimeSecs, -1.0f); + BitStreamUtils::WriteOptional(bitStream, iTimesCanCast, -1); + bitStream.Write(skillID); + BitStreamUtils::WriteOptional(bitStream, slotID, BehaviorSlot::Invalid); + bitStream.Write(temporary); + } + + bool AddSkill::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, AICombatWeight, 0)); + VALIDATE_READ(bitStream.Read(bFromSkillSet)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, castType, 0)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, fTimeSecs, -1.0f)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iTimesCanCast, -1)); + VALIDATE_READ(bitStream.Read(skillID)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, slotID, BehaviorSlot::Invalid)); + VALIDATE_READ(bitStream.Read(temporary)); + return true; + } + + void RemoveSkill::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bFromSkillSet); + bitStream.Write(skillID); + } + + bool RemoveSkill::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bFromSkillSet)); + VALIDATE_READ(bitStream.Read(skillID)); + return true; + } + + void SelectSkill::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bFromSkillSet); + bitStream.Write(skillID); + } + + bool SelectSkill::Deserialize(RakNet::BitStream& bitStream) { + // The old handler read nothing; keep accepting the message whatever its payload. + if (!bitStream.Read(bFromSkillSet) || !bitStream.Read(skillID)) { + bFromSkillSet = false; + skillID = 0; + } + return true; + } + + // Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client. + void SelectSkill::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto var = entity.GetVar(u"dlu_first_time_load"); + if (var) { + entity.SetVar(u"dlu_first_time_load", false); + InventoryComponent* inventoryComponent = entity.GetComponent(); + + if (inventoryComponent) inventoryComponent->FixInvisibleItems(); + } + } + + void StartSkill::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bUsedMouse); + BitStreamUtils::WriteOptional(bitStream, consumableItemID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, fCasterLatency, 0.0f); + BitStreamUtils::WriteOptional(bitStream, iCastType, 0); + BitStreamUtils::WriteOptional(bitStream, lastClickedPosit, NiPoint3Constant::ZERO); + bitStream.Write(optionalOriginatorID); + BitStreamUtils::WriteOptional(bitStream, optionalTargetID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, originatorRot, QuatUtils::IDENTITY); + BitStreamUtils::WriteLengthPrefixed(bitStream, sBitStream); + bitStream.Write(skillID); + BitStreamUtils::WriteOptional(bitStream, uiSkillHandle, 0u); + } + + bool StartSkill::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bUsedMouse)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, consumableItemID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, fCasterLatency, 0.0f)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iCastType, 0)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, lastClickedPosit, NiPoint3Constant::ZERO)); + VALIDATE_READ(bitStream.Read(optionalOriginatorID)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, optionalTargetID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, originatorRot, QuatUtils::IDENTITY)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, sBitStream)); + VALIDATE_READ(bitStream.Read(skillID)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, uiSkillHandle, 0u)); + return true; + } + + void StartSkill::Handle(Entity& entityRef, const SystemAddress& sysAddr) { + Entity* entity = &entityRef; + if (skillID == 1561 || skillID == 1562 || skillID == 1541) return; + + MissionComponent* comp = entity->GetComponent(); + if (comp) { + comp->Progress(eMissionTaskType::USE_SKILL, skillID); + } + + CDSkillBehaviorTable* skillTable = CDClientManager::GetTable(); + unsigned int behaviorId = skillTable->GetSkillByID(skillID).behaviorID; + + bool success = false; + + if (behaviorId > 0) { + auto bs = RakNet::BitStream(reinterpret_cast(&sBitStream[0]), sBitStream.size(), false); + + auto* const skillComponent = entity->GetComponent(); + + if (skillComponent) success = skillComponent->CastPlayerSkill(behaviorId, uiSkillHandle, bs, optionalTargetID, skillID); + + if (success && entity->GetCharacter()) { + DestroyableComponent* destComp = entity->GetComponent(); + destComp->SetImagination(destComp->GetImagination() - skillTable->GetSkillByID(skillID).imaginationcost); + } + } + + if (Game::server->GetZoneID() == 1302) { + return; + } + + if (success) { + // Echo the cast to every other client + EchoStartSkill echoStartSkill; + echoStartSkill.target = entity->GetObjectID(); + echoStartSkill.bUsedMouse = bUsedMouse; + echoStartSkill.fCasterLatency = fCasterLatency; + echoStartSkill.iCastType = iCastType; + echoStartSkill.lastClickedPosit = lastClickedPosit; + echoStartSkill.optionalOriginatorID = optionalOriginatorID; + echoStartSkill.optionalTargetID = optionalTargetID; + echoStartSkill.originatorRot = originatorRot; + echoStartSkill.sBitStream = sBitStream; + echoStartSkill.skillID = skillID; + echoStartSkill.uiSkillHandle = uiSkillHandle; + echoStartSkill.BroadcastExcept(entity->GetSystemAddress()); + } + } + + void EchoStartSkill::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bUsedMouse); + BitStreamUtils::WriteOptional(bitStream, fCasterLatency, 0.0f); + BitStreamUtils::WriteOptional(bitStream, iCastType, 0); + BitStreamUtils::WriteOptional(bitStream, lastClickedPosit, NiPoint3Constant::ZERO); + bitStream.Write(optionalOriginatorID); + BitStreamUtils::WriteOptional(bitStream, optionalTargetID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, originatorRot, QuatUtils::IDENTITY); + BitStreamUtils::WriteLengthPrefixed(bitStream, sBitStream); + bitStream.Write(skillID); + BitStreamUtils::WriteOptional(bitStream, uiSkillHandle, 0u); + } + + bool EchoStartSkill::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bUsedMouse)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, fCasterLatency, 0.0f)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iCastType, 0)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, lastClickedPosit, NiPoint3Constant::ZERO)); + VALIDATE_READ(bitStream.Read(optionalOriginatorID)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, optionalTargetID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, originatorRot, QuatUtils::IDENTITY)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, sBitStream)); + VALIDATE_READ(bitStream.Read(skillID)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, uiSkillHandle, 0u)); + return true; + } + + void SyncSkill::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bDone); + BitStreamUtils::WriteLengthPrefixed(bitStream, sBitStream); + bitStream.Write(uiBehaviorHandle); + bitStream.Write(uiSkillHandle); + } + + bool SyncSkill::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bDone)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, sBitStream)); + VALIDATE_READ(bitStream.Read(uiBehaviorHandle)); + VALIDATE_READ(bitStream.Read(uiSkillHandle)); + return true; + } + + void SyncSkill::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto bs = RakNet::BitStream(reinterpret_cast(&sBitStream[0]), sBitStream.size(), false); + + auto* const skillComponent = entity.GetComponent(); + + if (skillComponent) skillComponent->SyncPlayerSkill(uiSkillHandle, uiBehaviorHandle, bs); + + // Echo the sync to every other client + EchoSyncSkill echo; + echo.target = entity.GetObjectID(); + echo.bDone = bDone; + echo.sBitStream = sBitStream; + echo.uiBehaviorHandle = uiBehaviorHandle; + echo.uiSkillHandle = uiSkillHandle; + echo.BroadcastExcept(sysAddr); + } + + void EchoSyncSkill::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bDone); + BitStreamUtils::WriteLengthPrefixed(bitStream, sBitStream); + bitStream.Write(uiBehaviorHandle); + bitStream.Write(uiSkillHandle); + } + + bool EchoSyncSkill::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bDone)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, sBitStream)); + VALIDATE_READ(bitStream.Read(uiBehaviorHandle)); + VALIDATE_READ(bitStream.Read(uiSkillHandle)); + return true; + } + + void RequestServerProjectileImpact::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, i64LocalID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, i64TargetID, LWOOBJID_EMPTY); + BitStreamUtils::WriteLengthPrefixed(bitStream, sBitStream); + } + + bool RequestServerProjectileImpact::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64LocalID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64TargetID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, sBitStream)); + return true; + } + + void RequestServerProjectileImpact::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* skill_component = entity.GetComponent(); + + if (skill_component != nullptr) { + auto bs = RakNet::BitStream(reinterpret_cast(&sBitStream[0]), sBitStream.size(), false); + + skill_component->SyncPlayerProjectile(i64LocalID, bs, i64TargetID); + } + } + + void DoClientProjectileImpact::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, i64OrgID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, i64OwnerID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, i64TargetID, LWOOBJID_EMPTY); + BitStreamUtils::WriteLengthPrefixed(bitStream, sBitStream); + } + + bool DoClientProjectileImpact::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64OrgID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64OwnerID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64TargetID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, sBitStream)); + return true; + } +} diff --git a/dGame/dGameMessages/SkillMessages.h b/dGame/dGameMessages/SkillMessages.h new file mode 100644 index 000000000..26d1195b7 --- /dev/null +++ b/dGame/dGameMessages/SkillMessages.h @@ -0,0 +1,142 @@ +#ifndef SKILLMESSAGES_H +#define SKILLMESSAGES_H + +#include "GameMessages.h" +#include "NiPoint3.h" +#include "NiQuaternion.h" + +#include + +enum class BehaviorSlot : int32_t; + +// Game messages for skills: casting (StartSkill / SyncSkill and their echoes), projectile impacts and the +// player's skill bar. Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order. +// sBitStream is a skill's behavior data (its own bit stream), sent as a u32 byte count and the bytes. +namespace GameMessages { + // Server -> client, to the player only. + struct AddSkill : public NetGameMsg { + AddSkill() : NetGameMsg(MessageType::Game::ADD_SKILL) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + int32_t AICombatWeight{ 0 }; // optional + bool bFromSkillSet{ false }; + int32_t castType{ 0 }; // optional + float fTimeSecs{ -1.0f }; // optional + int32_t iTimesCanCast{ -1 }; // optional + TSkillID skillID{}; + BehaviorSlot slotID{ static_cast(-1) }; // optional, BehaviorSlot::Invalid when unset + bool temporary{ true }; + }; + + // Server -> client, to the player only. + struct RemoveSkill : public NetGameMsg { + RemoveSkill() : NetGameMsg(MessageType::Game::REMOVE_SKILL) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bFromSkillSet{ false }; + TSkillID skillID{}; + }; + + // Client -> server. The player picked a skill; DLU only uses it to fix invisible items on first load. + struct SelectSkill : public NetGameMsg { + SelectSkill() : NetGameMsg(MessageType::Game::SELECT_SKILL) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + // Not read by DLU: the old handler ignored the payload, so a message without it is still accepted. + bool bFromSkillSet{ false }; + int32_t skillID{}; + }; + + // Client -> server. The player cast a skill. + struct StartSkill : public NetGameMsg { + StartSkill() : NetGameMsg(MessageType::Game::START_SKILL) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + bool bUsedMouse{ false }; + LWOOBJID consumableItemID{ LWOOBJID_EMPTY }; // optional + float fCasterLatency{ 0.0f }; // optional + int32_t iCastType{ 0 }; // optional + NiPoint3 lastClickedPosit{ NiPoint3Constant::ZERO }; // optional + LWOOBJID optionalOriginatorID{}; + LWOOBJID optionalTargetID{ LWOOBJID_EMPTY }; // optional + NiQuaternion originatorRot{ QuatUtils::IDENTITY }; // optional + std::string sBitStream{}; + TSkillID skillID{ 0 }; + uint32_t uiSkillHandle{ 0 }; // optional + }; + + // Server -> clients. A skill cast echoed to the other clients so they play it too. + struct EchoStartSkill : public NetGameMsg { + EchoStartSkill() : NetGameMsg(MessageType::Game::ECHO_START_SKILL) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bUsedMouse{ false }; + float fCasterLatency{ 0.0f }; // optional + int32_t iCastType{ 0 }; // optional + NiPoint3 lastClickedPosit{ NiPoint3Constant::ZERO }; // optional + LWOOBJID optionalOriginatorID{}; + LWOOBJID optionalTargetID{ LWOOBJID_EMPTY }; // optional + NiQuaternion originatorRot{ QuatUtils::IDENTITY }; // optional + std::string sBitStream{}; + TSkillID skillID{ 0 }; + uint32_t uiSkillHandle{ 0 }; // optional + }; + + // Client -> server. More behavior data for a skill that is being cast. + struct SyncSkill : public NetGameMsg { + SyncSkill() : NetGameMsg(MessageType::Game::SYNC_SKILL) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + bool bDone{}; + std::string sBitStream{}; + uint32_t uiBehaviorHandle{}; + uint32_t uiSkillHandle{}; + }; + + // Server -> clients. + struct EchoSyncSkill : public NetGameMsg { + EchoSyncSkill() : NetGameMsg(MessageType::Game::ECHO_SYNC_SKILL) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bDone{}; + std::string sBitStream{}; + uint32_t uiBehaviorHandle{}; + uint32_t uiSkillHandle{}; + }; + + // Client -> server. A player's projectile hit something. + struct RequestServerProjectileImpact : public NetGameMsg { + RequestServerProjectileImpact() : NetGameMsg(MessageType::Game::REQUEST_SERVER_PROJECTILE_IMPACT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + LWOOBJID i64LocalID{ LWOOBJID_EMPTY }; // optional + LWOOBJID i64TargetID{ LWOOBJID_EMPTY }; // optional + std::string sBitStream{}; + }; + + // Server -> clients. A server side projectile hit something. + struct DoClientProjectileImpact : public NetGameMsg { + DoClientProjectileImpact() : NetGameMsg(MessageType::Game::DO_CLIENT_PROJECTILE_IMPACT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID i64OrgID{ LWOOBJID_EMPTY }; // optional + LWOOBJID i64OwnerID{ LWOOBJID_EMPTY }; // optional + LWOOBJID i64TargetID{ LWOOBJID_EMPTY }; // optional + std::string sBitStream{}; + }; +}; + +#endif // SKILLMESSAGES_H diff --git a/dGame/dGameMessages/StartSkill.h b/dGame/dGameMessages/StartSkill.h deleted file mode 100644 index 6ca510086..000000000 --- a/dGame/dGameMessages/StartSkill.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef __STARTSKILL__H__ -#define __STARTSKILL__H__ - -#include "dCommonVars.h" -#include "NiPoint3.h" -#include "NiQuaternion.h" -#include "MessageType/Game.h" - -/** - * Same as sync skill but with different network options. An echo down to other clients that need to play the skill. - */ -class StartSkill { -public: - StartSkill() { - bUsedMouse = false; - consumableItemID = LWOOBJID_EMPTY; - fCasterLatency = 0.0f; - iCastType = 0; - lastClickedPosit = NiPoint3Constant::ZERO; - optionalTargetID = LWOOBJID_EMPTY; - originatorRot = QuatUtils::IDENTITY; - uiSkillHandle = 0; - } - - StartSkill(LWOOBJID _optionalOriginatorID, std::string _sBitStream, TSkillID _skillID, bool _bUsedMouse = false, LWOOBJID _consumableItemID = LWOOBJID_EMPTY, float _fCasterLatency = 0.0f, int32_t _iCastType = 0, NiPoint3 _lastClickedPosit = NiPoint3Constant::ZERO, LWOOBJID _optionalTargetID = LWOOBJID_EMPTY, NiQuaternion _originatorRot = QuatUtils::IDENTITY, uint32_t _uiSkillHandle = 0) { - bUsedMouse = _bUsedMouse; - consumableItemID = _consumableItemID; - fCasterLatency = _fCasterLatency; - iCastType = _iCastType; - lastClickedPosit = _lastClickedPosit; - optionalOriginatorID = _optionalOriginatorID; - optionalTargetID = _optionalTargetID; - originatorRot = _originatorRot; - sBitStream = _sBitStream; - skillID = _skillID; - uiSkillHandle = _uiSkillHandle; - } - - StartSkill(RakNet::BitStream& stream) : StartSkill() { - Deserialize(stream); - } - - ~StartSkill() { - } - - void Serialize(RakNet::BitStream& stream) { - stream.Write(MessageType::Game::START_SKILL); - - stream.Write(bUsedMouse); - - stream.Write(consumableItemID != LWOOBJID_EMPTY); - if (consumableItemID != LWOOBJID_EMPTY) stream.Write(consumableItemID); - - stream.Write(fCasterLatency != 0.0f); - if (fCasterLatency != 0.0f) stream.Write(fCasterLatency); - - stream.Write(iCastType != 0); - if (iCastType != 0) stream.Write(iCastType); - - stream.Write(lastClickedPosit != NiPoint3Constant::ZERO); - if (lastClickedPosit != NiPoint3Constant::ZERO) stream.Write(lastClickedPosit); - - stream.Write(optionalOriginatorID); - - stream.Write(optionalTargetID != LWOOBJID_EMPTY); - if (optionalTargetID != LWOOBJID_EMPTY) stream.Write(optionalTargetID); - - stream.Write(originatorRot != QuatUtils::IDENTITY); - if (originatorRot != QuatUtils::IDENTITY) stream.Write(originatorRot); - - uint32_t sBitStreamLength = sBitStream.length(); - stream.Write(sBitStreamLength); - for (uint32_t k = 0; k < sBitStreamLength; k++) { - stream.Write(sBitStream[k]); - } - - stream.Write(skillID); - - stream.Write(uiSkillHandle != 0); - if (uiSkillHandle != 0) stream.Write(uiSkillHandle); - } - - bool Deserialize(RakNet::BitStream& stream) { - stream.Read(bUsedMouse); - - bool consumableItemIDIsDefault{}; - stream.Read(consumableItemIDIsDefault); - if (consumableItemIDIsDefault != 0) stream.Read(consumableItemID); - - bool fCasterLatencyIsDefault{}; - stream.Read(fCasterLatencyIsDefault); - if (fCasterLatencyIsDefault != 0) stream.Read(fCasterLatency); - - bool iCastTypeIsDefault{}; - stream.Read(iCastTypeIsDefault); - if (iCastTypeIsDefault != 0) stream.Read(iCastType); - - bool lastClickedPositIsDefault{}; - stream.Read(lastClickedPositIsDefault); - if (lastClickedPositIsDefault != 0) stream.Read(lastClickedPosit); - - stream.Read(optionalOriginatorID); - - bool optionalTargetIDIsDefault{}; - stream.Read(optionalTargetIDIsDefault); - if (optionalTargetIDIsDefault != 0) stream.Read(optionalTargetID); - - bool originatorRotIsDefault{}; - stream.Read(originatorRotIsDefault); - if (originatorRotIsDefault != 0) stream.Read(originatorRot); - - uint32_t sBitStreamLength{}; - stream.Read(sBitStreamLength); - if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; - for (uint32_t k = 0; k < sBitStreamLength; k++) { - unsigned char character; - stream.Read(character); - sBitStream.push_back(character); - } - - stream.Read(skillID); - - bool uiSkillHandleIsDefault{}; - stream.Read(uiSkillHandleIsDefault); - if (uiSkillHandleIsDefault != 0) stream.Read(uiSkillHandle); - - return true; - } - - bool bUsedMouse = false; - LWOOBJID consumableItemID{}; - float fCasterLatency{}; - int32_t iCastType{}; - NiPoint3 lastClickedPosit{}; - LWOOBJID optionalOriginatorID{}; - LWOOBJID optionalTargetID{}; - NiQuaternion originatorRot = QuatUtils::IDENTITY; - std::string sBitStream = ""; - TSkillID skillID = 0; - uint32_t uiSkillHandle = 0; -}; - -#endif //!__STARTSKILL__H__ diff --git a/dGame/dGameMessages/SyncSkill.h b/dGame/dGameMessages/SyncSkill.h deleted file mode 100644 index 3128ce91f..000000000 --- a/dGame/dGameMessages/SyncSkill.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef __SYNCSKILL__H__ -#define __SYNCSKILL__H__ - -#include -#include - -#include "BitStream.h" -#include "MessageType/Game.h" - -/* Message to synchronize a skill cast */ -class SyncSkill { -public: - SyncSkill() { - bDone = false; - } - - SyncSkill(std::string _sBitStream, uint32_t _uiBehaviorHandle, uint32_t _uiSkillHandle, bool _bDone = false) { - bDone = _bDone; - sBitStream = _sBitStream; - uiBehaviorHandle = _uiBehaviorHandle; - uiSkillHandle = _uiSkillHandle; - } - - SyncSkill(RakNet::BitStream& stream) : SyncSkill() { - Deserialize(stream); - } - - ~SyncSkill() { - } - - void Serialize(RakNet::BitStream& stream) { - stream.Write(MessageType::Game::SYNC_SKILL); - - stream.Write(bDone); - uint32_t sBitStreamLength = sBitStream.length(); - stream.Write(sBitStreamLength); - for (unsigned int k = 0; k < sBitStreamLength; k++) { - stream.Write(sBitStream[k]); - } - - stream.Write(uiBehaviorHandle); - stream.Write(uiSkillHandle); - } - - bool Deserialize(RakNet::BitStream& stream) { - stream.Read(bDone); - uint32_t sBitStreamLength{}; - stream.Read(sBitStreamLength); - if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; - for (uint32_t k = 0; k < sBitStreamLength; k++) { - unsigned char character; - stream.Read(character); - sBitStream.push_back(character); - } - - stream.Read(uiBehaviorHandle); - stream.Read(uiSkillHandle); - - return true; - } - - bool bDone{}; - std::string sBitStream{}; - uint32_t uiBehaviorHandle{}; - uint32_t uiSkillHandle{}; -}; - -#endif //!__SYNCSKILL__H__ diff --git a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt index 098fa353c..d9e855536 100644 --- a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt +++ b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt @@ -9,6 +9,7 @@ SET(DGAMEMESSAGES_TESTS "PetMessagesTests.cpp" "PropertyMessagesTests.cpp" "RacingMessagesTests.cpp" + "SkillMessagesTests.cpp" "UnSmashTests.cpp" "VendorMessagesTests.cpp" "LegacyGameMessageTests.cpp" diff --git a/tests/dGameTests/dGameMessagesTests/GameMessageDecoderTests.cpp b/tests/dGameTests/dGameMessagesTests/GameMessageDecoderTests.cpp index 7dc967c64..5529b305d 100644 --- a/tests/dGameTests/dGameMessagesTests/GameMessageDecoderTests.cpp +++ b/tests/dGameTests/dGameMessagesTests/GameMessageDecoderTests.cpp @@ -3,8 +3,7 @@ #include "GameMessageDecoder.h" #include "dCommonVars.h" #include "MessageType/Game.h" -#include "EchoSyncSkill.h" -#include "StartSkill.h" +#include "SkillMessages.h" TEST(GameMessageDecoderTest, DecodesATypedClientMessage) { RakNet::BitStream payload; @@ -26,14 +25,14 @@ TEST(GameMessageDecoderTest, DecodesATypedClientMessage) { } TEST(GameMessageDecoderTest, ReadsWhatTheServerWrites) { - // The server's own Serialize writes the message ID first; the decoder gets what follows it - StartSkill sent(1234, std::string("\x01\x02", 2), 42); + // The decoder gets the payload, what follows the message ID + GameMessages::StartSkill sent; + sent.optionalOriginatorID = 1234; + sent.sBitStream = std::string("\x01\x02", 2); + sent.skillID = 42; sent.optionalTargetID = 77; RakNet::BitStream stream; sent.Serialize(stream); - MessageType::Game id{}; - ASSERT_TRUE(stream.Read(id)); - ASSERT_EQ(id, MessageType::Game::START_SKILL); const auto fields = GameMessageDecoder::Decode(MessageType::Game::START_SKILL, true, stream); ASSERT_TRUE(fields); @@ -44,13 +43,11 @@ TEST(GameMessageDecoderTest, ReadsWhatTheServerWrites) { } TEST(GameMessageDecoderTest, DirectionMatters) { - EchoSyncSkill sent; + GameMessages::EchoSyncSkill sent; sent.bDone = true; sent.uiSkillHandle = 5; RakNet::BitStream stream; sent.Serialize(stream); - MessageType::Game id{}; - ASSERT_TRUE(stream.Read(id)); EXPECT_FALSE(GameMessageDecoder::CanDecode(MessageType::Game::ECHO_SYNC_SKILL, true)); const auto fields = GameMessageDecoder::Decode(MessageType::Game::ECHO_SYNC_SKILL, false, stream); diff --git a/tests/dGameTests/dGameMessagesTests/Legacy/SkillMessagesLegacy.h b/tests/dGameTests/dGameMessagesTests/Legacy/SkillMessagesLegacy.h new file mode 100644 index 000000000..5ab64d25e --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/Legacy/SkillMessagesLegacy.h @@ -0,0 +1,600 @@ +#ifndef SKILLMESSAGESLEGACY_H +#define SKILLMESSAGESLEGACY_H + +// FROZEN ORACLE - DO NOT EDIT. +// Verbatim copies of the one-off skill and projectile message classes that SkillMessages.h replaced +// (dGame/dGameMessages/{StartSkill,EchoStartSkill,SyncSkill,EchoSyncSkill,RequestServerProjectileImpact, +// DoClientProjectileImpact}.h) and of GameMessages::SendAddSkill / SendRemoveSkill +// (dGame/dGameMessages/GameMessages.cpp), branched from origin/main 129199e4. Only the namespace changed. +// Each class's Serialize writes the message ID and then the payload; the old senders wrote the CLIENT/GAME_MSG +// header and the target object ID before it. The Read* function is the read sequence of the replaced switch case. + +#include "BehaviorSlot.h" +#include "BitStream.h" +#include "BitStreamUtils.h" +#include "dCommonVars.h" +#include "dServer.h" +#include "Entity.h" +#include "Game.h" +#include "MessageType/Client.h" +#include "MessageType/Game.h" +#include "NiPoint3.h" +#include "NiQuaternion.h" +#include "ServiceType.h" + +#include +#include + +namespace LegacyGameMessages { +/** + * Same as sync skill but with different network options. An echo down to other clients that need to play the skill. + */ +class StartSkill { +public: + StartSkill() { + bUsedMouse = false; + consumableItemID = LWOOBJID_EMPTY; + fCasterLatency = 0.0f; + iCastType = 0; + lastClickedPosit = NiPoint3Constant::ZERO; + optionalTargetID = LWOOBJID_EMPTY; + originatorRot = QuatUtils::IDENTITY; + uiSkillHandle = 0; + } + + StartSkill(LWOOBJID _optionalOriginatorID, std::string _sBitStream, TSkillID _skillID, bool _bUsedMouse = false, LWOOBJID _consumableItemID = LWOOBJID_EMPTY, float _fCasterLatency = 0.0f, int32_t _iCastType = 0, NiPoint3 _lastClickedPosit = NiPoint3Constant::ZERO, LWOOBJID _optionalTargetID = LWOOBJID_EMPTY, NiQuaternion _originatorRot = QuatUtils::IDENTITY, uint32_t _uiSkillHandle = 0) { + bUsedMouse = _bUsedMouse; + consumableItemID = _consumableItemID; + fCasterLatency = _fCasterLatency; + iCastType = _iCastType; + lastClickedPosit = _lastClickedPosit; + optionalOriginatorID = _optionalOriginatorID; + optionalTargetID = _optionalTargetID; + originatorRot = _originatorRot; + sBitStream = _sBitStream; + skillID = _skillID; + uiSkillHandle = _uiSkillHandle; + } + + StartSkill(RakNet::BitStream& stream) : StartSkill() { + Deserialize(stream); + } + + ~StartSkill() { + } + + void Serialize(RakNet::BitStream& stream) { + stream.Write(MessageType::Game::START_SKILL); + + stream.Write(bUsedMouse); + + stream.Write(consumableItemID != LWOOBJID_EMPTY); + if (consumableItemID != LWOOBJID_EMPTY) stream.Write(consumableItemID); + + stream.Write(fCasterLatency != 0.0f); + if (fCasterLatency != 0.0f) stream.Write(fCasterLatency); + + stream.Write(iCastType != 0); + if (iCastType != 0) stream.Write(iCastType); + + stream.Write(lastClickedPosit != NiPoint3Constant::ZERO); + if (lastClickedPosit != NiPoint3Constant::ZERO) stream.Write(lastClickedPosit); + + stream.Write(optionalOriginatorID); + + stream.Write(optionalTargetID != LWOOBJID_EMPTY); + if (optionalTargetID != LWOOBJID_EMPTY) stream.Write(optionalTargetID); + + stream.Write(originatorRot != QuatUtils::IDENTITY); + if (originatorRot != QuatUtils::IDENTITY) stream.Write(originatorRot); + + uint32_t sBitStreamLength = sBitStream.length(); + stream.Write(sBitStreamLength); + for (uint32_t k = 0; k < sBitStreamLength; k++) { + stream.Write(sBitStream[k]); + } + + stream.Write(skillID); + + stream.Write(uiSkillHandle != 0); + if (uiSkillHandle != 0) stream.Write(uiSkillHandle); + } + + bool Deserialize(RakNet::BitStream& stream) { + stream.Read(bUsedMouse); + + bool consumableItemIDIsDefault{}; + stream.Read(consumableItemIDIsDefault); + if (consumableItemIDIsDefault != 0) stream.Read(consumableItemID); + + bool fCasterLatencyIsDefault{}; + stream.Read(fCasterLatencyIsDefault); + if (fCasterLatencyIsDefault != 0) stream.Read(fCasterLatency); + + bool iCastTypeIsDefault{}; + stream.Read(iCastTypeIsDefault); + if (iCastTypeIsDefault != 0) stream.Read(iCastType); + + bool lastClickedPositIsDefault{}; + stream.Read(lastClickedPositIsDefault); + if (lastClickedPositIsDefault != 0) stream.Read(lastClickedPosit); + + stream.Read(optionalOriginatorID); + + bool optionalTargetIDIsDefault{}; + stream.Read(optionalTargetIDIsDefault); + if (optionalTargetIDIsDefault != 0) stream.Read(optionalTargetID); + + bool originatorRotIsDefault{}; + stream.Read(originatorRotIsDefault); + if (originatorRotIsDefault != 0) stream.Read(originatorRot); + + uint32_t sBitStreamLength{}; + stream.Read(sBitStreamLength); + if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; + for (uint32_t k = 0; k < sBitStreamLength; k++) { + unsigned char character; + stream.Read(character); + sBitStream.push_back(character); + } + + stream.Read(skillID); + + bool uiSkillHandleIsDefault{}; + stream.Read(uiSkillHandleIsDefault); + if (uiSkillHandleIsDefault != 0) stream.Read(uiSkillHandle); + + return true; + } + + bool bUsedMouse = false; + LWOOBJID consumableItemID{}; + float fCasterLatency{}; + int32_t iCastType{}; + NiPoint3 lastClickedPosit{}; + LWOOBJID optionalOriginatorID{}; + LWOOBJID optionalTargetID{}; + NiQuaternion originatorRot = QuatUtils::IDENTITY; + std::string sBitStream = ""; + TSkillID skillID = 0; + uint32_t uiSkillHandle = 0; +}; + +/* Same as start skill but with different network options. An echo down to other clients that need to play the skill. */ +class EchoStartSkill { +public: + EchoStartSkill() { + bUsedMouse = false; + fCasterLatency = 0.0f; + iCastType = 0; + lastClickedPosit = NiPoint3Constant::ZERO; + optionalTargetID = LWOOBJID_EMPTY; + originatorRot = QuatUtils::IDENTITY; + uiSkillHandle = 0; + } + + EchoStartSkill(LWOOBJID _optionalOriginatorID, std::string _sBitStream, TSkillID _skillID, bool _bUsedMouse = false, float _fCasterLatency = 0.0f, int32_t _iCastType = 0, NiPoint3 _lastClickedPosit = NiPoint3Constant::ZERO, LWOOBJID _optionalTargetID = LWOOBJID_EMPTY, NiQuaternion _originatorRot = QuatUtils::IDENTITY, uint32_t _uiSkillHandle = 0) { + bUsedMouse = _bUsedMouse; + fCasterLatency = _fCasterLatency; + iCastType = _iCastType; + lastClickedPosit = _lastClickedPosit; + optionalOriginatorID = _optionalOriginatorID; + optionalTargetID = _optionalTargetID; + originatorRot = _originatorRot; + sBitStream = _sBitStream; + skillID = _skillID; + uiSkillHandle = _uiSkillHandle; + } + + EchoStartSkill(RakNet::BitStream& stream) : EchoStartSkill() { + Deserialize(stream); + } + + ~EchoStartSkill() { + } + + void Serialize(RakNet::BitStream& stream) { + stream.Write(MessageType::Game::ECHO_START_SKILL); + + stream.Write(bUsedMouse); + + stream.Write(fCasterLatency != 0.0f); + if (fCasterLatency != 0.0f) stream.Write(fCasterLatency); + + stream.Write(iCastType != 0); + if (iCastType != 0) stream.Write(iCastType); + + stream.Write(lastClickedPosit != NiPoint3Constant::ZERO); + if (lastClickedPosit != NiPoint3Constant::ZERO) stream.Write(lastClickedPosit); + + stream.Write(optionalOriginatorID); + + stream.Write(optionalTargetID != LWOOBJID_EMPTY); + if (optionalTargetID != LWOOBJID_EMPTY) stream.Write(optionalTargetID); + + stream.Write(originatorRot != QuatUtils::IDENTITY); + if (originatorRot != QuatUtils::IDENTITY) stream.Write(originatorRot); + + uint32_t sBitStreamLength = sBitStream.length(); + stream.Write(sBitStreamLength); + for (uint32_t k = 0; k < sBitStreamLength; k++) { + stream.Write(sBitStream[k]); + } + + stream.Write(skillID); + + stream.Write(uiSkillHandle != 0); + if (uiSkillHandle != 0) stream.Write(uiSkillHandle); + } + + bool Deserialize(RakNet::BitStream& stream) { + stream.Read(bUsedMouse); + + bool fCasterLatencyIsDefault{}; + stream.Read(fCasterLatencyIsDefault); + if (fCasterLatencyIsDefault != 0) stream.Read(fCasterLatency); + + bool iCastTypeIsDefault{}; + stream.Read(iCastTypeIsDefault); + if (iCastTypeIsDefault != 0) stream.Read(iCastType); + + bool lastClickedPositIsDefault{}; + stream.Read(lastClickedPositIsDefault); + if (lastClickedPositIsDefault != 0) stream.Read(lastClickedPosit); + + stream.Read(optionalOriginatorID); + + bool optionalTargetIDIsDefault{}; + stream.Read(optionalTargetIDIsDefault); + if (optionalTargetIDIsDefault != 0) stream.Read(optionalTargetID); + + bool originatorRotIsDefault{}; + stream.Read(originatorRotIsDefault); + if (originatorRotIsDefault != 0) stream.Read(originatorRot); + + uint32_t sBitStreamLength{}; + stream.Read(sBitStreamLength); + if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; + for (uint32_t k = 0; k < sBitStreamLength; k++) { + unsigned char character; + stream.Read(character); + sBitStream.push_back(character); + } + + stream.Read(skillID); + + bool uiSkillHandleIsDefault{}; + stream.Read(uiSkillHandleIsDefault); + if (uiSkillHandleIsDefault != 0) stream.Read(uiSkillHandle); + + return true; + } + + bool bUsedMouse; + float fCasterLatency; + int32_t iCastType; + NiPoint3 lastClickedPosit; + LWOOBJID optionalOriginatorID; + LWOOBJID optionalTargetID; + NiQuaternion originatorRot = QuatUtils::IDENTITY; + std::string sBitStream; + TSkillID skillID; + uint32_t uiSkillHandle; +}; + +/* Message to synchronize a skill cast */ +class SyncSkill { +public: + SyncSkill() { + bDone = false; + } + + SyncSkill(std::string _sBitStream, uint32_t _uiBehaviorHandle, uint32_t _uiSkillHandle, bool _bDone = false) { + bDone = _bDone; + sBitStream = _sBitStream; + uiBehaviorHandle = _uiBehaviorHandle; + uiSkillHandle = _uiSkillHandle; + } + + SyncSkill(RakNet::BitStream& stream) : SyncSkill() { + Deserialize(stream); + } + + ~SyncSkill() { + } + + void Serialize(RakNet::BitStream& stream) { + stream.Write(MessageType::Game::SYNC_SKILL); + + stream.Write(bDone); + uint32_t sBitStreamLength = sBitStream.length(); + stream.Write(sBitStreamLength); + for (unsigned int k = 0; k < sBitStreamLength; k++) { + stream.Write(sBitStream[k]); + } + + stream.Write(uiBehaviorHandle); + stream.Write(uiSkillHandle); + } + + bool Deserialize(RakNet::BitStream& stream) { + stream.Read(bDone); + uint32_t sBitStreamLength{}; + stream.Read(sBitStreamLength); + if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; + for (uint32_t k = 0; k < sBitStreamLength; k++) { + unsigned char character; + stream.Read(character); + sBitStream.push_back(character); + } + + stream.Read(uiBehaviorHandle); + stream.Read(uiSkillHandle); + + return true; + } + + bool bDone{}; + std::string sBitStream{}; + uint32_t uiBehaviorHandle{}; + uint32_t uiSkillHandle{}; +}; + +/* Message to synchronize a skill cast */ +class EchoSyncSkill { +public: + EchoSyncSkill() { + bDone = false; + } + + EchoSyncSkill(std::string _sBitStream, uint32_t _uiBehaviorHandle, uint32_t _uiSkillHandle, bool _bDone = false) { + bDone = _bDone; + sBitStream = _sBitStream; + uiBehaviorHandle = _uiBehaviorHandle; + uiSkillHandle = _uiSkillHandle; + } + + EchoSyncSkill(RakNet::BitStream& stream) : EchoSyncSkill() { + Deserialize(stream); + } + + ~EchoSyncSkill() { + } + + void Serialize(RakNet::BitStream& stream) { + stream.Write(MessageType::Game::ECHO_SYNC_SKILL); + + stream.Write(bDone); + uint32_t sBitStreamLength = sBitStream.length(); + stream.Write(sBitStreamLength); + for (uint32_t k = 0; k < sBitStreamLength; k++) { + stream.Write(sBitStream[k]); + } + + stream.Write(uiBehaviorHandle); + stream.Write(uiSkillHandle); + } + + bool Deserialize(RakNet::BitStream& stream) { + stream.Read(bDone); + + uint32_t sBitStreamLength{}; + stream.Read(sBitStreamLength); + if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; + for (unsigned int k = 0; k < sBitStreamLength; k++) { + unsigned char character; + stream.Read(character); + sBitStream.push_back(character); + } + + stream.Read(uiBehaviorHandle); + stream.Read(uiSkillHandle); + + return true; + } + + bool bDone{}; + std::string sBitStream{}; + uint32_t uiBehaviorHandle{}; + uint32_t uiSkillHandle{}; +}; + +/* Notifying the server that a locally owned projectile impacted. Sent to the caster of the projectile + should always be the local char. */ +class RequestServerProjectileImpact { +public: + RequestServerProjectileImpact() { + i64LocalID = LWOOBJID_EMPTY; + i64TargetID = LWOOBJID_EMPTY; + } + + RequestServerProjectileImpact(std::string _sBitStream, LWOOBJID _i64LocalID = LWOOBJID_EMPTY, LWOOBJID _i64TargetID = LWOOBJID_EMPTY) { + i64LocalID = _i64LocalID; + i64TargetID = _i64TargetID; + sBitStream = _sBitStream; + } + + RequestServerProjectileImpact(RakNet::BitStream& stream) : RequestServerProjectileImpact() { + Deserialize(stream); + } + + ~RequestServerProjectileImpact() { + } + + void Serialize(RakNet::BitStream& stream) { + stream.Write(MessageType::Game::REQUEST_SERVER_PROJECTILE_IMPACT); + + stream.Write(i64LocalID != LWOOBJID_EMPTY); + if (i64LocalID != LWOOBJID_EMPTY) stream.Write(i64LocalID); + + stream.Write(i64TargetID != LWOOBJID_EMPTY); + if (i64TargetID != LWOOBJID_EMPTY) stream.Write(i64TargetID); + + uint32_t sBitStreamLength = sBitStream.length(); + stream.Write(sBitStreamLength); + for (uint32_t k = 0; k < sBitStreamLength; k++) { + stream.Write(sBitStream[k]); + } + + } + + bool Deserialize(RakNet::BitStream& stream) { + bool i64LocalIDIsDefault{}; + stream.Read(i64LocalIDIsDefault); + if (i64LocalIDIsDefault != 0) stream.Read(i64LocalID); + + bool i64TargetIDIsDefault{}; + stream.Read(i64TargetIDIsDefault); + if (i64TargetIDIsDefault != 0) stream.Read(i64TargetID); + + uint32_t sBitStreamLength{}; + stream.Read(sBitStreamLength); + if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; + for (uint32_t k = 0; k < sBitStreamLength; k++) { + unsigned char character; + stream.Read(character); + sBitStream.push_back(character); + } + + + return true; + } + + LWOOBJID i64LocalID; + LWOOBJID i64TargetID; + std::string sBitStream; +}; + +/* Tell a client local projectile to impact */ +class DoClientProjectileImpact { +public: + DoClientProjectileImpact() { + i64OrgID = LWOOBJID_EMPTY; + i64OwnerID = LWOOBJID_EMPTY; + i64TargetID = LWOOBJID_EMPTY; + } + + DoClientProjectileImpact(std::string _sBitStream, LWOOBJID _i64OrgID = LWOOBJID_EMPTY, LWOOBJID _i64OwnerID = LWOOBJID_EMPTY, LWOOBJID _i64TargetID = LWOOBJID_EMPTY) { + i64OrgID = _i64OrgID; + i64OwnerID = _i64OwnerID; + i64TargetID = _i64TargetID; + sBitStream = _sBitStream; + } + + DoClientProjectileImpact(RakNet::BitStream& stream) : DoClientProjectileImpact() { + Deserialize(stream); + } + + ~DoClientProjectileImpact() { + } + + void Serialize(RakNet::BitStream& stream) { + stream.Write(MessageType::Game::DO_CLIENT_PROJECTILE_IMPACT); + + stream.Write(i64OrgID != LWOOBJID_EMPTY); + if (i64OrgID != LWOOBJID_EMPTY) stream.Write(i64OrgID); + + stream.Write(i64OwnerID != LWOOBJID_EMPTY); + if (i64OwnerID != LWOOBJID_EMPTY) stream.Write(i64OwnerID); + + stream.Write(i64TargetID != LWOOBJID_EMPTY); + if (i64TargetID != LWOOBJID_EMPTY) stream.Write(i64TargetID); + + uint32_t sBitStreamLength = sBitStream.length(); + stream.Write(sBitStreamLength); + for (uint32_t k = 0; k < sBitStreamLength; k++) { + stream.Write(sBitStream[k]); + } + + } + + bool Deserialize(RakNet::BitStream& stream) { + bool i64OrgIDIsDefault{}; + stream.Read(i64OrgIDIsDefault); + if (i64OrgIDIsDefault != 0) stream.Read(i64OrgID); + + bool i64OwnerIDIsDefault{}; + stream.Read(i64OwnerIDIsDefault); + if (i64OwnerIDIsDefault != 0) stream.Read(i64OwnerID); + + bool i64TargetIDIsDefault{}; + stream.Read(i64TargetIDIsDefault); + if (i64TargetIDIsDefault != 0) stream.Read(i64TargetID); + + uint32_t sBitStreamLength{}; + stream.Read(sBitStreamLength); + if (sBitStreamLength > MAX_MESSAGE_LENGTH) return false; + for (uint32_t k = 0; k < sBitStreamLength; k++) { + unsigned char character; + stream.Read(character); + sBitStream.push_back(character); + } + + + return true; + } + + LWOOBJID i64OrgID; + LWOOBJID i64OwnerID; + LWOOBJID i64TargetID; + std::string sBitStream; +}; + +inline void SendAddSkill(Entity* entity, TSkillID skillID, BehaviorSlot slotID) { + int AICombatWeight = 0; + bool bFromSkillSet = false; + int castType = 0; + float fTimeSecs = -1.0f; + int iTimesCanCast = -1; + bool temporary = true; + + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(MessageType::Game::ADD_SKILL); + + bitStream.Write(AICombatWeight != 0); + if (AICombatWeight != 0) bitStream.Write(AICombatWeight); + + bitStream.Write(bFromSkillSet); + + bitStream.Write(castType != 0); + if (castType != 0) bitStream.Write(castType); + + bitStream.Write(fTimeSecs != -1.0f); + if (fTimeSecs != -1.0f) bitStream.Write(fTimeSecs); + + bitStream.Write(iTimesCanCast != -1); + if (iTimesCanCast != -1) bitStream.Write(iTimesCanCast); + + bitStream.Write(skillID); + + bitStream.Write(slotID != BehaviorSlot::Invalid); + if (slotID != BehaviorSlot::Invalid) bitStream.Write(slotID); + + bitStream.Write(temporary); + + SystemAddress sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} + +inline void SendRemoveSkill(Entity* entity, TSkillID skillID) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(MessageType::Game::REMOVE_SKILL); + bitStream.Write(false); + bitStream.Write(skillID); + + SystemAddress sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} + +// The START_SKILL switch case in GameMessageHandler::HandleMessage read the message with StartSkill::Deserialize, +// SYNC_SKILL with the SyncSkill(RakNet::BitStream&) constructor and REQUEST_SERVER_PROJECTILE_IMPACT with +// RequestServerProjectileImpact::Deserialize; the classes above are those read sequences. +} + +#endif // SKILLMESSAGESLEGACY_H diff --git a/tests/dGameTests/dGameMessagesTests/SkillMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/SkillMessagesTests.cpp new file mode 100644 index 000000000..64a353781 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/SkillMessagesTests.cpp @@ -0,0 +1,313 @@ +#include "SkillMessages.h" +#include "GameDependencies.h" +#include "GameMessageTestUtils.h" +#include "Legacy/SkillMessagesLegacy.h" + +#include "BehaviorSlot.h" + +#include +#include +#include + +#include + +using namespace GameMessageTestUtils; + +namespace { + const std::vector g_Streams = { "", std::string("\x00", 1), std::string("\x01\x02\xff", 3), std::string(1000, '\x5a') }; + const std::vector g_Ids = { LWOOBJID_EMPTY, 0x1000000000000001LL, 0x0102030405060708LL }; + const std::vector g_Points = { NiPoint3Constant::ZERO, NiPoint3(1.0f, -2.5f, 3.0f) }; + const std::vector g_Rotations = { QuatUtils::IDENTITY, NiQuaternion(0.5f, 0.5f, -0.5f, 0.5f) }; + + PacketBytes Payload(const GameMessages::NetGameMsg& msg) { + RakNet::BitStream bitStream; + msg.Serialize(bitStream); + return FromBitStream(bitStream); + } + + // What the old senders put on the wire: the CLIENT/GAME_MSG header, the target, then the class's Serialize + // (message ID and payload). + template + PacketBytes LegacyPacket(LWOOBJID target, Legacy& legacy) { + RakNet::BitStream bitStream; + BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::GAME_MSG); + bitStream.Write(target); + legacy.Serialize(bitStream); + return FromBitStream(bitStream); + } + + // Serializes msg and reads it back with the legacy class's Deserialize; both must consume the same bits. + template + Legacy ReadWithLegacy(const GameMessages::NetGameMsg& msg) { + RakNet::BitStream wire; + msg.Serialize(wire); + RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + Legacy legacy; + EXPECT_TRUE(legacy.Deserialize(legacyStream)); + EXPECT_EQ(legacyStream.GetReadOffset(), wire.GetNumberOfBitsUsed()); + return legacy; + } +} + +class SkillMessagesTests : public GameDependenciesTest { +protected: + void SetUp() override { SetUpDependencies(); } + void TearDown() override { TearDownDependencies(); } +}; + +TEST_F(SkillMessagesTests, AddAndRemoveSkillMatchLegacy) { + for (const auto target : g_Targets) { + Entity entity(target, info); + for (const TSkillID skillID : { 0, 1, -1, 1727, std::numeric_limits::max() }) { + for (const auto slot : { BehaviorSlot::Invalid, BehaviorSlot::Primary, BehaviorSlot::Offhand, BehaviorSlot::Neck, BehaviorSlot::Head, BehaviorSlot::Consumable }) { + GameMessages::AddSkill msg; + msg.target = target; + msg.skillID = skillID; + msg.slotID = slot; + const auto legacy = Capture([&] { LegacyGameMessages::SendAddSkill(&entity, skillID, slot); }); + const auto ours = Capture([&] { msg.SendToClient(entity.GetSystemAddress()); }); + ASSERT_EQ(legacy.size(), 1); + ASSERT_EQ(ours.size(), 1); + EXPECT_PACKET_EQ(FromCapture(legacy[0]), FromCapture(ours[0])); + EXPECT_EQ(legacy[0].broadcast, ours[0].broadcast); + EXPECT_EQ(legacy[0].sysAddr, ours[0].sysAddr); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.skillID, skillID); + EXPECT_EQ(copy.slotID, slot); + EXPECT_TRUE(copy.temporary); + } + + GameMessages::RemoveSkill remove; + remove.target = target; + remove.skillID = skillID; + const auto legacy = Capture([&] { LegacyGameMessages::SendRemoveSkill(&entity, skillID); }); + const auto ours = Capture([&] { remove.SendToClient(entity.GetSystemAddress()); }); + ASSERT_EQ(legacy.size(), 1); + ASSERT_EQ(ours.size(), 1); + EXPECT_PACKET_EQ(FromCapture(legacy[0]), FromCapture(ours[0])); + EXPECT_EQ(legacy[0].broadcast, ours[0].broadcast); + EXPECT_EQ(RoundTrip(remove).skillID, skillID); + } + } + + // Every optional field of AddSkill set, which DLU never sends, still round trips. + GameMessages::AddSkill full; + full.AICombatWeight = 3; + full.bFromSkillSet = true; + full.castType = 2; + full.fTimeSecs = 1.5f; + full.iTimesCanCast = 4; + full.skillID = 9; + full.slotID = BehaviorSlot::Head; + full.temporary = false; + const auto copy = RoundTrip(full); + EXPECT_EQ(copy.AICombatWeight, 3); + EXPECT_EQ(copy.castType, 2); + EXPECT_EQ(copy.fTimeSecs, 1.5f); + EXPECT_EQ(copy.iTimesCanCast, 4); + ExpectTruncatedFails(full); +} + +TEST_F(SkillMessagesTests, StartSkillAndEchoMatchLegacy) { + for (const auto target : g_Targets) { + for (const auto& sBitStream : g_Streams) { + for (const auto optionalID : g_Ids) { + for (const auto& point : g_Points) { + for (const auto& rotation : g_Rotations) { + for (const bool flag : { false, true }) { + const float latency = flag ? 0.25f : 0.0f; + const int32_t castType = flag ? 3 : 0; + const uint32_t handle = flag ? 77u : 0u; + const TSkillID skillID = flag ? 1727 : -1; + + GameMessages::EchoStartSkill echo; + echo.target = target; + echo.bUsedMouse = flag; + echo.fCasterLatency = latency; + echo.iCastType = castType; + echo.lastClickedPosit = point; + echo.optionalOriginatorID = target; + echo.optionalTargetID = optionalID; + echo.originatorRot = rotation; + echo.sBitStream = sBitStream; + echo.skillID = skillID; + echo.uiSkillHandle = handle; + LegacyGameMessages::EchoStartSkill legacyEcho(target, sBitStream, skillID, flag, latency, castType, point, optionalID, rotation, handle); + EXPECT_PACKET_EQ(LegacyPacket(target, legacyEcho), StructPacket(echo)); + const auto echoRead = ReadWithLegacy(echo); + EXPECT_EQ(echoRead.sBitStream, sBitStream); + EXPECT_EQ(echoRead.optionalTargetID, optionalID); + const auto echoCopy = RoundTrip(echo); + EXPECT_EQ(echoCopy.sBitStream, sBitStream); + EXPECT_EQ(echoCopy.originatorRot, rotation); + EXPECT_EQ(echoCopy.lastClickedPosit, point); + + GameMessages::StartSkill start; + start.bUsedMouse = flag; + start.consumableItemID = optionalID; + start.fCasterLatency = latency; + start.iCastType = castType; + start.lastClickedPosit = point; + start.optionalOriginatorID = target; + start.optionalTargetID = optionalID; + start.originatorRot = rotation; + start.sBitStream = sBitStream; + start.skillID = skillID; + start.uiSkillHandle = handle; + LegacyGameMessages::StartSkill legacyStart(target, sBitStream, skillID, flag, optionalID, latency, castType, point, optionalID, rotation, handle); + EXPECT_PACKET_EQ(LegacyPacket(target, legacyStart), [&] { auto copy = start; copy.target = target; return StructPacket(copy); }()); + const auto legacy = ReadWithLegacy(start); + EXPECT_EQ(legacy.bUsedMouse, start.bUsedMouse); + EXPECT_EQ(legacy.consumableItemID, start.consumableItemID); + EXPECT_EQ(legacy.fCasterLatency, start.fCasterLatency); + EXPECT_EQ(legacy.iCastType, start.iCastType); + EXPECT_EQ(legacy.lastClickedPosit, start.lastClickedPosit); + EXPECT_EQ(legacy.optionalOriginatorID, start.optionalOriginatorID); + EXPECT_EQ(legacy.optionalTargetID, start.optionalTargetID); + EXPECT_EQ(legacy.originatorRot, start.originatorRot); + EXPECT_EQ(legacy.sBitStream, start.sBitStream); + EXPECT_EQ(legacy.skillID, start.skillID); + EXPECT_EQ(legacy.uiSkillHandle, start.uiSkillHandle); + const auto startCopy = RoundTrip(start); + EXPECT_EQ(startCopy.sBitStream, sBitStream); + EXPECT_EQ(startCopy.consumableItemID, optionalID); + if (sBitStream.size() < 8) { + ExpectTruncatedFails(start); + ExpectTruncatedFails(echo); + } + } + } + } + } + } + } +} + +TEST_F(SkillMessagesTests, SyncSkillAndEchoMatchLegacy) { + for (const auto target : g_Targets) { + for (const auto& sBitStream : g_Streams) { + for (const bool bDone : { false, true }) { + for (const uint32_t handle : { 0u, 1u, 0xFFFFFFFFu }) { + GameMessages::EchoSyncSkill echo; + echo.target = target; + echo.bDone = bDone; + echo.sBitStream = sBitStream; + echo.uiBehaviorHandle = handle; + echo.uiSkillHandle = handle ^ 0x5u; + LegacyGameMessages::EchoSyncSkill legacyEcho(sBitStream, handle, handle ^ 0x5u, bDone); + EXPECT_PACKET_EQ(LegacyPacket(target, legacyEcho), StructPacket(echo)); + EXPECT_EQ(RoundTrip(echo).sBitStream, sBitStream); + + GameMessages::SyncSkill sync; + sync.bDone = bDone; + sync.sBitStream = sBitStream; + sync.uiBehaviorHandle = handle; + sync.uiSkillHandle = handle ^ 0x5u; + const auto legacy = ReadWithLegacy(sync); + EXPECT_EQ(legacy.bDone, bDone); + EXPECT_EQ(legacy.sBitStream, sBitStream); + EXPECT_EQ(legacy.uiBehaviorHandle, handle); + EXPECT_EQ(legacy.uiSkillHandle, handle ^ 0x5u); + LegacyGameMessages::SyncSkill legacySync(sBitStream, handle, handle ^ 0x5u, bDone); + EXPECT_PACKET_EQ(LegacyPacket(target, legacySync), [&] { auto copy = sync; copy.target = target; return StructPacket(copy); }()); + RoundTrip(sync); + if (sBitStream.size() < 8) ExpectTruncatedFails(sync); + } + } + } + } +} + +TEST_F(SkillMessagesTests, ProjectileImpactsMatchLegacy) { + for (const auto target : g_Targets) { + for (const auto& sBitStream : g_Streams) { + for (const auto a : g_Ids) { + for (const auto b : g_Ids) { + GameMessages::DoClientProjectileImpact impact; + impact.target = target; + impact.i64OrgID = a; + impact.i64OwnerID = b; + impact.i64TargetID = target; + impact.sBitStream = sBitStream; + LegacyGameMessages::DoClientProjectileImpact legacyImpact(sBitStream, a, b, target); + EXPECT_PACKET_EQ(LegacyPacket(target, legacyImpact), StructPacket(impact)); + const auto impactCopy = RoundTrip(impact); + EXPECT_EQ(impactCopy.i64OrgID, a); + EXPECT_EQ(impactCopy.i64OwnerID, b); + + GameMessages::RequestServerProjectileImpact request; + request.i64LocalID = a; + request.i64TargetID = b; + request.sBitStream = sBitStream; + const auto legacy = ReadWithLegacy(request); + EXPECT_EQ(legacy.i64LocalID, a); + EXPECT_EQ(legacy.i64TargetID, b); + EXPECT_EQ(legacy.sBitStream, sBitStream); + LegacyGameMessages::RequestServerProjectileImpact legacyRequest(sBitStream, a, b); + EXPECT_PACKET_EQ(LegacyPacket(target, legacyRequest), [&] { auto copy = request; copy.target = target; return StructPacket(copy); }()); + RoundTrip(request); + if (sBitStream.size() < 8) ExpectTruncatedFails(request); + } + } + } + } +} + +// The echoes went to everyone except the caster: a RakNet broadcast with the caster's address excluded. +TEST_F(SkillMessagesTests, EchoesBroadcastExceptTheCaster) { + GameMessages::EchoSyncSkill echo; + echo.target = 0x0102030405060708LL; + echo.bDone = true; + echo.sBitStream = "ab"; + const auto caster = ClientAddress(); + const auto legacy = Capture([&] { + RakNet::BitStream bitStreamLocal; + BitStreamUtils::WriteHeader(bitStreamLocal, ServiceType::CLIENT, MessageType::Client::GAME_MSG); + bitStreamLocal.Write(echo.target); + LegacyGameMessages::EchoSyncSkill legacyEcho("ab", 0, 0, true); + legacyEcho.Serialize(bitStreamLocal); + Game::server->Send(bitStreamLocal, caster, true); + }); + const auto ours = Capture([&] { echo.BroadcastExcept(caster); }); + ASSERT_EQ(legacy.size(), 1); + ASSERT_EQ(ours.size(), 1); + EXPECT_PACKET_EQ(FromCapture(legacy[0]), FromCapture(ours[0])); + EXPECT_TRUE(ours[0].broadcast); + EXPECT_EQ(ours[0].sysAddr, caster); +} + +TEST_F(SkillMessagesTests, SelectSkillAcceptsAnyPayload) { + GameMessages::SelectSkill select; + select.bFromSkillSet = true; + select.skillID = 1727; + const auto copy = RoundTrip(select); + EXPECT_TRUE(copy.bFromSkillSet); + EXPECT_EQ(copy.skillID, 1727); + RakNet::BitStream empty; + GameMessages::SelectSkill fromEmpty; + EXPECT_TRUE(fromEmpty.Deserialize(empty)); +} + +// Independent of the legacy code: hand computed payloads (RakNet writes MSB first). +TEST_F(SkillMessagesTests, GoldenBytes) { + GameMessages::AddSkill add; + add.skillID = 1; + add.slotID = BehaviorSlot::Primary; + // 5 zero bits (weight, fromSkillSet, castType, time, timesCanCast), skillID 1, bit + slot 0, temporary. + EXPECT_PACKET_EQ(FromHex("00 08 00 00 04 00 00 00 02", 71), Payload(add)); + + GameMessages::RemoveSkill remove; + remove.skillID = 1727; + EXPECT_PACKET_EQ(FromHex("5f 83 00 00 00", 33), Payload(remove)); + + GameMessages::SyncSkill sync; + sync.bDone = true; + sync.sBitStream = std::string("\xab", 1); + sync.uiBehaviorHandle = 2; + sync.uiSkillHandle = 3; + EXPECT_PACKET_EQ(FromHex("80 80 00 00 55 81 00 00 00 01 80 00 00 00", 105), Payload(sync)); + + GameMessages::RequestServerProjectileImpact request; + request.i64TargetID = 0x11; + EXPECT_PACKET_EQ(FromHex("44 40 00 00 00 00 00 00 00 00 00 00 00", 98), Payload(request)); +}