From f7f5d304d0c62825820bd870977d85f8c18e753b Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sun, 27 Sep 2026 00:01:18 -0500 Subject: [PATCH] refactor: death, stun, buff and knockback game messages as structs Converts the combat messages to NetGameMsgs in CombatMessages.{h,cpp}: Die, Resurrect, SetResurrectRestoreValues, SetPlayerAllowedRespawn (SendToClient, as before), Knockback, SetStunned, SetStunImmunity, SetStatusImmunity, AddBuff, RemoveBuff, AddRunSpeedModifier, RemoveRunSpeedModifier and DeactivateBubbleBuffFromServer, and the received RequestDie, RequestSmashPlayer, RequestResurrect, Resurrect, ActivateBubbleBuff and DeactivateBubbleBuff. Smash and UnSmash move here from GameMessages.h (Smash's ghostCapacity is ghostOpacity, the client's name) and gain a Deserialize. The callers (DestroyableComponent, BuffComponent, ControllablePhysicsComponent, QuickBuildComponent, RacingControl, RailActivator and the scripts) build the structs. SendResurrect's respawn timer moves to DestroyableComponent::Resurrect. The received messages are registered in GameMessageHandler's map and their switch cases are deleted, along with the unused HandleRequestDie overload and the Send* functions nothing called (SendSmash, SendUnSmash, the run speed modifiers, SendActivateBubbleBuffFromServer). Handler logic is unchanged. No wire change and no change in recipients. Verified byte for byte against a frozen verbatim copy of the old functions over an input grid (every optional field, bool patterns, empty and long strings), received messages compared with the old handlers' read sequences and truncated payloads rejected, hand computed golden bytes, round trips, and a deliberate width mutation made the tests fail. Known difference from the client, not changed here: DLU writes SetStatusImmunity's nine flags in its own order; the client reads them alphabetically (GameMessage::SetStatusImmunity::Serialize @ 00d8f140). Co-Authored-By: Claude Opus 5.5 --- dGame/Entity.cpp | 2 +- dGame/dComponents/BuffComponent.cpp | 27 +- .../ControllablePhysicsComponent.cpp | 23 +- dGame/dComponents/DestroyableComponent.cpp | 70 ++- dGame/dComponents/DestroyableComponent.h | 6 + dGame/dComponents/ModelComponent.cpp | 1 + dGame/dComponents/QuickBuildComponent.cpp | 23 +- dGame/dComponents/RacingControlComponent.cpp | 40 +- dGame/dComponents/RailActivatorComponent.cpp | 29 +- dGame/dGameMessages/CMakeLists.txt | 1 + dGame/dGameMessages/CombatMessages.cpp | 415 +++++++++++++ dGame/dGameMessages/CombatMessages.h | 278 +++++++++ dGame/dGameMessages/GameMessageHandler.cpp | 32 +- dGame/dGameMessages/GameMessages.cpp | 571 ------------------ dGame/dGameMessages/GameMessages.h | 108 ---- dGame/dPropertyBehaviors/Strip.cpp | 1 + .../SlashCommands/GMZeroCommands.cpp | 2 +- .../02_server/Enemy/General/BaseEnemyApe.cpp | 17 +- dScripts/02_server/Map/AM/AmSkullkinDrill.cpp | 13 +- .../02_server/Map/AM/AmSkullkinDrillStand.cpp | 8 +- .../Map/FV/Racing/RaceMaelstromGeiser.cpp | 8 +- .../02_server/Map/GF/GfCaptainsCannon.cpp | 31 +- dScripts/02_server/Map/GF/MastTeleport.cpp | 29 +- .../02_server/Map/NT/NtAssemblyTubeServer.cpp | 29 +- .../02_server/Map/NT/NtParadoxPanelServer.cpp | 24 +- .../02_server/Map/NT/NtParadoxTeleServer.cpp | 29 +- .../Map/NT/NtVentureCannonServer.cpp | 29 +- .../02_server/Map/njhub/FlameJetServer.cpp | 9 +- dScripts/BaseConsoleTeleportServer.cpp | 33 +- dScripts/BaseWavesServer.cpp | 6 +- .../EquipmentScripts/PersonalFortress.cpp | 31 +- dScripts/ai/ACT/ActVehicleDeathTrigger.cpp | 8 +- dScripts/ai/AG/AgShipPlayerShockServer.cpp | 8 +- dScripts/ai/AG/AgSpiderBossMessage.cpp | 6 +- .../dGameMessagesTests/CMakeLists.txt | 1 + .../CombatMessagesTests.cpp | 381 ++++++++++++ .../Legacy/CombatMessagesLegacy.h | 480 +++++++++++++++ .../dGameMessagesTests/UnSmashTests.cpp | 1 + 38 files changed, 2002 insertions(+), 808 deletions(-) create mode 100644 dGame/dGameMessages/CombatMessages.cpp create mode 100644 dGame/dGameMessages/CombatMessages.h create mode 100644 tests/dGameTests/dGameMessagesTests/CombatMessagesTests.cpp create mode 100644 tests/dGameTests/dGameMessagesTests/Legacy/CombatMessagesLegacy.h diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index c9434cffb..2a9403954 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -2082,7 +2082,7 @@ std::string Entity::GetVarAsString(const std::u16string& name) const { void Entity::Resurrect() { if (IsPlayer()) { - GameMessages::SendResurrect(this); + DestroyableComponent::Resurrect(*this); } } diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index 95a9c4508..82b856ce0 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -7,6 +7,7 @@ #include "Logger.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "SkillComponent.h" #include "ControllablePhysicsComponent.h" #include "EntityManager.h" @@ -157,9 +158,22 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO addedByTeammate = std::count(team->members.begin(), team->members.end(), m_Parent->GetObjectID()) > 0; } - GameMessages::SendAddBuff(const_cast(m_Parent->GetObjectID()), source, static_cast(id), - static_cast(duration) * 1000, addImmunity, cancelOnDamaged, cancelOnDeath, - cancelOnLogout, cancelOnRemoveBuff, cancelOnUi, cancelOnUnequip, cancelOnZone, addedByTeammate, applyOnTeammates); + GameMessages::AddBuff addBuff; + addBuff.target = m_Parent->GetObjectID(); + addBuff.bAddedByTeammate = addedByTeammate; + addBuff.bApplyOnTeammates = applyOnTeammates; + addBuff.bCancelOnDamaged = cancelOnDamaged; + addBuff.bCancelOnDeath = cancelOnDeath; + addBuff.bCancelOnLogOut = cancelOnLogout; + addBuff.bCancelOnRemoveBuff = cancelOnRemoveBuff; + addBuff.bCancelOnUI = cancelOnUi; + addBuff.bCancelOnUnEquip = cancelOnUnequip; + addBuff.bCancelOnZone = cancelOnZone; + addBuff.bIsImmunity = addImmunity; + addBuff.i64AddedBy = source; + addBuff.uiBuffID = static_cast(id); + addBuff.uiDurationMS = static_cast(duration) * 1000; + addBuff.Send(UNASSIGNED_SYSTEM_ADDRESS); float tick = 0; float stacks = 0; @@ -223,7 +237,12 @@ void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity } } - GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id); + GameMessages::RemoveBuff removeBuff; + removeBuff.target = m_Parent->GetObjectID(); + removeBuff.bFromUnEquip = fromUnEquip; + removeBuff.bRemoveImmunity = removeImmunity; + removeBuff.uiBuffID = static_cast(id); + removeBuff.Send(UNASSIGNED_SYSTEM_ADDRESS); m_BuffsToRemove.insert(id); diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 9778ae301..99ae1b663 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -1,4 +1,5 @@ #include "ControllablePhysicsComponent.h" +#include "CombatMessages.h" #include "Entity.h" #include "BitStream.h" #include "Logger.h" @@ -347,16 +348,18 @@ void ControllablePhysicsComponent::SetStunImmunity( if (bImmuneToStunUseItem) m_ImmuneToStunUseItemCount += 1; } - GameMessages::SendSetStunImmunity( - m_Parent->GetObjectID(), state, m_Parent->GetSystemAddress(), originator, - bImmuneToStunAttack, - bImmuneToStunEquip, - bImmuneToStunInteract, - bImmuneToStunJump, - bImmuneToStunMove, - bImmuneToStunTurn, - bImmuneToStunUseItem - ); + GameMessages::SetStunImmunity stunImmunity; + stunImmunity.target = m_Parent->GetObjectID(); + stunImmunity.Caster = originator; + stunImmunity.StateChangeType = state; + stunImmunity.bImmuneToStunAttack = bImmuneToStunAttack; + stunImmunity.bImmuneToStunEquip = bImmuneToStunEquip; + stunImmunity.bImmuneToStunInteract = bImmuneToStunInteract; + stunImmunity.bImmuneToStunJump = bImmuneToStunJump; + stunImmunity.bImmuneToStunMove = bImmuneToStunMove; + stunImmunity.bImmuneToStunTurn = bImmuneToStunTurn; + stunImmunity.bImmuneToStunUseItem = bImmuneToStunUseItem; + stunImmunity.Send(m_Parent->GetSystemAddress()); } bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) { diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 297dd574a..1579b7eb0 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -1,4 +1,6 @@ #include "DestroyableComponent.h" +#include "CombatMessages.h" +#include "LevelProgressionComponent.h" #include "EconomyLedger.h" #include "BitStream.h" #include "Logger.h" @@ -779,7 +781,15 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType const auto isPlayer = m_Parent->IsPlayer(); - GameMessages::SendDie(m_Parent, source, source, true, killType, deathType, 0, 0, 0, isPlayer, true, 1); + GameMessages::Die die; + die.target = m_Parent->GetObjectID(); + die.bClientDeath = isPlayer; + die.bSpawnLoot = true; + die.deathType = deathType; + die.killType = killType; + die.killerID = source; + die.lootOwnerID = source; + die.Send(UNASSIGNED_SYSTEM_ADDRESS); //NANI?! if (!isPlayer) { @@ -870,18 +880,19 @@ void DestroyableComponent::SetStatusImmunity( if (bImmuneToPullToPoint) m_ImmuneToPullToPointCount += 1; } - GameMessages::SendSetStatusImmunity( - m_Parent->GetObjectID(), state, m_Parent->GetSystemAddress(), - bImmuneToBasicAttack, - bImmuneToDamageOverTime, - bImmuneToKnockback, - bImmuneToInterrupt, - bImmuneToSpeed, - bImmuneToImaginationGain, - bImmuneToImaginationLoss, - bImmuneToQuickbuildInterrupt, - bImmuneToPullToPoint - ); + GameMessages::SetStatusImmunity statusImmunity; + statusImmunity.target = m_Parent->GetObjectID(); + statusImmunity.StateChangeType = state; + statusImmunity.bImmuneToBasicAttack = bImmuneToBasicAttack; + statusImmunity.bImmuneToDOT = bImmuneToDamageOverTime; + statusImmunity.bImmuneToKnockback = bImmuneToKnockback; + statusImmunity.bImmuneToInterrupt = bImmuneToInterrupt; + statusImmunity.bImmuneToSpeed = bImmuneToSpeed; + statusImmunity.bImmuneToImaginationGain = bImmuneToImaginationGain; + statusImmunity.bImmuneToImaginationLoss = bImmuneToImaginationLoss; + statusImmunity.bImmuneToQuickbuildInterrupt = bImmuneToQuickbuildInterrupt; + statusImmunity.bImmuneToPullToPoint = bImmuneToPullToPoint; + statusImmunity.Send(m_Parent->GetSystemAddress()); } void DestroyableComponent::FixStats() { @@ -1222,3 +1233,36 @@ bool DestroyableComponent::OnIsDead(GameMessages::IsDead& isDead) { isDead.bDead = m_IsDead || (GetHealth() == 0 && GetArmor() == 0); return true; } + +void DestroyableComponent::Resurrect(Entity& entityRef) { + Entity* entity = &entityRef; + // Restore the players health after the animation for respawning has finished. + // This is when the health appered back in live, not immediately upon requesting respawn + // Add a half second in case someone decides to cheat and move during the death animation + // and just make sure the client has time to be ready. + constexpr float respawnTime = 3.66700005531311f + 0.5f; + entity->AddCallbackTimer(respawnTime, [=]() { + GameMessages::PlayerResurrectionFinished msg; + entity->NotifyPlayerResurrectionFinished(msg); + auto* destroyableComponent = entity->GetComponent(); + + if (destroyableComponent != nullptr && entity->GetLOT() == 1) { + destroyableComponent->SetIsDead(false); + auto* levelComponent = entity->GetComponent(); + if (levelComponent) { + int32_t healthToRestore = levelComponent->GetLevel() >= 45 ? 8 : 4; + if (healthToRestore > destroyableComponent->GetMaxHealth()) healthToRestore = destroyableComponent->GetMaxHealth(); + destroyableComponent->SetHealth(healthToRestore); + + int32_t imaginationToRestore = levelComponent->GetLevel() >= 45 ? 20 : 6; + if (imaginationToRestore > destroyableComponent->GetMaxImagination()) imaginationToRestore = destroyableComponent->GetMaxImagination(); + destroyableComponent->SetImagination(imaginationToRestore); + } + } + }); + + GameMessages::Resurrect resurrect; + resurrect.target = entity->GetObjectID(); + resurrect.bRezImmediately = false; + resurrect.Send(UNASSIGNED_SYSTEM_ADDRESS); +} diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 7ef55a210..820deee55 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -404,6 +404,12 @@ public: */ void Smash(LWOOBJID source, eKillType killType = eKillType::VIOLENT, const std::u16string& deathType = u"", uint32_t skillID = 0); + /** + * Brings entity back to life: tells every client (Resurrect), then, once the respawn animation is done, + * finishes the resurrection and restores a player's health and imagination. + */ + static void Resurrect(Entity& entity); + /** * Push or Pop a layer of status immunity to this entity */ diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index a701e85ec..ffc325339 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -1,4 +1,5 @@ #include "ModelComponent.h" +#include "CombatMessages.h" #include "Entity.h" #include "Game.h" diff --git a/dGame/dComponents/QuickBuildComponent.cpp b/dGame/dComponents/QuickBuildComponent.cpp index 11737c82a..f41cc72c5 100644 --- a/dGame/dComponents/QuickBuildComponent.cpp +++ b/dGame/dComponents/QuickBuildComponent.cpp @@ -3,6 +3,7 @@ #include "DestroyableComponent.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "EntityManager.h" #include "Game.h" #include "Logger.h" @@ -132,7 +133,12 @@ void QuickBuildComponent::Update(float deltaTime) { if (m_TimerIncomplete >= m_TimeBeforeSmash) { m_Builder = LWOOBJID_EMPTY; - GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true); + GameMessages::Die die; + die.target = m_Parent->GetObjectID(); + die.bClientDeath = false; + die.bSpawnLoot = true; + die.killerID = LWOOBJID_EMPTY; + die.Send(UNASSIGNED_SYSTEM_ADDRESS); ResetQuickBuild(false); } @@ -154,7 +160,13 @@ void QuickBuildComponent::Update(float deltaTime) { if (m_Timer >= m_ResetTime) { - GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 7.0f, false, true); + GameMessages::Die die; + die.target = m_Parent->GetObjectID(); + die.bClientDeath = false; + die.bSpawnLoot = true; + die.directionRelative_Force = 7.0f; + die.killerID = LWOOBJID_EMPTY; + die.Send(UNASSIGNED_SYSTEM_ADDRESS); ResetQuickBuild(false); } @@ -215,7 +227,12 @@ void QuickBuildComponent::Update(float deltaTime) { if (m_TimerIncomplete >= m_TimeBeforeSmash) { m_Builder = LWOOBJID_EMPTY; - GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true); + GameMessages::Die die; + die.target = m_Parent->GetObjectID(); + die.bClientDeath = false; + die.bSpawnLoot = true; + die.killerID = LWOOBJID_EMPTY; + die.Send(UNASSIGNED_SYSTEM_ADDRESS); ResetQuickBuild(false); } diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 9f5e29ddf..e86364e93 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -8,6 +8,7 @@ #include "DestroyableComponent.h" #include "EntityManager.h" #include "GameMessages.h" +#include "CombatMessages.h" #include "ActivityMessages.h" #include "RacingMessages.h" #include "InventoryComponent.h" @@ -83,7 +84,18 @@ void RacingControlComponent::OnPlayerLoaded(Entity* player) { m_LoadedPlayers++; // not live accurate to stun the player but prevents them from using skills during the race that are not meant to be used. - GameMessages::SendSetStunned(player->GetObjectID(), eStateChangeType::PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, true, true, true, true, true, true, true, true, true); + GameMessages::SetStunned stun; + stun.target = player->GetObjectID(); + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.bDontTerminateInteract = true; + stun.Send(player->GetSystemAddress()); LOG("Loading player %i", m_LoadedPlayers); @@ -316,8 +328,14 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string& if (!racingPlayer.noSmashOnReload) { racingPlayer.smashedTimes++; LOG("Death type %s", GeneralUtils::UTF16ToWTF8(deathType).c_str()); - GameMessages::SendDie(vehicle, vehicle->GetObjectID(), LWOOBJID_EMPTY, true, - eKillType::VIOLENT, deathType, 0, 0, 90.0f, false, true, 0); + GameMessages::Die die; + die.target = vehicle->GetObjectID(); + die.bClientDeath = false; + die.bSpawnLoot = true; + die.deathType = deathType; + die.directionRelative_Force = 90.0f; + die.killerID = vehicle->GetObjectID(); + die.Send(UNASSIGNED_SYSTEM_ADDRESS); auto* destroyableComponent = vehicle->GetComponent(); int32_t respawnImagination = 0; @@ -325,7 +343,10 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string& // Do not actually change the value yet. Do that on respawn. if (destroyableComponent) { respawnImagination = static_cast(ceil(destroyableComponent->GetImagination() / 2.0f / 10.0f)) * 10.0f; - GameMessages::SendSetResurrectRestoreValues(vehicle, -1, -1, respawnImagination); + GameMessages::SetResurrectRestoreValues restoreValues; + restoreValues.target = vehicle->GetObjectID(); + restoreValues.iImaginationRestore = respawnImagination; + restoreValues.Send(UNASSIGNED_SYSTEM_ADDRESS); } // Respawn the player in 2 seconds, as was done in live. Not sure if this value is in a setting somewhere else... @@ -350,7 +371,7 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string& resetInfo.upcomingPlane = racingPlayer.respawnIndex + 1; resetInfo.Send(UNASSIGNED_SYSTEM_ADDRESS); - GameMessages::SendResurrect(vehicle); + DestroyableComponent::Resurrect(*vehicle); auto* destroyableComponent = vehicle->GetComponent(); // Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live. if (destroyableComponent) destroyableComponent->SetImagination(respawnImagination); @@ -771,9 +792,12 @@ void RacingControlComponent::Update(float deltaTime) { // If the player is this far below the map, safe to assume they should // be smashed by death plane if (vehiclePosition.y < -500) { - GameMessages::SendDie(vehicle, m_Parent->GetObjectID(), - LWOOBJID_EMPTY, true, eKillType::VIOLENT, u"", 0, 0, 0, - true, false, 0); + GameMessages::Die die; + die.target = vehicle->GetObjectID(); + die.bClientDeath = true; + die.bSpawnLoot = false; + die.killerID = m_Parent->GetObjectID(); + die.Send(UNASSIGNED_SYSTEM_ADDRESS); OnRequestDie(playerEntity); diff --git a/dGame/dComponents/RailActivatorComponent.cpp b/dGame/dComponents/RailActivatorComponent.cpp index ce54ce057..1df1b9917 100644 --- a/dGame/dComponents/RailActivatorComponent.cpp +++ b/dGame/dComponents/RailActivatorComponent.cpp @@ -5,6 +5,7 @@ #include "Entity.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "QuickBuildComponent.h" #include "Game.h" #include "Logger.h" @@ -83,9 +84,17 @@ void RailActivatorComponent::OnUse(Entity* originator) { void RailActivatorComponent::OnRailMovementReady(Entity* originator) const { // Stun the originator - GameMessages::SendSetStunned(originator->GetObjectID(), eStateChangeType::PUSH, originator->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = originator->GetObjectID(); + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(originator->GetSystemAddress()); if (std::find(m_EntitiesOnRail.begin(), m_EntitiesOnRail.end(), originator->GetObjectID()) != m_EntitiesOnRail.end()) { // Stop the initial effects @@ -110,9 +119,17 @@ void RailActivatorComponent::OnRailMovementReady(Entity* originator) const { void RailActivatorComponent::OnCancelRailMovement(Entity* originator) { // Remove the stun from the originator - GameMessages::SendSetStunned(originator->GetObjectID(), eStateChangeType::POP, originator->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = originator->GetObjectID(); + stun.StateChangeType = eStateChangeType::POP; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(originator->GetSystemAddress()); auto* quickBuildComponent = m_Parent->GetComponent(); diff --git a/dGame/dGameMessages/CMakeLists.txt b/dGame/dGameMessages/CMakeLists.txt index 7c189ec03..1f95cdd73 100644 --- a/dGame/dGameMessages/CMakeLists.txt +++ b/dGame/dGameMessages/CMakeLists.txt @@ -1,6 +1,7 @@ set(DGAME_DGAMEMESSAGES_SOURCES "ActivityMessages.cpp" "BuildingMessages.cpp" + "CombatMessages.cpp" "EffectsMessages.cpp" "GameMessageDecoder.cpp" "GameMessageHandler.cpp" diff --git a/dGame/dGameMessages/CombatMessages.cpp b/dGame/dGameMessages/CombatMessages.cpp new file mode 100644 index 000000000..9702cac60 --- /dev/null +++ b/dGame/dGameMessages/CombatMessages.cpp @@ -0,0 +1,415 @@ +#include "CombatMessages.h" + +#include "BitStreamUtils.h" +#include "ControllablePhysicsComponent.h" +#include "CppScripts.h" +#include "DestroyableComponent.h" +#include "eBubbleType.h" +#include "eKillType.h" +#include "eReplicaComponentType.h" +#include "eStateChangeType.h" +#include "Entity.h" +#include "EntityManager.h" +#include "Game.h" +#include "Logger.h" +#include "PossessableComponent.h" +#include "RacingControlComponent.h" +#include "dZoneManager.h" + +namespace GameMessages { + void Die::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bClientDeath); + bitStream.Write(bSpawnLoot); + BitStreamUtils::WriteLengthPrefixed(bitStream, deathType); + bitStream.Write(directionRelative_AngleXZ); + bitStream.Write(directionRelative_AngleY); + bitStream.Write(directionRelative_Force); + BitStreamUtils::WriteOptional(bitStream, killType, eKillType::VIOLENT); + bitStream.Write(killerID); + BitStreamUtils::WriteOptional(bitStream, lootOwnerID, LWOOBJID_EMPTY); + } + + bool Die::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bClientDeath)); + VALIDATE_READ(bitStream.Read(bSpawnLoot)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, deathType)); + VALIDATE_READ(bitStream.Read(directionRelative_AngleXZ)); + VALIDATE_READ(bitStream.Read(directionRelative_AngleY)); + VALIDATE_READ(bitStream.Read(directionRelative_Force)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, killType, eKillType::VIOLENT)); + VALIDATE_READ(bitStream.Read(killerID)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, lootOwnerID, LWOOBJID_EMPTY)); + return true; + } + + void RequestDie::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bClientDeath); + bitStream.Write(bSpawnLoot); + BitStreamUtils::WriteLengthPrefixed(bitStream, deathType); + bitStream.Write(directionRelative_AngleXZ); + bitStream.Write(directionRelative_AngleY); + bitStream.Write(directionRelative_Force); + BitStreamUtils::WriteOptional(bitStream, killType, eKillType::VIOLENT); + bitStream.Write(killerID); + BitStreamUtils::WriteOptional(bitStream, lootOwnerID, LWOOBJID_EMPTY); + } + + bool RequestDie::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bClientDeath)); + VALIDATE_READ(bitStream.Read(bSpawnLoot)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, deathType)); + VALIDATE_READ(bitStream.Read(directionRelative_AngleXZ)); + VALIDATE_READ(bitStream.Read(directionRelative_AngleY)); + VALIDATE_READ(bitStream.Read(directionRelative_Force)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, killType, eKillType::VIOLENT)); + VALIDATE_READ(bitStream.Read(killerID)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, lootOwnerID, LWOOBJID_EMPTY)); + return true; + } + + void RequestDie::Handle(Entity& entityRef, const SystemAddress& sysAddr) { + Entity* entity = &entityRef; + auto* zoneController = Game::zoneManager->GetZoneControlObject(); + + auto* racingControlComponent = zoneController->GetComponent(); + + LOG("Got die request: %i", entity->GetLOT()); + + if (racingControlComponent != nullptr) { + auto* possessableComponent = entity->GetComponent(); + + if (possessableComponent != nullptr) { + entity = Game::entityManager->GetEntity(possessableComponent->GetPossessor()); + + if (entity == nullptr) { + return; + } + } + + racingControlComponent->OnRequestDie(entity); + } else { + auto* destroyableComponent = entity->GetComponent(); + + if (!destroyableComponent) return; + + destroyableComponent->Smash(killerID, killType, deathType); + } + } + + void RequestSmashPlayer::Serialize(RakNet::BitStream& bitStream) const {} + + bool RequestSmashPlayer::Deserialize(RakNet::BitStream& bitStream) { + return true; + } + + void RequestSmashPlayer::Handle(Entity& entity, const SystemAddress& sysAddr) { + entity.Smash(entity.GetObjectID()); + } + + void RequestResurrect::Serialize(RakNet::BitStream& bitStream) const {} + + bool RequestResurrect::Deserialize(RakNet::BitStream& bitStream) { + return true; + } + + void RequestResurrect::Handle(Entity& entity, const SystemAddress& sysAddr) { + DestroyableComponent::Resurrect(entity); + } + + void Resurrect::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bRezImmediately); + } + + bool Resurrect::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bRezImmediately)); + return true; + } + + void Resurrect::Handle(Entity& entityRef, const SystemAddress& sysAddr) { + Entity* entity = &entityRef; + Entity* zoneControl = Game::entityManager->GetZoneControlEntity(); + if (zoneControl) { + zoneControl->GetScript()->OnPlayerResurrected(zoneControl, entity); + } + + std::vector scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY); + for (Entity* scriptEntity : scriptedActs) { + if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds + scriptEntity->GetScript()->OnPlayerResurrected(scriptEntity, entity); + } + } + } + + void Smash::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bIgnoreObjectVisibility); + bitStream.Write(force); + bitStream.Write(ghostOpacity); + bitStream.Write(killerID); + } + + bool Smash::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bIgnoreObjectVisibility)); + VALIDATE_READ(bitStream.Read(force)); + VALIDATE_READ(bitStream.Read(ghostOpacity)); + VALIDATE_READ(bitStream.Read(killerID)); + return true; + } + + void UnSmash::Serialize(RakNet::BitStream& bitStream) const { + // Both fields are optional with a default, like the client's GameMessage::UnSmash::Serialize. + BitStreamUtils::WriteOptional(bitStream, builderID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, duration, 3.0f); + } + + bool UnSmash::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, builderID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, duration, 3.0f)); + return true; + } + + void SetResurrectRestoreValues::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, iArmorRestore, -1); + BitStreamUtils::WriteOptional(bitStream, iHealthRestore, -1); + BitStreamUtils::WriteOptional(bitStream, iImaginationRestore, -1); + } + + bool SetResurrectRestoreValues::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iArmorRestore, -1)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iHealthRestore, -1)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iImaginationRestore, -1)); + return true; + } + + void SetPlayerAllowedRespawn::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(dontPromptForRespawn); + } + + bool SetPlayerAllowedRespawn::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(dontPromptForRespawn)); + return true; + } + + void Knockback::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, Caster, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, Originator, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, iKnockBackTimeMS, 0); + bitStream.Write(vector); + } + + bool Knockback::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, Caster, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, Originator, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, iKnockBackTimeMS, 0)); + VALIDATE_READ(bitStream.Read(vector)); + return true; + } + + void SetStunned::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, Originator, LWOOBJID_EMPTY); + bitStream.Write(StateChangeType); + bitStream.Write(bCantAttack); + bitStream.Write(bCantAttackOutChangeWasApplied); + bitStream.Write(bCantEquip); + bitStream.Write(bCantEquipOutChangeWasApplied); + bitStream.Write(bCantInteract); + bitStream.Write(bCantInteractOutChangeWasApplied); + bitStream.Write(bCantJump); + bitStream.Write(bCantJumpOutChangeWasApplied); + bitStream.Write(bCantMove); + bitStream.Write(bCantMoveOutChangeWasApplied); + bitStream.Write(bCantTurn); + bitStream.Write(bCantTurnOutChangeWasApplied); + bitStream.Write(bCantUseItem); + bitStream.Write(bCantUseItemOutChangeWasApplied); + bitStream.Write(bDontTerminateInteract); + bitStream.Write(bIgnoreImmunity); + } + + bool SetStunned::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, Originator, LWOOBJID_EMPTY)); + VALIDATE_READ(bitStream.Read(StateChangeType)); + VALIDATE_READ(bitStream.Read(bCantAttack)); + VALIDATE_READ(bitStream.Read(bCantAttackOutChangeWasApplied)); + VALIDATE_READ(bitStream.Read(bCantEquip)); + VALIDATE_READ(bitStream.Read(bCantEquipOutChangeWasApplied)); + VALIDATE_READ(bitStream.Read(bCantInteract)); + VALIDATE_READ(bitStream.Read(bCantInteractOutChangeWasApplied)); + VALIDATE_READ(bitStream.Read(bCantJump)); + VALIDATE_READ(bitStream.Read(bCantJumpOutChangeWasApplied)); + VALIDATE_READ(bitStream.Read(bCantMove)); + VALIDATE_READ(bitStream.Read(bCantMoveOutChangeWasApplied)); + VALIDATE_READ(bitStream.Read(bCantTurn)); + VALIDATE_READ(bitStream.Read(bCantTurnOutChangeWasApplied)); + VALIDATE_READ(bitStream.Read(bCantUseItem)); + VALIDATE_READ(bitStream.Read(bCantUseItemOutChangeWasApplied)); + VALIDATE_READ(bitStream.Read(bDontTerminateInteract)); + VALIDATE_READ(bitStream.Read(bIgnoreImmunity)); + return true; + } + + void SetStunImmunity::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, Caster, LWOOBJID_EMPTY); + bitStream.Write(StateChangeType); + bitStream.Write(bImmuneToStunAttack); + bitStream.Write(bImmuneToStunEquip); + bitStream.Write(bImmuneToStunInteract); + bitStream.Write(bImmuneToStunJump); + bitStream.Write(bImmuneToStunMove); + bitStream.Write(bImmuneToStunTurn); + bitStream.Write(bImmuneToStunUseItem); + } + + bool SetStunImmunity::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, Caster, LWOOBJID_EMPTY)); + VALIDATE_READ(bitStream.Read(StateChangeType)); + VALIDATE_READ(bitStream.Read(bImmuneToStunAttack)); + VALIDATE_READ(bitStream.Read(bImmuneToStunEquip)); + VALIDATE_READ(bitStream.Read(bImmuneToStunInteract)); + VALIDATE_READ(bitStream.Read(bImmuneToStunJump)); + VALIDATE_READ(bitStream.Read(bImmuneToStunMove)); + VALIDATE_READ(bitStream.Read(bImmuneToStunTurn)); + VALIDATE_READ(bitStream.Read(bImmuneToStunUseItem)); + return true; + } + + void SetStatusImmunity::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(StateChangeType); + bitStream.Write(bImmuneToBasicAttack); + bitStream.Write(bImmuneToDOT); + bitStream.Write(bImmuneToKnockback); + bitStream.Write(bImmuneToInterrupt); + bitStream.Write(bImmuneToSpeed); + bitStream.Write(bImmuneToImaginationGain); + bitStream.Write(bImmuneToImaginationLoss); + bitStream.Write(bImmuneToQuickbuildInterrupt); + bitStream.Write(bImmuneToPullToPoint); + } + + bool SetStatusImmunity::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(StateChangeType)); + VALIDATE_READ(bitStream.Read(bImmuneToBasicAttack)); + VALIDATE_READ(bitStream.Read(bImmuneToDOT)); + VALIDATE_READ(bitStream.Read(bImmuneToKnockback)); + VALIDATE_READ(bitStream.Read(bImmuneToInterrupt)); + VALIDATE_READ(bitStream.Read(bImmuneToSpeed)); + VALIDATE_READ(bitStream.Read(bImmuneToImaginationGain)); + VALIDATE_READ(bitStream.Read(bImmuneToImaginationLoss)); + VALIDATE_READ(bitStream.Read(bImmuneToQuickbuildInterrupt)); + VALIDATE_READ(bitStream.Read(bImmuneToPullToPoint)); + return true; + } + + void AddBuff::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bAddedByTeammate); + bitStream.Write(bApplyOnTeammates); + bitStream.Write(bCancelOnDamaged); + bitStream.Write(bCancelOnDeath); + bitStream.Write(bCancelOnLogOut); + bitStream.Write(bCancelOnMove); + bitStream.Write(bCancelOnRemoveBuff); + bitStream.Write(bCancelOnUI); + bitStream.Write(bCancelOnUnEquip); + bitStream.Write(bCancelOnZone); + bitStream.Write(bIgnoreImmunities); + bitStream.Write(bIsImmunity); + bitStream.Write(bUseRefCount); + BitStreamUtils::WriteOptional(bitStream, i64AddedBy, LWOOBJID_EMPTY); + bitStream.Write(uiBuffID); + BitStreamUtils::WriteOptional(bitStream, uiDurationMS, 0u); + } + + bool AddBuff::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bAddedByTeammate)); + VALIDATE_READ(bitStream.Read(bApplyOnTeammates)); + VALIDATE_READ(bitStream.Read(bCancelOnDamaged)); + VALIDATE_READ(bitStream.Read(bCancelOnDeath)); + VALIDATE_READ(bitStream.Read(bCancelOnLogOut)); + VALIDATE_READ(bitStream.Read(bCancelOnMove)); + VALIDATE_READ(bitStream.Read(bCancelOnRemoveBuff)); + VALIDATE_READ(bitStream.Read(bCancelOnUI)); + VALIDATE_READ(bitStream.Read(bCancelOnUnEquip)); + VALIDATE_READ(bitStream.Read(bCancelOnZone)); + VALIDATE_READ(bitStream.Read(bIgnoreImmunities)); + VALIDATE_READ(bitStream.Read(bIsImmunity)); + VALIDATE_READ(bitStream.Read(bUseRefCount)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64AddedBy, LWOOBJID_EMPTY)); + VALIDATE_READ(bitStream.Read(uiBuffID)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, uiDurationMS, 0u)); + return true; + } + + void RemoveBuff::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bFromRemoveBehavior); + bitStream.Write(bFromUnEquip); + bitStream.Write(bRemoveImmunity); + bitStream.Write(uiBuffID); + } + + bool RemoveBuff::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bFromRemoveBehavior)); + VALIDATE_READ(bitStream.Read(bFromUnEquip)); + VALIDATE_READ(bitStream.Read(bRemoveImmunity)); + VALIDATE_READ(bitStream.Read(uiBuffID)); + return true; + } + + void AddRunSpeedModifier::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, i64Caster, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, uiModifier, 500u); + } + + bool AddRunSpeedModifier::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, i64Caster, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, uiModifier, 500u)); + return true; + } + + void RemoveRunSpeedModifier::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, uiModifier, 500u); + } + + bool RemoveRunSpeedModifier::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, uiModifier, 500u)); + return true; + } + + void ActivateBubbleBuff::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bSpecialAnims); + BitStreamUtils::WriteLengthPrefixed(bitStream, wszType); + } + + bool ActivateBubbleBuff::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bSpecialAnims)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, wszType)); + return true; + } + + void ActivateBubbleBuff::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto bubbleType = eBubbleType::DEFAULT; + if (wszType == u"skunk") bubbleType = eBubbleType::SKUNK; + else if (wszType == u"energy") bubbleType = eBubbleType::ENERGY; + + auto controllablePhysicsComponent = entity.GetComponent(); + if (controllablePhysicsComponent) controllablePhysicsComponent->ActivateBubbleBuff(bubbleType, bSpecialAnims); + } + + void DeactivateBubbleBuff::Serialize(RakNet::BitStream& bitStream) const {} + + bool DeactivateBubbleBuff::Deserialize(RakNet::BitStream& bitStream) { + return true; + } + + void DeactivateBubbleBuff::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto controllablePhysicsComponent = entity.GetComponent(); + if (controllablePhysicsComponent) controllablePhysicsComponent->DeactivateBubbleBuff(); + DeactivateBubbleBuffFromServer deactivate; + deactivate.target = entity.GetObjectID(); + deactivate.Send(entity.GetSystemAddress()); + } + + void DeactivateBubbleBuffFromServer::Serialize(RakNet::BitStream& bitStream) const {} + + bool DeactivateBubbleBuffFromServer::Deserialize(RakNet::BitStream& bitStream) { + return true; + } +} diff --git a/dGame/dGameMessages/CombatMessages.h b/dGame/dGameMessages/CombatMessages.h new file mode 100644 index 000000000..2e0d2d2e0 --- /dev/null +++ b/dGame/dGameMessages/CombatMessages.h @@ -0,0 +1,278 @@ +#ifndef COMBATMESSAGES_H +#define COMBATMESSAGES_H + +#include "GameMessages.h" +#include "NiPoint3.h" + +#include + +enum class eKillType : uint32_t; +enum class eStateChangeType : uint32_t; + +// Game messages for combat: death and resurrection, stuns and immunities, buffs, knockback and the bubble buff. +// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order. +// Unless noted, server -> client messages are sent with Send (UNASSIGNED_SYSTEM_ADDRESS broadcasts), as before. +namespace GameMessages { + // Server -> client, broadcast. + struct Die : public NetGameMsg { + Die() : NetGameMsg(MessageType::Game::DIE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bClientDeath{ false }; + bool bSpawnLoot{ true }; + std::u16string deathType{}; // u32 length prefixed + float directionRelative_AngleXZ{}; + float directionRelative_AngleY{}; + float directionRelative_Force{}; + eKillType killType{ 0 }; // optional, eKillType::VIOLENT when unset + LWOOBJID killerID{}; + LWOOBJID lootOwnerID{ LWOOBJID_EMPTY }; // optional + }; + + // Client -> server. Read with the same layout as Die, as DLU always has. + struct RequestDie : public NetGameMsg { + RequestDie() : NetGameMsg(MessageType::Game::REQUEST_DIE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + bool bClientDeath{ false }; + bool bSpawnLoot{ false }; + std::u16string deathType{}; // u32 length prefixed + float directionRelative_AngleXZ{}; + float directionRelative_AngleY{}; + float directionRelative_Force{}; + eKillType killType{ 0 }; // optional, eKillType::VIOLENT when unset + LWOOBJID killerID{}; + LWOOBJID lootOwnerID{ LWOOBJID_EMPTY }; // optional + }; + + // Client -> server. No payload. + struct RequestSmashPlayer : public NetGameMsg { + RequestSmashPlayer() : NetGameMsg(MessageType::Game::REQUEST_SMASH_PLAYER) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + }; + + // Client -> server. The player pressed respawn. No payload. + struct RequestResurrect : public NetGameMsg { + RequestResurrect() : NetGameMsg(MessageType::Game::REQUEST_RESURRECT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + }; + + // Server -> client (broadcast) when an entity comes back to life, and client -> server when the player has + // resurrected. + struct Resurrect : public NetGameMsg { + Resurrect() : NetGameMsg(MessageType::Game::RESURRECT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + bool bRezImmediately{ false }; + }; + + // Server -> client. Smashes the target into bricks without destroying it. + struct Smash : public NetGameMsg { + Smash() : NetGameMsg(MessageType::Game::SMASH) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bIgnoreObjectVisibility{ false }; + float force{}; + float ghostOpacity{}; + LWOOBJID killerID{}; + }; + + // Server -> client. Rebuilds a smashed target over duration seconds. + struct UnSmash : public NetGameMsg { + UnSmash() : NetGameMsg(MessageType::Game::UN_SMASH) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID builderID{ LWOOBJID_EMPTY }; // optional + float duration{ 3.0f }; // optional + }; + + // Server -> client, broadcast. + struct SetResurrectRestoreValues : public NetGameMsg { + SetResurrectRestoreValues() : NetGameMsg(MessageType::Game::SET_RESURRECT_RESTORE_VALUES) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + int32_t iArmorRestore{ -1 }; // optional + int32_t iHealthRestore{ -1 }; // optional + int32_t iImaginationRestore{ -1 }; // optional + }; + + // Server -> client. Sent with SendToClient: the old function never broadcast. + struct SetPlayerAllowedRespawn : public NetGameMsg { + SetPlayerAllowedRespawn() : NetGameMsg(MessageType::Game::SET_PLAYER_ALLOWED_RESPAWN) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool dontPromptForRespawn{}; + }; + + // Server -> client, broadcast. + struct Knockback : public NetGameMsg { + Knockback() : NetGameMsg(MessageType::Game::KNOCKBACK) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID Caster{ LWOOBJID_EMPTY }; // optional + LWOOBJID Originator{ LWOOBJID_EMPTY }; // optional + int32_t iKnockBackTimeMS{ 0 }; // optional + NiPoint3 vector{}; + }; + + // Server -> client. + struct SetStunned : public NetGameMsg { + SetStunned() : NetGameMsg(MessageType::Game::SET_STUNNED) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID Originator{ LWOOBJID_EMPTY }; // optional + eStateChangeType StateChangeType{}; + bool bCantAttack{ false }; + bool bCantAttackOutChangeWasApplied{ false }; + bool bCantEquip{ false }; + bool bCantEquipOutChangeWasApplied{ false }; + bool bCantInteract{ false }; + bool bCantInteractOutChangeWasApplied{ false }; + bool bCantJump{ false }; + bool bCantJumpOutChangeWasApplied{ false }; + bool bCantMove{ false }; + bool bCantMoveOutChangeWasApplied{ false }; + bool bCantTurn{ false }; + bool bCantTurnOutChangeWasApplied{ false }; + bool bCantUseItem{ false }; + bool bCantUseItemOutChangeWasApplied{ false }; + bool bDontTerminateInteract{ false }; + bool bIgnoreImmunity{ true }; + }; + + // Server -> client. + struct SetStunImmunity : public NetGameMsg { + SetStunImmunity() : NetGameMsg(MessageType::Game::SET_STUN_IMMUNITY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID Caster{ LWOOBJID_EMPTY }; // optional + eStateChangeType StateChangeType{}; + bool bImmuneToStunAttack{ false }; + bool bImmuneToStunEquip{ false }; + bool bImmuneToStunInteract{ false }; + bool bImmuneToStunJump{ false }; + bool bImmuneToStunMove{ false }; + bool bImmuneToStunTurn{ false }; + bool bImmuneToStunUseItem{ false }; + }; + + // Server -> client. + // NOTE: DLU writes the flags in this order; the client reads them in alphabetical order + // (GameMessage::SetStatusImmunity::Serialize @ 00d8f140 in 1.10.64). Kept as DLU sent it in this conversion. + struct SetStatusImmunity : public NetGameMsg { + SetStatusImmunity() : NetGameMsg(MessageType::Game::SET_STATUS_IMMUNITY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + eStateChangeType StateChangeType{}; + bool bImmuneToBasicAttack{ false }; + bool bImmuneToDOT{ false }; + bool bImmuneToKnockback{ false }; + bool bImmuneToInterrupt{ false }; + bool bImmuneToSpeed{ false }; + bool bImmuneToImaginationGain{ false }; + bool bImmuneToImaginationLoss{ false }; + bool bImmuneToQuickbuildInterrupt{ false }; + bool bImmuneToPullToPoint{ false }; + }; + + // Server -> client. + struct AddBuff : public NetGameMsg { + AddBuff() : NetGameMsg(MessageType::Game::ADD_BUFF) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bAddedByTeammate{ false }; + bool bApplyOnTeammates{ false }; + bool bCancelOnDamaged{ false }; + bool bCancelOnDeath{ false }; + bool bCancelOnLogOut{ false }; + bool bCancelOnMove{ false }; + bool bCancelOnRemoveBuff{ false }; + bool bCancelOnUI{ false }; + bool bCancelOnUnEquip{ false }; + bool bCancelOnZone{ false }; + bool bIgnoreImmunities{ false }; + bool bIsImmunity{ false }; + bool bUseRefCount{ false }; + LWOOBJID i64AddedBy{ LWOOBJID_EMPTY }; // optional + uint32_t uiBuffID{}; + uint32_t uiDurationMS{ 0 }; // optional + }; + + // Server -> client, broadcast. + struct RemoveBuff : public NetGameMsg { + RemoveBuff() : NetGameMsg(MessageType::Game::REMOVE_BUFF) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + // DLU always sends false: setting it makes the client ignore the message. + bool bFromRemoveBehavior{ false }; + bool bFromUnEquip{ false }; + bool bRemoveImmunity{ false }; + uint32_t uiBuffID{}; + }; + + // Server -> client. + struct AddRunSpeedModifier : public NetGameMsg { + AddRunSpeedModifier() : NetGameMsg(MessageType::Game::ADD_RUN_SPEED_MODIFIER) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID i64Caster{ LWOOBJID_EMPTY }; // optional + uint32_t uiModifier{ 500 }; // optional + }; + + // Server -> client. + struct RemoveRunSpeedModifier : public NetGameMsg { + RemoveRunSpeedModifier() : NetGameMsg(MessageType::Game::REMOVE_RUN_SPEED_MODIFIER) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + uint32_t uiModifier{ 500 }; // optional + }; + + // Client -> server. The player went under water (or into skunk stink) and wants the bubble helmet. + struct ActivateBubbleBuff : public NetGameMsg { + ActivateBubbleBuff() : NetGameMsg(MessageType::Game::ACTIVATE_BUBBLE_BUFF) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + bool bSpecialAnims{}; + std::u16string wszType{}; // u32 length prefixed + }; + + // Client -> server. No payload. + struct DeactivateBubbleBuff : public NetGameMsg { + DeactivateBubbleBuff() : NetGameMsg(MessageType::Game::DECTIVATE_BUBBLE_BUFF) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + }; + + // Server -> client. No payload. + struct DeactivateBubbleBuffFromServer : public NetGameMsg { + DeactivateBubbleBuffFromServer() : NetGameMsg(MessageType::Game::DEACTIVATE_BUBBLE_BUFF_FROM_SERVER) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; +}; + +#endif // COMBATMESSAGES_H diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 9523f353f..89fb16d01 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -30,6 +30,7 @@ #include "BuildingMessages.h" #include "RacingMessages.h" #include "MissionMessages.h" +#include "CombatMessages.h" #include "SkillMessages.h" #include "TradeMessages.h" #include "VendorMessages.h" @@ -159,6 +160,14 @@ namespace { { START_SKILL, []() { return std::make_unique(); } }, { SYNC_SKILL, []() { return std::make_unique(); } }, { REQUEST_SERVER_PROJECTILE_IMPACT, []() { return std::make_unique(); } }, + + // Combat + { REQUEST_DIE, []() { return std::make_unique(); } }, + { REQUEST_SMASH_PLAYER, []() { return std::make_unique(); } }, + { REQUEST_RESURRECT, []() { return std::make_unique(); } }, + { RESURRECT, []() { return std::make_unique(); } }, + { ACTIVATE_BUBBLE_BUFF, []() { return std::make_unique(); } }, + { DECTIVATE_BUBBLE_BUFF, []() { return std::make_unique(); } }, }; }; @@ -352,19 +361,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System break; } - case MessageType::Game::RESURRECT: { - GameMessages::HandleResurrect(inStream, entity); - break; - } - - case MessageType::Game::REQUEST_RESURRECT: { - GameMessages::SendResurrect(entity); - break; - } - case MessageType::Game::REQUEST_SMASH_PLAYER: - entity->Smash(entity->GetObjectID()); - break; - case MessageType::Game::REBUILD_CANCEL: GameMessages::HandleQuickBuildCancel(inStream, entity); break; @@ -382,10 +378,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System GameMessages::HandleAcknowledgePossession(inStream, entity, sysAddr); break; - case MessageType::Game::REQUEST_DIE: - GameMessages::HandleRequestDie(inStream, entity, sysAddr); - break; - // SG case MessageType::Game::UPDATE_SHOOTING_GALLERY_ROTATION: GameMessages::HandleUpdateShootingGalleryRotation(inStream, entity, sysAddr); break; @@ -431,12 +423,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System case MessageType::Game::DISMOUNT_COMPLETE: GameMessages::HandleDismountComplete(inStream, entity, sysAddr); break; - case MessageType::Game::DECTIVATE_BUBBLE_BUFF: - GameMessages::HandleDeactivateBubbleBuff(inStream, entity); - break; - case MessageType::Game::ACTIVATE_BUBBLE_BUFF: - GameMessages::HandleActivateBubbleBuff(inStream, entity); - break; case MessageType::Game::ZONE_SUMMARY_DISMISSED: GameMessages::HandleZoneSummaryDismissed(inStream, entity); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 7b7a83363..82bca7225 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -172,17 +172,6 @@ void GameMessages::SendPlayerReady(Entity* entity, const SystemAddress& sysAddr) SEND_PACKET; } -void GameMessages::SendPlayerAllowedRespawn(LWOOBJID entityID, bool doNotPromptRespawn, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entityID); - bitStream.Write(MessageType::Game::SET_PLAYER_ALLOWED_RESPAWN); - bitStream.Write(doNotPromptRespawn); - - SEND_PACKET; -} - void GameMessages::SendInvalidZoneTransferList(Entity* entity, const SystemAddress& sysAddr, const std::u16string& feedbackURL, const std::u16string& invalidMapTransferList, bool feedbackOnExit, bool feedbackOnInvalidTransfer) { CBITSTREAM; CMSGHEADER; @@ -208,28 +197,6 @@ void GameMessages::SendInvalidZoneTransferList(Entity* entity, const SystemAddre SEND_PACKET; } -void GameMessages::SendKnockback(const LWOOBJID& objectID, const LWOOBJID& caster, const LWOOBJID& originator, int knockBackTimeMS, const NiPoint3& vector) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectID); - bitStream.Write(MessageType::Game::KNOCKBACK); - - bool casterFlag = caster != LWOOBJID_EMPTY; - bool originatorFlag = originator != LWOOBJID_EMPTY; - bool knockBackTimeMSFlag = knockBackTimeMS != 0; - - bitStream.Write(casterFlag); - if (casterFlag) bitStream.Write(caster); - bitStream.Write(originatorFlag); - if (originatorFlag) bitStream.Write(originator); - bitStream.Write(knockBackTimeMSFlag); - if (knockBackTimeMSFlag) bitStream.Write(knockBackTimeMS); - bitStream.Write(vector); - - SEND_PACKET_BROADCAST; -} - void GameMessages::SendPlayerSetCameraCyclingMode(const LWOOBJID& objectID, const SystemAddress& sysAddr, bool bAllowCyclingWhileDeadOnly, eCyclingMode cyclingMode) { CBITSTREAM; @@ -452,70 +419,6 @@ void GameMessages::SendTerminateInteraction(const LWOOBJID& objectID, eTerminate SEND_PACKET_BROADCAST; } -void GameMessages::SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, const LWOOBJID& lootOwnerID, eKillType killType, std::u16string deathType, float directionRelative_AngleY, float directionRelative_AngleXZ, float directionRelative_Force, bool bClientDeath, bool bSpawnLoot) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::DIE); - - bitStream.Write(bClientDeath); - bitStream.Write(bSpawnLoot); - bitStream.Write(deathType.size()); - bitStream.Write(deathType); - bitStream.Write(directionRelative_AngleXZ); - bitStream.Write(directionRelative_AngleY); - bitStream.Write(directionRelative_Force); - - bitStream.Write(killType != eKillType::VIOLENT); - if (killType != eKillType::VIOLENT) bitStream.Write(killType); - - bitStream.Write(killerID); - bitStream.Write(lootOwnerID != LWOOBJID_EMPTY); - if (lootOwnerID != LWOOBJID_EMPTY) { - bitStream.Write(lootOwnerID); - } - - SEND_PACKET_BROADCAST; -} - -void GameMessages::SendDie(Entity* entity, const LWOOBJID& killerID, const LWOOBJID& lootOwnerID, bool bDieAccepted, eKillType killType, std::u16string deathType, float directionRelative_AngleY, float directionRelative_AngleXZ, float directionRelative_Force, bool bClientDeath, bool bSpawnLoot, float coinSpawnTime) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - - bitStream.Write(MessageType::Game::DIE); - - bitStream.Write(bClientDeath); - bitStream.Write(bSpawnLoot); - - //bitStream.Write(coinSpawnTime != -1.0f); - //if (coinSpawnTime != -1.0f) bitStream.Write(coinSpawnTime); - - uint32_t deathTypeLength = deathType.size(); - bitStream.Write(deathTypeLength); - for (uint32_t k = 0; k < deathTypeLength; k++) { - bitStream.Write(deathType[k]); - } - - bitStream.Write(directionRelative_AngleXZ); - bitStream.Write(directionRelative_AngleY); - bitStream.Write(directionRelative_Force); - - bitStream.Write(killType != eKillType::VIOLENT); - if (killType != eKillType::VIOLENT) bitStream.Write(killType); - - bitStream.Write(killerID); - - bitStream.Write(lootOwnerID != LWOOBJID_EMPTY); - if (lootOwnerID != LWOOBJID_EMPTY) { - bitStream.Write(lootOwnerID); - } - - SEND_PACKET_BROADCAST; -} - void GameMessages::SendSetJetPackMode(Entity* entity, bool use, bool bypassChecks, bool doHover, int effectID, float airspeed, float maxAirspeed, float verticalVelocity, int warningEffectID) { /* historical jamesster jetpack values if (bIsJamessterPhysics) { @@ -553,44 +456,6 @@ void GameMessages::SendSetJetPackMode(Entity* entity, bool use, bool bypassCheck SEND_PACKET_BROADCAST; } -void GameMessages::SendResurrect(Entity* entity) { - // Restore the players health after the animation for respawning has finished. - // This is when the health appered back in live, not immediately upon requesting respawn - // Add a half second in case someone decides to cheat and move during the death animation - // and just make sure the client has time to be ready. - constexpr float respawnTime = 3.66700005531311f + 0.5f; - entity->AddCallbackTimer(respawnTime, [=]() { - GameMessages::PlayerResurrectionFinished msg; - entity->NotifyPlayerResurrectionFinished(msg); - auto* destroyableComponent = entity->GetComponent(); - - if (destroyableComponent != nullptr && entity->GetLOT() == 1) { - destroyableComponent->SetIsDead(false); - auto* levelComponent = entity->GetComponent(); - if (levelComponent) { - int32_t healthToRestore = levelComponent->GetLevel() >= 45 ? 8 : 4; - if (healthToRestore > destroyableComponent->GetMaxHealth()) healthToRestore = destroyableComponent->GetMaxHealth(); - destroyableComponent->SetHealth(healthToRestore); - - int32_t imaginationToRestore = levelComponent->GetLevel() >= 45 ? 20 : 6; - if (imaginationToRestore > destroyableComponent->GetMaxImagination()) imaginationToRestore = destroyableComponent->GetMaxImagination(); - destroyableComponent->SetImagination(imaginationToRestore); - } - } - }); - - CBITSTREAM; - CMSGHEADER; - - bool bRezImmediately = false; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::RESURRECT); - bitStream.Write(bRezImmediately); - - SEND_PACKET_BROADCAST; -} - void GameMessages::SendSetNetworkScriptVar(Entity* entity, const SystemAddress& sysAddr, std::string data) { CBITSTREAM; CMSGHEADER; @@ -1062,149 +927,6 @@ void GameMessages::SendLockNodeRotation(Entity* entity, std::string nodeName) { SEND_PACKET_BROADCAST; } -void GameMessages::SendSmash(Entity* entity, float force, float ghostOpacity, LWOOBJID killerID, bool ignoreObjectVisibility) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::SMASH); - - bitStream.Write(ignoreObjectVisibility); - bitStream.Write(force); - bitStream.Write(ghostOpacity); - bitStream.Write(killerID); - - SEND_PACKET_BROADCAST; -} - -void GameMessages::SendUnSmash(Entity* entity, LWOOBJID builderID, float duration) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::UN_SMASH); - - bitStream.Write(builderID != LWOOBJID_EMPTY); - if (builderID != LWOOBJID_EMPTY) bitStream.Write(builderID); - - bitStream.Write(duration != 3.0f); - if (duration != 3.0f) bitStream.Write(duration); - - SEND_PACKET_BROADCAST; -} - -void GameMessages::SendSetStunned(LWOOBJID objectId, eStateChangeType stateChangeType, const SystemAddress& sysAddr, - LWOOBJID originator, bool bCantAttack, bool bCantEquip, - bool bCantInteract, bool bCantJump, bool bCantMove, bool bCantTurn, - bool bCantUseItem, bool bDontTerminateInteract, bool bIgnoreImmunity, - bool bCantAttackOutChangeWasApplied, bool bCantEquipOutChangeWasApplied, - bool bCantInteractOutChangeWasApplied, bool bCantJumpOutChangeWasApplied, - bool bCantMoveOutChangeWasApplied, bool bCantTurnOutChangeWasApplied, - bool bCantUseItemOutChangeWasApplied) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::SET_STUNNED); - - bitStream.Write(originator != LWOOBJID_EMPTY); - if (originator != LWOOBJID_EMPTY) bitStream.Write(originator); - - bitStream.Write(stateChangeType); - - bitStream.Write(bCantAttack); - bitStream.Write(bCantAttackOutChangeWasApplied); - - bitStream.Write(bCantEquip); - bitStream.Write(bCantEquipOutChangeWasApplied); - - bitStream.Write(bCantInteract); - bitStream.Write(bCantInteractOutChangeWasApplied); - - bitStream.Write(bCantJump); - bitStream.Write(bCantJumpOutChangeWasApplied); - - bitStream.Write(bCantMove); - bitStream.Write(bCantMoveOutChangeWasApplied); - - bitStream.Write(bCantTurn); - bitStream.Write(bCantTurnOutChangeWasApplied); - - bitStream.Write(bCantUseItem); - bitStream.Write(bCantUseItemOutChangeWasApplied); - - bitStream.Write(bDontTerminateInteract); - - bitStream.Write(bIgnoreImmunity); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendSetStunImmunity(LWOOBJID target, eStateChangeType state, const SystemAddress& sysAddr, - LWOOBJID originator, - bool bImmuneToStunAttack, - bool bImmuneToStunEquip, - bool bImmuneToStunInteract, - bool bImmuneToStunJump, - bool bImmuneToStunMove, - bool bImmuneToStunTurn, - bool bImmuneToStunUseItem) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(target); - bitStream.Write(MessageType::Game::SET_STUN_IMMUNITY); - - bitStream.Write(originator != LWOOBJID_EMPTY); - if (originator != LWOOBJID_EMPTY) bitStream.Write(originator); - - bitStream.Write(state); - - bitStream.Write(bImmuneToStunAttack); - bitStream.Write(bImmuneToStunEquip); - bitStream.Write(bImmuneToStunInteract); - bitStream.Write(bImmuneToStunJump); - bitStream.Write(bImmuneToStunMove); - bitStream.Write(bImmuneToStunTurn); - bitStream.Write(bImmuneToStunUseItem); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendSetStatusImmunity(LWOOBJID objectId, eStateChangeType state, const SystemAddress& sysAddr, - bool bImmuneToBasicAttack, - bool bImmuneToDamageOverTime, - bool bImmuneToKnockback, - bool bImmuneToInterrupt, - bool bImmuneToSpeed, - bool bImmuneToImaginationGain, - bool bImmuneToImaginationLoss, - bool bImmuneToQuickbuildInterrupt, - bool bImmuneToPullToPoint) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::SET_STATUS_IMMUNITY); - - bitStream.Write(state); - - bitStream.Write(bImmuneToBasicAttack); - bitStream.Write(bImmuneToDamageOverTime); - bitStream.Write(bImmuneToKnockback); - bitStream.Write(bImmuneToInterrupt); - bitStream.Write(bImmuneToSpeed); - bitStream.Write(bImmuneToImaginationGain); - bitStream.Write(bImmuneToImaginationLoss); - bitStream.Write(bImmuneToQuickbuildInterrupt); - bitStream.Write(bImmuneToPullToPoint); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - void GameMessages::SendOrientToAngle(LWOOBJID objectId, bool bRelativeToCurrent, float fAngle, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -1220,37 +942,6 @@ void GameMessages::SendOrientToAngle(LWOOBJID objectId, bool bRelativeToCurrent, } -void GameMessages::SendAddRunSpeedModifier(LWOOBJID objectId, LWOOBJID caster, uint32_t modifier, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::ADD_RUN_SPEED_MODIFIER); - - bitStream.Write(caster != LWOOBJID_EMPTY); - if (caster != LWOOBJID_EMPTY) bitStream.Write(caster); - - bitStream.Write(modifier != 500); - if (modifier != 500) bitStream.Write(modifier); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendRemoveRunSpeedModifier(LWOOBJID objectId, uint32_t modifier, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::REMOVE_RUN_SPEED_MODIFIER); - - bitStream.Write(modifier != 500); - if (modifier != 500) bitStream.Write(modifier); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - void GameMessages::SendNotifyObject(LWOOBJID objectId, LWOOBJID objIDSender, std::u16string name, const SystemAddress& sysAddr, int param1, int param2) { CBITSTREAM; CMSGHEADER; @@ -1308,21 +999,6 @@ void GameMessages::SendTeamPickupItem(LWOOBJID objectId, LWOOBJID lootID, LWOOBJ //Pets: -void GameMessages::SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::REMOVE_BUFF); - - bitStream.Write(false); // bFromRemoveBehavior but setting this to true makes the GM not do anything on the client? - bitStream.Write(fromUnEquip); - bitStream.Write(removeImmunity); - bitStream.Write(buffId); - - SEND_PACKET_BROADCAST; -} - void GameMessages::SendDisplayZoneSummary(LWOOBJID objectId, const SystemAddress& sysAddr, bool isPropertyMap, bool isZoneStart, LWOOBJID sender) { CBITSTREAM; CMSGHEADER; @@ -1426,74 +1102,6 @@ void GameMessages::HandleAcknowledgePossession(RakNet::BitStream& inStream, Enti -void GameMessages::HandleRequestDie(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - bool bClientDeath; - bool bSpawnLoot; - std::u16string deathType; - float directionRelativeAngleXZ; - float directionRelativeAngleY; - float directionRelativeForce; - eKillType killType = eKillType::VIOLENT; - LWOOBJID killerID; - LWOOBJID lootOwnerID = LWOOBJID_EMPTY; - - bClientDeath = inStream.ReadBit(); - bSpawnLoot = inStream.ReadBit(); - - uint32_t deathTypeLength = 0; - inStream.Read(deathTypeLength); - - for (size_t i = 0; i < deathTypeLength; i++) { - char16_t character; - inStream.Read(character); - - deathType.push_back(character); - } - - inStream.Read(directionRelativeAngleXZ); - inStream.Read(directionRelativeAngleY); - inStream.Read(directionRelativeForce); - - if (inStream.ReadBit()) { - inStream.Read(killType); - } - - inStream.Read(killerID); - - if (inStream.ReadBit()) { - inStream.Read(lootOwnerID); - } - - auto* zoneController = Game::zoneManager->GetZoneControlObject(); - - auto* racingControlComponent = zoneController->GetComponent(); - - LOG("Got die request: %i", entity->GetLOT()); - - if (racingControlComponent != nullptr) { - auto* possessableComponent = entity->GetComponent(); - - if (possessableComponent != nullptr) { - entity = Game::entityManager->GetEntity(possessableComponent->GetPossessor()); - - if (entity == nullptr) { - return; - } - } - - racingControlComponent->OnRequestDie(entity); - } else { - auto* destroyableComponent = entity->GetComponent(); - - if (!destroyableComponent) return; - - destroyableComponent->Smash(killerID, killType, deathType); - } -} - - - - void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -1506,71 +1114,6 @@ void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t r SEND_PACKET; } -void GameMessages::SendSetResurrectRestoreValues(Entity* targetEntity, int32_t armorRestore, int32_t healthRestore, int32_t imaginationRestore) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(targetEntity->GetObjectID()); - bitStream.Write(MessageType::Game::SET_RESURRECT_RESTORE_VALUES); - - bitStream.Write(armorRestore != -1); - if (armorRestore != -1) bitStream.Write(armorRestore); - - bitStream.Write(healthRestore != -1); - if (healthRestore != -1) bitStream.Write(healthRestore); - - bitStream.Write(imaginationRestore != -1); - if (imaginationRestore != -1) bitStream.Write(imaginationRestore); - - SEND_PACKET_BROADCAST; -} - - - - - - - - -void GameMessages::SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uint32_t buffID, uint32_t msDuration, - bool addImmunity, bool cancelOnDamaged, bool cancelOnDeath, bool cancelOnLogout, - bool cancelOnRemoveBuff, bool cancelOnUi, bool cancelOnUnequip, bool cancelOnZone, bool addedByTeammate, bool applyOnTeammates, - const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectID); - bitStream.Write(MessageType::Game::ADD_BUFF); - - bitStream.Write(addedByTeammate); // Added by teammate - bitStream.Write(applyOnTeammates); // Apply on teammates - bitStream.Write(cancelOnDamaged); - bitStream.Write(cancelOnDeath); - bitStream.Write(cancelOnLogout); - - bitStream.Write(false); // Cancel on move - bitStream.Write(cancelOnRemoveBuff); - bitStream.Write(cancelOnUi); - bitStream.Write(cancelOnUnequip); - bitStream.Write(cancelOnZone); - - bitStream.Write(false); // Ignore immunities - bitStream.Write(addImmunity); - bitStream.Write(false); // Use ref count - - bitStream.Write(casterID != LWOOBJID_EMPTY); - if (casterID != LWOOBJID_EMPTY) bitStream.Write(casterID); - - bitStream.Write(buffID); - - bitStream.Write(msDuration != 0); - if (msDuration != 0) bitStream.Write(msDuration); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - // NT //----------------------------------------------------------------------------------------------------------------------------------------------- @@ -1800,45 +1343,6 @@ void GameMessages::HandlePickupCurrency(RakNet::BitStream& inStream, Entity* ent } } -void GameMessages::HandleRequestDie(RakNet::BitStream& inStream, Entity* entity) { - LWOOBJID killerID; - LWOOBJID lootOwnerID; - bool bDieAccepted = false; - eKillType killType; - std::u16string deathType; - float directionRelative_AngleY; - float directionRelative_AngleXZ; - float directionRelative_Force; - bool bClientDeath = false; - bool bSpawnLoot = true; - float coinSpawnTime = -1.0f; - - inStream.Read(bClientDeath); - inStream.Read(bDieAccepted); - inStream.Read(bSpawnLoot); - - bool coinSpawnTimeIsDefault{}; - inStream.Read(coinSpawnTimeIsDefault); - if (coinSpawnTimeIsDefault != 0) inStream.Read(coinSpawnTime); - - /*uint32_t deathTypeLength = deathType.size(); - inStream.Read(deathTypeLength); - for (uint32_t k = 0; k < deathTypeLength; k++) { - inStream.Read(deathType[k]); - }*/ - - inStream.Read(directionRelative_AngleXZ); - inStream.Read(directionRelative_AngleY); - inStream.Read(directionRelative_Force); - - bool killTypeIsDefault{}; - inStream.Read(killTypeIsDefault); - if (killTypeIsDefault != 0) inStream.Read(killType); - - inStream.Read(lootOwnerID); - inStream.Read(killerID); -} - void GameMessages::SendSetGravityScale(const LWOOBJID& target, const float effectScale, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -1851,22 +1355,6 @@ void GameMessages::SendSetGravityScale(const LWOOBJID& target, const float effec SEND_PACKET; } -void GameMessages::HandleResurrect(RakNet::BitStream& inStream, Entity* entity) { - bool immediate = inStream.ReadBit(); - - Entity* zoneControl = Game::entityManager->GetZoneControlEntity(); - if (zoneControl) { - zoneControl->GetScript()->OnPlayerResurrected(zoneControl, entity); - } - - std::vector scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY); - for (Entity* scriptEntity : scriptedActs) { - if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds - scriptEntity->GetScript()->OnPlayerResurrected(scriptEntity, entity); - } - } -} - void GameMessages::HandleMatchRequest(RakNet::BitStream& inStream, Entity* entity) { LWOOBJID activator; uint32_t playerChoicesLen; @@ -2055,47 +1543,6 @@ void GameMessages::HandleUpdatePlayerStatistic(RakNet::BitStream& inStream, Enti } } -void GameMessages::HandleDeactivateBubbleBuff(RakNet::BitStream& inStream, Entity* entity) { - auto controllablePhysicsComponent = entity->GetComponent(); - if (controllablePhysicsComponent) controllablePhysicsComponent->DeactivateBubbleBuff(); - GameMessages::SendDeactivateBubbleBuffFromServer(entity->GetObjectID(), entity->GetSystemAddress()); -} - -void GameMessages::HandleActivateBubbleBuff(RakNet::BitStream& inStream, Entity* entity) { - bool specialAnimations; - if (!inStream.Read(specialAnimations)) return; - - std::u16string type = GeneralUtils::ReadWString(inStream); - auto bubbleType = eBubbleType::DEFAULT; - if (type == u"skunk") bubbleType = eBubbleType::SKUNK; - else if (type == u"energy") bubbleType = eBubbleType::ENERGY; - - auto controllablePhysicsComponent = entity->GetComponent(); - if (controllablePhysicsComponent) controllablePhysicsComponent->ActivateBubbleBuff(bubbleType, specialAnimations); -} - -void GameMessages::SendActivateBubbleBuffFromServer(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::ACTIVATE_BUBBLE_BUFF_FROM_SERVER); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendDeactivateBubbleBuffFromServer(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::DEACTIVATE_BUBBLE_BUFF_FROM_SERVER); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - void GameMessages::HandleZoneSummaryDismissed(RakNet::BitStream& inStream, Entity* entity) { LWOOBJID player_id; inStream.Read(player_id); @@ -2292,24 +1739,6 @@ namespace GameMessages { missionComponent->Progress(eMissionTaskType::INTERACT, interactedObject->GetLOT(), interactedObject->GetObjectID()); } - void Smash::Serialize(RakNet::BitStream& stream) const { - stream.Write(bIgnoreObjectVisibility); - stream.Write(force); - stream.Write(ghostCapacity); - stream.Write(killerID); - } - - void UnSmash::Serialize(RakNet::BitStream& stream) const { - // Both fields are optional with a default, like the client's GameMessage::UnSmash::Serialize. - BitStreamUtils::WriteOptional(stream, builderID, LWOOBJID_EMPTY); - BitStreamUtils::WriteOptional(stream, duration, 3.0f); - } - bool UnSmash::Deserialize(RakNet::BitStream& stream) { - VALIDATE_READ(BitStreamUtils::ReadOptional(stream, builderID, LWOOBJID_EMPTY)); - VALIDATE_READ(BitStreamUtils::ReadOptional(stream, duration, 3.0f)); - return true; - } - void PlayBehaviorSound::Serialize(RakNet::BitStream& stream) const { stream.Write(soundID != -1); if (soundID != -1) stream.Write(soundID); diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 1478cac01..e0d2c2b8e 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -132,9 +132,7 @@ namespace GameMessages { void SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender); void SendTeleport(const LWOOBJID& objectID, const NiPoint3& pos, const NiQuaternion& rot, const SystemAddress& sysAddr, bool bSetRotation = false); void SendPlayerReady(Entity* entity, const SystemAddress& sysAddr); - void SendPlayerAllowedRespawn(LWOOBJID entityID, bool doNotPromptRespawn, const SystemAddress& systemAddress); void SendInvalidZoneTransferList(Entity* entity, const SystemAddress& sysAddr, const std::u16string& feedbackURL, const std::u16string& invalidMapTransferList, bool feedbackOnExit, bool feedbackOnInvalidTransfer); - void SendKnockback(const LWOOBJID& objectID, const LWOOBJID& caster, const LWOOBJID& originator, int knockBackTimeMS, const NiPoint3& vector); void SendPlayerSetCameraCyclingMode(const LWOOBJID& objectID, const SystemAddress& sysAddr, bool bAllowCyclingWhileDeadOnly = true, eCyclingMode cyclingMode = eCyclingMode::ALLOW_CYCLE_TEAMMATES); @@ -161,13 +159,9 @@ namespace GameMessages { void AddActivityOwner(Entity* entity, LWOOBJID& ownerID); void SendTerminateInteraction(const LWOOBJID& objectID, eTerminateType type, const LWOOBJID& terminator); - void SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, const LWOOBJID& lootOwnerID, eKillType killType, std::u16string deathType, float directionRelative_AngleY, float directionRelative_AngleXZ, float directionRelative_Force, bool bClientDeath, bool bSpawnLoot); - void SendDie(Entity* entity, const LWOOBJID& killerID, const LWOOBJID& lootOwnerID, bool bDieAccepted, eKillType killType, std::u16string deathType, float directionRelative_AngleY, float directionRelative_AngleXZ, float directionRelative_Force, bool bClientDeath, bool bSpawnLoot, float coinSpawnTime); - void SendSetGravityScale(const LWOOBJID& target, const float effectScale, const SystemAddress& sysAddr); void SendSetJetPackMode(Entity* entity, bool use, bool bypassChecks = false, bool doHover = false, int effectID = -1, float airspeed = 10, float maxAirspeed = 15, float verticalVelocity = 1, int warningEffectID = -1); - void SendResurrect(Entity* entity); void SendSetNetworkScriptVar(Entity* entity, const SystemAddress& sysAddr, std::string data); void SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme); @@ -176,28 +170,6 @@ namespace GameMessages { void SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response); void SendMatchUpdate(Entity* entity, const SystemAddress& sysAddr, std::string data, eMatchUpdate type); - void SendSetResurrectRestoreValues(Entity* targetEntity, int32_t armorRestore, int32_t healthRestore, int32_t imaginationRestore); - - /** - * Sends a message to an Entity to smash itself, but not delete or destroy itself from the world - * - * @param entity The Entity that will smash itself into bricks - * @param force The force the Entity will be smashed with - * @param ghostOpacity The ghosting opacity of the smashed Entity - * @param killerID The Entity that invoked the smash, if none exists, this should be LWOOBJID_EMPTY - * @param ignoreObjectVisibility Whether or not to ignore the objects visibility - */ - void SendSmash(Entity* entity, float force, float ghostOpacity, LWOOBJID killerID, bool ignoreObjectVisibility = false); - - /** - * Sends a message to an Entity to UnSmash itself (aka rebuild itself over a duration) - * - * @param entity The Entity that will UnSmash itself - * @param builderID The Entity that invoked the build (LWOOBJID_EMPTY if none exists or invoked the rebuild) - * @param duration The duration for the Entity to rebuild over. 3 seconds by default - */ - void SendUnSmash(Entity* entity, LWOOBJID builderID = LWOOBJID_EMPTY, float duration = 3.0f); - // Rails stuff void SendSetRailMovement(const LWOOBJID& objectID, bool pathGoForward, std::u16string pathName, uint32_t pathStart, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS, @@ -219,59 +191,12 @@ namespace GameMessages { void SendNotifyClientFailedPrecondition(LWOOBJID objectId, const SystemAddress& sysAddr, const std::u16string& failedReason, int preconditionID); - void SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uint32_t buffID, uint32_t msDuration, - bool addImmunity = false, bool cancelOnDamaged = false, bool cancelOnDeath = true, - bool cancelOnLogout = false, bool cancelOnRemoveBuff = true, bool cancelOnUi = false, - bool cancelOnUnequip = false, bool cancelOnZone = false, bool addedByTeammate = false, bool applyOnTeammates = false, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); - void SendSetName(LWOOBJID objectID, std::u16string name, const SystemAddress& sysAddr); void SendLockNodeRotation(Entity* entity, std::string nodeName); - void SendSetStunned(LWOOBJID objectId, eStateChangeType stateChangeType, const SystemAddress& sysAddr, - LWOOBJID originator = LWOOBJID_EMPTY, bool bCantAttack = false, bool bCantEquip = false, - bool bCantInteract = false, bool bCantJump = false, bool bCantMove = false, bool bCantTurn = false, - bool bCantUseItem = false, bool bDontTerminateInteract = false, bool bIgnoreImmunity = true, - bool bCantAttackOutChangeWasApplied = false, bool bCantEquipOutChangeWasApplied = false, - bool bCantInteractOutChangeWasApplied = false, bool bCantJumpOutChangeWasApplied = false, - bool bCantMoveOutChangeWasApplied = false, bool bCantTurnOutChangeWasApplied = false, - bool bCantUseItemOutChangeWasApplied = false); - - void SendSetStunImmunity( - LWOOBJID target, - eStateChangeType state, - const SystemAddress& sysAddr, - LWOOBJID originator = LWOOBJID_EMPTY, - bool bImmuneToStunAttack = false, - bool bImmuneToStunEquip = false, - bool bImmuneToStunInteract = false, - bool bImmuneToStunJump = false, - bool bImmuneToStunMove = false, - bool bImmuneToStunTurn = false, - bool bImmuneToStunUseItem = false - ); - - void SendSetStatusImmunity( - LWOOBJID objectId, - eStateChangeType state, - const SystemAddress& sysAddr, - bool bImmuneToBasicAttack = false, - bool bImmuneToDamageOverTime = false, - bool bImmuneToKnockback = false, - bool bImmuneToInterrupt = false, - bool bImmuneToSpeed = false, - bool bImmuneToImaginationGain = false, - bool bImmuneToImaginationLoss = false, - bool bImmuneToQuickbuildInterrupt = false, - bool bImmuneToPullToPoint = false - ); - void SendOrientToAngle(LWOOBJID objectId, bool bRelativeToCurrent, float fAngle, const SystemAddress& sysAddr); - void SendAddRunSpeedModifier(LWOOBJID objectId, LWOOBJID caster, uint32_t modifier, const SystemAddress& sysAddr); - - void SendRemoveRunSpeedModifier(LWOOBJID objectId, uint32_t modifier, const SystemAddress& sysAddr); - void SendNotifyObject(LWOOBJID objectId, LWOOBJID objIDSender, std::u16string name, const SystemAddress& sysAddr, int param1 = 0, int param2 = 0); void HandleVerifyAck(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); @@ -311,7 +236,6 @@ namespace GameMessages { void HandleAcknowledgePossession(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); //Racing: - void HandleRequestDie(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); // SG: @@ -363,9 +287,7 @@ namespace GameMessages { void HandleQuickBuildCancel(RakNet::BitStream& inStream, Entity* entity); void HandleNotifyServerLevelProcessingComplete(RakNet::BitStream& inStream, Entity* entity); void HandlePickupCurrency(RakNet::BitStream& inStream, Entity* entity); - void HandleRequestDie(RakNet::BitStream& inStream, Entity* entity); void HandlePickupItem(RakNet::BitStream& inStream, Entity* entity); - void HandleResurrect(RakNet::BitStream& inStream, Entity* entity); void HandleModifyPlayerZoneStatistic(RakNet::BitStream& inStream, Entity* entity); void HandleUpdatePlayerStatistic(RakNet::BitStream& inStream, Entity* entity); @@ -373,16 +295,7 @@ namespace GameMessages { void HandleReportBug(RakNet::BitStream& inStream, Entity* entity); - void SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId); - // bubble - void HandleDeactivateBubbleBuff(RakNet::BitStream& inStream, Entity* entity); - - void HandleActivateBubbleBuff(RakNet::BitStream& inStream, Entity* entity); - - void SendActivateBubbleBuffFromServer(LWOOBJID objectId, const SystemAddress& sysAddr); - - void SendDeactivateBubbleBuffFromServer(LWOOBJID objectId, const SystemAddress& sysAddr); void HandleZoneSummaryDismissed(RakNet::BitStream& inStream, Entity* entity); @@ -505,27 +418,6 @@ namespace GameMessages { }; using RequestUseEvent = NetGameMsgEvent; - struct Smash : public NetGameMsg { - Smash() : NetGameMsg(MessageType::Game::SMASH) {} - - void Serialize(RakNet::BitStream& stream) const override; - - bool bIgnoreObjectVisibility{}; - bool force{}; - float ghostCapacity{}; - LWOOBJID killerID{}; - }; - - struct UnSmash : public NetGameMsg { - UnSmash() : NetGameMsg(MessageType::Game::UN_SMASH) {} - - void Serialize(RakNet::BitStream& stream) const override; - bool Deserialize(RakNet::BitStream& stream) override; - - LWOOBJID builderID{ LWOOBJID_EMPTY }; - float duration{ 3.0f }; - }; - struct PlayBehaviorSound : public NetGameMsg { PlayBehaviorSound() : NetGameMsg(MessageType::Game::PLAY_BEHAVIOR_SOUND) {} diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index 82a937282..da1958b02 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -1,4 +1,5 @@ #include "Strip.h" +#include "CombatMessages.h" #include "Amf3.h" #include "ControlBehaviorMsgs.h" diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp index 92b189960..ff1e3d140 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp @@ -251,7 +251,7 @@ namespace GMZeroCommands { return; } - GameMessages::SendResurrect(entity); + DestroyableComponent::Resurrect(*entity); } void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { diff --git a/dScripts/02_server/Enemy/General/BaseEnemyApe.cpp b/dScripts/02_server/Enemy/General/BaseEnemyApe.cpp index 8c38cbceb..2ba6449bd 100644 --- a/dScripts/02_server/Enemy/General/BaseEnemyApe.cpp +++ b/dScripts/02_server/Enemy/General/BaseEnemyApe.cpp @@ -3,6 +3,7 @@ #include "DestroyableComponent.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "EntityManager.h" #include "EntityInfo.h" #include "SkillComponent.h" @@ -139,9 +140,19 @@ void BaseEnemyApe::StunApe(Entity* self, bool stunState) { skillComponent->Interrupt(); } - GameMessages::SendSetStunned(self->GetObjectID(), stunState ? eStateChangeType::PUSH : eStateChangeType::POP, UNASSIGNED_SYSTEM_ADDRESS, self->GetObjectID(), - true, true, true, true, true, - true, true, true, true); + GameMessages::SetStunned stun; + stun.target = self->GetObjectID(); + stun.StateChangeType = stunState ? eStateChangeType::PUSH : eStateChangeType::POP; + stun.Originator = self->GetObjectID(); + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.bDontTerminateInteract = true; + stun.Send(UNASSIGNED_SYSTEM_ADDRESS); self->SetBoolean(u"knockedOut", stunState); } diff --git a/dScripts/02_server/Map/AM/AmSkullkinDrill.cpp b/dScripts/02_server/Map/AM/AmSkullkinDrill.cpp index bf0eb6f7e..891a81e1b 100644 --- a/dScripts/02_server/Map/AM/AmSkullkinDrill.cpp +++ b/dScripts/02_server/Map/AM/AmSkullkinDrill.cpp @@ -1,6 +1,7 @@ #include "AmSkullkinDrill.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "MovingPlatformComponent.h" #include "DestroyableComponent.h" #include "ProximityMonitorComponent.h" @@ -165,9 +166,15 @@ void AmSkullkinDrill::FreezePlayer(Entity* self, Entity* player, bool bFreeze) { } } - GameMessages::SendSetStunned(player->GetObjectID(), StateChangeType, player->GetSystemAddress(), self->GetObjectID(), - true, false, true, false, true, false, true - ); + GameMessages::SetStunned stun; + stun.target = player->GetObjectID(); + stun.StateChangeType = StateChangeType; + stun.Originator = self->GetObjectID(); + stun.bCantAttack = true; + stun.bCantInteract = true; + stun.bCantMove = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); } void AmSkullkinDrill::OnArrived(Entity* self, uint32_t waypointIndex) { diff --git a/dScripts/02_server/Map/AM/AmSkullkinDrillStand.cpp b/dScripts/02_server/Map/AM/AmSkullkinDrillStand.cpp index f1919ac9d..cde7b0c86 100644 --- a/dScripts/02_server/Map/AM/AmSkullkinDrillStand.cpp +++ b/dScripts/02_server/Map/AM/AmSkullkinDrillStand.cpp @@ -1,6 +1,7 @@ #include "AmSkullkinDrillStand.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "dpEntity.h" #include "Entity.h" #include "RenderComponent.h" @@ -30,7 +31,12 @@ void AmSkullkinDrillStand::OnProximityUpdate(Entity* self, Entity* entering, std NiPoint3 newVec = { (objPos.x - myPos.x) * 4.5f, 15, (objPos.z - myPos.z) * 4.5f }; - GameMessages::SendKnockback(entering->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 0, newVec); + GameMessages::Knockback knockback; + knockback.target = entering->GetObjectID(); + knockback.Caster = self->GetObjectID(); + knockback.Originator = self->GetObjectID(); + knockback.vector = newVec; + knockback.Send(UNASSIGNED_SYSTEM_ADDRESS); GameMessages::PlayFXEffect(entering->GetObjectID(), 1378, u"create", "pushBack").Send(UNASSIGNED_SYSTEM_ADDRESS); diff --git a/dScripts/02_server/Map/FV/Racing/RaceMaelstromGeiser.cpp b/dScripts/02_server/Map/FV/Racing/RaceMaelstromGeiser.cpp index 2866f7c8e..9941bab19 100644 --- a/dScripts/02_server/Map/FV/Racing/RaceMaelstromGeiser.cpp +++ b/dScripts/02_server/Map/FV/Racing/RaceMaelstromGeiser.cpp @@ -1,6 +1,7 @@ #include "RaceMaelstromGeiser.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "PossessableComponent.h" #include "PossessorComponent.h" #include "EntityManager.h" @@ -56,7 +57,12 @@ void RaceMaelstromGeiser::OnProximityUpdate(Entity* self, Entity* entering, std: } - GameMessages::SendDie(vehicle, self->GetObjectID(), LWOOBJID_EMPTY, true, eKillType::VIOLENT, u"", 0, 0, 0, true, false, 0); + GameMessages::Die die; + die.target = vehicle->GetObjectID(); + die.bClientDeath = true; + die.bSpawnLoot = false; + die.killerID = self->GetObjectID(); + die.Send(UNASSIGNED_SYSTEM_ADDRESS); auto* zoneController = Game::zoneManager->GetZoneControlObject(); diff --git a/dScripts/02_server/Map/GF/GfCaptainsCannon.cpp b/dScripts/02_server/Map/GF/GfCaptainsCannon.cpp index 759d8817e..73f5ba3d4 100644 --- a/dScripts/02_server/Map/GF/GfCaptainsCannon.cpp +++ b/dScripts/02_server/Map/GF/GfCaptainsCannon.cpp @@ -1,6 +1,7 @@ #include "GfCaptainsCannon.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "EntityManager.h" #include "MissionComponent.h" #include "RenderComponent.h" @@ -17,9 +18,18 @@ void GfCaptainsCannon::OnUse(Entity* self, Entity* user) { self->SetVar(u"bIsInUse", true); self->SetNetworkVar(u"bIsInUse", true); - GameMessages::SendSetStunned(user->GetObjectID(), eStateChangeType::PUSH, user->GetSystemAddress(), - LWOOBJID_EMPTY, true, true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = user->GetObjectID(); + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.bDontTerminateInteract = true; + stun.Send(user->GetSystemAddress()); auto position = self->GetPosition(); auto forward = QuatUtils::Forward(self->GetRotation()); @@ -73,9 +83,18 @@ void GfCaptainsCannon::OnTimerDone(Entity* self, std::string timerName) { ambientSound.audioGUID = "{7457d85c-4537-4317-ac9d-2f549219ea87}"; ambientSound.SendToClient(player->GetSystemAddress()); } else if (timerName == "cinematicTimer") { - GameMessages::SendSetStunned(playerId, eStateChangeType::POP, player->GetSystemAddress(), - LWOOBJID_EMPTY, true, true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = playerId; + stun.StateChangeType = eStateChangeType::POP; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.bDontTerminateInteract = true; + stun.Send(player->GetSystemAddress()); self->SetVar(u"bIsInUse", false); self->SetNetworkVar(u"bIsInUse", false); diff --git a/dScripts/02_server/Map/GF/MastTeleport.cpp b/dScripts/02_server/Map/GF/MastTeleport.cpp index 7c1633846..34f97e1fa 100644 --- a/dScripts/02_server/Map/GF/MastTeleport.cpp +++ b/dScripts/02_server/Map/GF/MastTeleport.cpp @@ -2,6 +2,7 @@ #include "EntityManager.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "Preconditions.h" #include "eEndBehavior.h" #include "DestroyableComponent.h" @@ -21,9 +22,17 @@ void MastTeleport::OnQuickBuildComplete(Entity* self, Entity* target) { if (Preconditions::Check(target, 154) && Preconditions::Check(target, 44)) { self->SetVar(u"userID", target->GetObjectID()); - GameMessages::SendSetStunned(target->GetObjectID(), eStateChangeType::PUSH, target->GetSystemAddress(), - LWOOBJID_EMPTY, true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = target->GetObjectID(); + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(target->GetSystemAddress()); auto* destroyableComponent = target->GetComponent(); if (destroyableComponent) destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, true, true, true, true, true, false, false, true, true); @@ -92,9 +101,17 @@ void MastTeleport::OnTimerDone(Entity* self, std::string timerName) { GameMessages::SendTeleport(playerId, position, QuatUtils::IDENTITY, player->GetSystemAddress()); - GameMessages::SendSetStunned(playerId, eStateChangeType::POP, player->GetSystemAddress(), - LWOOBJID_EMPTY, true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = playerId; + stun.StateChangeType = eStateChangeType::POP; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); auto* destroyableComponent = player->GetComponent(); if (destroyableComponent) destroyableComponent->SetStatusImmunity(eStateChangeType::POP, true, true, true, true, true, false, false, true, true); Game::entityManager->SerializeEntity(player); diff --git a/dScripts/02_server/Map/NT/NtAssemblyTubeServer.cpp b/dScripts/02_server/Map/NT/NtAssemblyTubeServer.cpp index 9d30ccd60..debcaf054 100644 --- a/dScripts/02_server/Map/NT/NtAssemblyTubeServer.cpp +++ b/dScripts/02_server/Map/NT/NtAssemblyTubeServer.cpp @@ -1,6 +1,7 @@ #include "NtAssemblyTubeServer.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "EntityManager.h" #include "MissionComponent.h" #include "eMissionTaskType.h" @@ -42,9 +43,17 @@ void NtAssemblyTubeServer::RunAssemblyTube(Entity* self, Entity* player) { if (player->IsPlayer() && !bPlayerBeingTeleported) { auto teleCinematic = self->GetVar(u"Cinematic"); - GameMessages::SendSetStunned(playerID, eStateChangeType::PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = playerID; + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); if (!teleCinematic.empty()) { const auto teleCinematicUname = teleCinematic; @@ -124,7 +133,15 @@ void NtAssemblyTubeServer::UnlockPlayer(Entity* self, Entity* player) { m_TeleportingPlayerTable[playerID] = false; - GameMessages::SendSetStunned(playerID, eStateChangeType::POP, player->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = playerID; + stun.StateChangeType = eStateChangeType::POP; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); } diff --git a/dScripts/02_server/Map/NT/NtParadoxPanelServer.cpp b/dScripts/02_server/Map/NT/NtParadoxPanelServer.cpp index 1d81a1192..9b65168dd 100644 --- a/dScripts/02_server/Map/NT/NtParadoxPanelServer.cpp +++ b/dScripts/02_server/Map/NT/NtParadoxPanelServer.cpp @@ -1,6 +1,7 @@ #include "NtParadoxPanelServer.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "MissionComponent.h" #include "EntityManager.h" #include "Character.h" @@ -40,11 +41,23 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user) { RenderComponent::PlayAnimation(player, u"rebuild-celebrate"); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"SparkStop", 0, 0, player->GetObjectID(), "", player->GetSystemAddress()); - GameMessages::SendSetStunned(player->GetObjectID(), eStateChangeType::POP, player->GetSystemAddress(), LWOOBJID_EMPTY, false, false, true, false, true, true, false, false, true); + GameMessages::SetStunned stun; + stun.target = player->GetObjectID(); + stun.StateChangeType = eStateChangeType::POP; + stun.bCantInteract = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.Send(player->GetSystemAddress()); self->SetVar(u"bActive", false); }); RenderComponent::PlayAnimation(user, u"nexus-powerpanel", 6.0f); - GameMessages::SendSetStunned(user->GetObjectID(), eStateChangeType::PUSH, user->GetSystemAddress(), LWOOBJID_EMPTY, false, false, true, false, true, true, false, false, true); + GameMessages::SetStunned stun; + stun.target = user->GetObjectID(); + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantInteract = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.Send(user->GetSystemAddress()); return; } @@ -52,7 +65,12 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user) { const auto dir = QuatUtils::Right(self->GetRotation()); - GameMessages::SendKnockback(user->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 0, { dir.x * 15, 5, dir.z * 15 }); + GameMessages::Knockback knockback; + knockback.target = user->GetObjectID(); + knockback.Caster = self->GetObjectID(); + knockback.Originator = self->GetObjectID(); + knockback.vector = NiPoint3{ dir.x * 15, 5, dir.z * 15 }; + knockback.Send(UNASSIGNED_SYSTEM_ADDRESS); GameMessages::PlayFXEffect(self->GetObjectID(), 6432, u"create", "console_sparks").Send(UNASSIGNED_SYSTEM_ADDRESS); diff --git a/dScripts/02_server/Map/NT/NtParadoxTeleServer.cpp b/dScripts/02_server/Map/NT/NtParadoxTeleServer.cpp index 65670fb28..7e63bb5e9 100644 --- a/dScripts/02_server/Map/NT/NtParadoxTeleServer.cpp +++ b/dScripts/02_server/Map/NT/NtParadoxTeleServer.cpp @@ -1,6 +1,7 @@ #include "NtParadoxTeleServer.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "EntityManager.h" #include "MissionComponent.h" #include "eMissionTaskType.h" @@ -26,9 +27,17 @@ void NtParadoxTeleServer::OnProximityUpdate(Entity* self, Entity* entering, std: const auto bPlayerBeingTeleported = m_TeleportingPlayerTable[playerID]; if (player->IsPlayer() && !bPlayerBeingTeleported) { - GameMessages::SendSetStunned(playerID, eStateChangeType::PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = playerID; + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); auto animTime = RenderComponent::PlayAnimation(player, u"teledeath", 4.0f); if (animTime == 0.0f) animTime = 2.0f; @@ -106,9 +115,17 @@ void NtParadoxTeleServer::UnlockPlayer(Entity* self, Entity* player) { m_TeleportingPlayerTable[playerID] = false; - GameMessages::SendSetStunned(playerID, eStateChangeType::POP, player->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = playerID; + stun.StateChangeType = eStateChangeType::POP; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); auto teleCinematic = self->GetVar(u"Cinematic"); diff --git a/dScripts/02_server/Map/NT/NtVentureCannonServer.cpp b/dScripts/02_server/Map/NT/NtVentureCannonServer.cpp index d35950f9d..ebf7beaea 100644 --- a/dScripts/02_server/Map/NT/NtVentureCannonServer.cpp +++ b/dScripts/02_server/Map/NT/NtVentureCannonServer.cpp @@ -1,6 +1,7 @@ #include "NtVentureCannonServer.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "EntityManager.h" #include "Entity.h" #include "GeneralUtils.h" @@ -21,9 +22,17 @@ void NtVentureCannonServer::OnUse(Entity* self, Entity* user) { self->SetNetworkVar(u"bIsInUse", true); - GameMessages::SendSetStunned(playerID, eStateChangeType::PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = playerID; + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); auto destPosition = self->GetPosition(); @@ -117,9 +126,17 @@ void NtVentureCannonServer::ExitCannonEnded(Entity* self, Entity* player) { } void NtVentureCannonServer::UnlockCannonPlayer(Entity* self, Entity* player) { - GameMessages::SendSetStunned(player->GetObjectID(), eStateChangeType::POP, player->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = player->GetObjectID(); + stun.StateChangeType = eStateChangeType::POP; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); self->SetNetworkVar(u"bIsInUse", false); diff --git a/dScripts/02_server/Map/njhub/FlameJetServer.cpp b/dScripts/02_server/Map/njhub/FlameJetServer.cpp index bf7609582..f9947c06d 100644 --- a/dScripts/02_server/Map/njhub/FlameJetServer.cpp +++ b/dScripts/02_server/Map/njhub/FlameJetServer.cpp @@ -1,6 +1,7 @@ #include "FlameJetServer.h" #include "SkillComponent.h" #include "GameMessages.h" +#include "CombatMessages.h" void FlameJetServer::OnStartup(Entity* self) { if (self->GetVar(u"NotActive")) { @@ -33,7 +34,13 @@ void FlameJetServer::OnCollisionPhantom(Entity* self, Entity* target) { dir.x = -dir.x * 15; dir.z = -dir.z * 15; - GameMessages::SendKnockback(target->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 1000, dir); + GameMessages::Knockback knockback; + knockback.target = target->GetObjectID(); + knockback.Caster = self->GetObjectID(); + knockback.Originator = self->GetObjectID(); + knockback.iKnockBackTimeMS = 1000; + knockback.vector = dir; + knockback.Send(UNASSIGNED_SYSTEM_ADDRESS); } void FlameJetServer::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { diff --git a/dScripts/BaseConsoleTeleportServer.cpp b/dScripts/BaseConsoleTeleportServer.cpp index 896cc24a7..c64b8a569 100644 --- a/dScripts/BaseConsoleTeleportServer.cpp +++ b/dScripts/BaseConsoleTeleportServer.cpp @@ -1,6 +1,7 @@ #include "BaseConsoleTeleportServer.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "CharacterComponent.h" #include "RenderComponent.h" #include "EntityManager.h" @@ -28,10 +29,18 @@ void BaseConsoleTeleportServer::BaseOnMessageBoxResponse(Entity* self, Entity* s if (button == 1) { - GameMessages::SendSetStunned( - player->GetObjectID(), eStateChangeType::PUSH, player->GetSystemAddress(), player->GetObjectID(), - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = player->GetObjectID(); + stun.StateChangeType = eStateChangeType::PUSH; + stun.Originator = player->GetObjectID(); + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); const auto teleportFXID = self->GetVar(u"teleportEffectID"); @@ -94,10 +103,18 @@ void BaseConsoleTeleportServer::TransferPlayer(Entity* self, Entity* player, int return; } - GameMessages::SendSetStunned( - player->GetObjectID(), eStateChangeType::POP, player->GetSystemAddress(), player->GetObjectID(), - true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = player->GetObjectID(); + stun.StateChangeType = eStateChangeType::POP; + stun.Originator = player->GetObjectID(); + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.Send(player->GetSystemAddress()); GameMessages::SendTerminateInteraction(player->GetObjectID(), eTerminateType::FROM_INTERACTION, player->GetObjectID()); diff --git a/dScripts/BaseWavesServer.cpp b/dScripts/BaseWavesServer.cpp index 5c287e1bd..1ff875800 100644 --- a/dScripts/BaseWavesServer.cpp +++ b/dScripts/BaseWavesServer.cpp @@ -1,6 +1,7 @@ #include "BaseWavesServer.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "ActivityMessages.h" #include "DestroyableComponent.h" #include "EntityManager.h" @@ -22,7 +23,10 @@ void BaseWavesServer::SetGameVariables(Entity* self) { // Done void BaseWavesServer::BasePlayerLoaded(Entity* self, Entity* player) { GameMessages::SendPlayerSetCameraCyclingMode(player->GetObjectID(), player->GetSystemAddress()); - GameMessages::SendPlayerAllowedRespawn(player->GetObjectID(), true, player->GetSystemAddress()); + GameMessages::SetPlayerAllowedRespawn allowedRespawn; + allowedRespawn.target = player->GetObjectID(); + allowedRespawn.dontPromptForRespawn = true; + allowedRespawn.SendToClient(player->GetSystemAddress()); state.waitingPlayers.push_back(player->GetObjectID()); state.players.push_back(player->GetObjectID()); diff --git a/dScripts/EquipmentScripts/PersonalFortress.cpp b/dScripts/EquipmentScripts/PersonalFortress.cpp index 7e6d35661..582706e77 100644 --- a/dScripts/EquipmentScripts/PersonalFortress.cpp +++ b/dScripts/EquipmentScripts/PersonalFortress.cpp @@ -1,5 +1,6 @@ #include "PersonalFortress.h" #include "GameMessages.h" +#include "CombatMessages.h" #include "SkillComponent.h" #include "DestroyableComponent.h" #include "ControllablePhysicsComponent.h" @@ -22,9 +23,18 @@ void PersonalFortress::OnStartup(Entity* self) { true, true, true, true, true, true ); - GameMessages::SendSetStunned(owner->GetObjectID(), eStateChangeType::PUSH, owner->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = owner->GetObjectID(); + stun.StateChangeType = eStateChangeType::PUSH; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.bDontTerminateInteract = true; + stun.Send(owner->GetSystemAddress()); Game::entityManager->SerializeEntity(owner); } @@ -43,9 +53,18 @@ void PersonalFortress::OnDie(Entity* self, Entity* killer) { true, true, true, true, true, true ); - GameMessages::SendSetStunned(owner->GetObjectID(), eStateChangeType::POP, owner->GetSystemAddress(), LWOOBJID_EMPTY, - true, true, true, true, true, true, true, true, true - ); + GameMessages::SetStunned stun; + stun.target = owner->GetObjectID(); + stun.StateChangeType = eStateChangeType::POP; + stun.bCantAttack = true; + stun.bCantEquip = true; + stun.bCantInteract = true; + stun.bCantJump = true; + stun.bCantMove = true; + stun.bCantTurn = true; + stun.bCantUseItem = true; + stun.bDontTerminateInteract = true; + stun.Send(owner->GetSystemAddress()); Game::entityManager->SerializeEntity(owner); } diff --git a/dScripts/ai/ACT/ActVehicleDeathTrigger.cpp b/dScripts/ai/ACT/ActVehicleDeathTrigger.cpp index 90333063d..113e1aa17 100644 --- a/dScripts/ai/ACT/ActVehicleDeathTrigger.cpp +++ b/dScripts/ai/ACT/ActVehicleDeathTrigger.cpp @@ -1,6 +1,7 @@ #include "ActVehicleDeathTrigger.h" #include "PossessableComponent.h" #include "GameMessages.h" +#include "CombatMessages.h" #include "RacingControlComponent.h" #include "dZoneManager.h" #include "EntityManager.h" @@ -40,7 +41,12 @@ void ActVehicleDeathTrigger::OnCollisionPhantom(Entity* self, Entity* target) { } - GameMessages::SendDie(vehicle, self->GetObjectID(), LWOOBJID_EMPTY, true, eKillType::VIOLENT, u"", 0, 0, 0, true, false, 0); + GameMessages::Die die; + die.target = vehicle->GetObjectID(); + die.bClientDeath = true; + die.bSpawnLoot = false; + die.killerID = self->GetObjectID(); + die.Send(UNASSIGNED_SYSTEM_ADDRESS); auto* zoneController = Game::zoneManager->GetZoneControlObject(); diff --git a/dScripts/ai/AG/AgShipPlayerShockServer.cpp b/dScripts/ai/AG/AgShipPlayerShockServer.cpp index c274a535d..a439f0bde 100644 --- a/dScripts/ai/AG/AgShipPlayerShockServer.cpp +++ b/dScripts/ai/AG/AgShipPlayerShockServer.cpp @@ -1,6 +1,7 @@ #include "AgShipPlayerShockServer.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "RenderComponent.h" #include "Entity.h" #include "eTerminateType.h" @@ -12,7 +13,12 @@ void AgShipPlayerShockServer::OnUse(Entity* self, Entity* user) { } active = true; RenderComponent::PlayAnimation(user, shockAnim); - GameMessages::SendKnockback(user->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 0, NiPoint3(-20, 10, -20)); + GameMessages::Knockback knockback; + knockback.target = user->GetObjectID(); + knockback.Caster = self->GetObjectID(); + knockback.Originator = self->GetObjectID(); + knockback.vector = NiPoint3(-20, 10, -20); + knockback.Send(UNASSIGNED_SYSTEM_ADDRESS); GameMessages::PlayFXEffect(self->GetObjectID(), 1430, u"create", "console_sparks").Send(UNASSIGNED_SYSTEM_ADDRESS); self->AddTimer("FXTime", fxTime); diff --git a/dScripts/ai/AG/AgSpiderBossMessage.cpp b/dScripts/ai/AG/AgSpiderBossMessage.cpp index a9a7ce0c5..a02f9441c 100644 --- a/dScripts/ai/AG/AgSpiderBossMessage.cpp +++ b/dScripts/ai/AG/AgSpiderBossMessage.cpp @@ -3,6 +3,7 @@ #include "Entity.h" #include "GameMessages.h" #include "EffectsMessages.h" +#include "CombatMessages.h" #include "RenderComponent.h" @@ -44,7 +45,10 @@ void AgSpiderBossMessage::OnCollisionPhantom(Entity* self, Entity* target) { forward.y += 15; forward.x *= 100; forward.z *= 100; - GameMessages::SendKnockback(target->GetObjectID(), LWOOBJID_EMPTY, LWOOBJID_EMPTY, 0, forward); + GameMessages::Knockback knockback; + knockback.target = target->GetObjectID(); + knockback.vector = forward; + knockback.Send(UNASSIGNED_SYSTEM_ADDRESS); if (box.isTouch || box.isDisplayed) return; box.boxSelf = self->GetObjectID(); diff --git a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt index d9e855536..551dc0637 100644 --- a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt +++ b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt @@ -1,6 +1,7 @@ SET(DGAMEMESSAGES_TESTS "ActivityMessagesTests.cpp" "BuildingMessagesTests.cpp" + "CombatMessagesTests.cpp" "GameMessageTests.cpp" "GameMsgSplitTests.cpp" "EffectsMessagesTests.cpp" diff --git a/tests/dGameTests/dGameMessagesTests/CombatMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/CombatMessagesTests.cpp new file mode 100644 index 000000000..21e812899 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/CombatMessagesTests.cpp @@ -0,0 +1,381 @@ +#include "CombatMessages.h" +#include "GameDependencies.h" +#include "GameMessageTestUtils.h" +#include "Legacy/CombatMessagesLegacy.h" + +#include "eKillType.h" +#include "eStateChangeType.h" + +#include +#include +#include +#include +#include + +#include + +using namespace GameMessageTestUtils; + +namespace { + const std::vector g_Strings = { u"", u"electro-shock-death", u"é中", std::u16string(300, u'x') }; + const std::vector g_Ids = { LWOOBJID_EMPTY, 0x1000000000000001LL, 0x0102030405060708LL }; + const std::vector g_Floats = { 0.0f, 90.0f, -1.5f }; + + PacketBytes Payload(const GameMessages::NetGameMsg& msg) { + RakNet::BitStream bitStream; + msg.Serialize(bitStream); + return FromBitStream(bitStream); + } + + template + Result ReadWithLegacy(const GameMessages::NetGameMsg& msg, const std::function& read) { + RakNet::BitStream wire; + msg.Serialize(wire); + RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + auto result = read(legacyStream); + EXPECT_EQ(legacyStream.GetReadOffset(), wire.GetNumberOfBitsUsed()); + return result; + } + + // For the old functions that always broadcast (SEND_PACKET_BROADCAST) whatever address they were given: + // the struct is sent with Send(UNASSIGNED_SYSTEM_ADDRESS) and must produce the same bytes and destination. + void ExpectSameAsLegacyBroadcast(const std::function& legacySend, const GameMessages::NetGameMsg& msg) { + const auto legacyPackets = Capture([&] { legacySend(UNASSIGNED_SYSTEM_ADDRESS); }); + const auto newPackets = Capture([&] { msg.Send(UNASSIGNED_SYSTEM_ADDRESS); }); + ASSERT_EQ(legacyPackets.size(), 1); + ASSERT_EQ(newPackets.size(), 1); + EXPECT_PACKET_EQ(FromCapture(legacyPackets[0]), FromCapture(newPackets[0])); + EXPECT_TRUE(legacyPackets[0].broadcast); + EXPECT_TRUE(newPackets[0].broadcast); + EXPECT_EQ(legacyPackets[0].sysAddr, newPackets[0].sysAddr); + } + + // Bit i of mask, for walking every combination of a message's bools. + bool Bit(uint32_t mask, int i) { return (mask >> i) & 1; } +} + +class CombatMessagesTests : public GameDependenciesTest { +protected: + void SetUp() override { SetUpDependencies(); } + void TearDown() override { TearDownDependencies(); } +}; + +TEST_F(CombatMessagesTests, DieMatchesLegacy) { + for (const auto target : g_Targets) { + Entity entity(target, info); + for (const auto& deathType : g_Strings) { + for (const auto killType : { eKillType::VIOLENT, eKillType::SILENT }) { + for (const auto killer : g_Ids) { + for (const auto lootOwner : g_Ids) { + for (uint32_t mask = 0; mask < 4; mask++) { + for (const float value : g_Floats) { + GameMessages::Die msg; + msg.target = target; + msg.bClientDeath = Bit(mask, 0); + msg.bSpawnLoot = Bit(mask, 1); + msg.deathType = deathType; + msg.directionRelative_AngleXZ = value; + msg.directionRelative_AngleY = value * 2; + msg.directionRelative_Force = value * 3; + msg.killType = killType; + msg.killerID = killer; + msg.lootOwnerID = lootOwner; + ExpectSameAsLegacyBroadcast([&](const SystemAddress&) { + LegacyGameMessages::SendDie(&entity, killer, lootOwner, true, killType, deathType, value * 2, value, value * 3, Bit(mask, 0), Bit(mask, 1), 1.0f); + }, msg); + ExpectSameAsLegacyBroadcast([&](const SystemAddress&) { + LegacyGameMessages::SendDieNoImplCode(&entity, killer, lootOwner, killType, deathType, value * 2, value, value * 3, Bit(mask, 0), Bit(mask, 1)); + }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.deathType, deathType); + EXPECT_EQ(copy.killType, killType); + EXPECT_EQ(copy.lootOwnerID, lootOwner); + + // RequestDie is read with the same layout. + GameMessages::RequestDie request; + request.bClientDeath = msg.bClientDeath; + request.bSpawnLoot = msg.bSpawnLoot; + request.deathType = deathType; + request.directionRelative_AngleXZ = value; + request.directionRelative_AngleY = value * 2; + request.directionRelative_Force = value * 3; + request.killType = killType; + request.killerID = killer; + request.lootOwnerID = lootOwner; + const auto legacy = ReadWithLegacy(request, LegacyGameMessages::ReadRequestDie); + const auto requestCopy = RoundTrip(request); + EXPECT_EQ(requestCopy.bClientDeath, legacy.bClientDeath); + EXPECT_EQ(requestCopy.bSpawnLoot, legacy.bSpawnLoot); + EXPECT_EQ(requestCopy.deathType, legacy.deathType); + EXPECT_EQ(requestCopy.directionRelative_AngleXZ, legacy.directionRelativeAngleXZ); + EXPECT_EQ(requestCopy.directionRelative_AngleY, legacy.directionRelativeAngleY); + EXPECT_EQ(requestCopy.directionRelative_Force, legacy.directionRelativeForce); + EXPECT_EQ(requestCopy.killType, legacy.killType); + EXPECT_EQ(requestCopy.killerID, legacy.killerID); + EXPECT_EQ(requestCopy.lootOwnerID, legacy.lootOwnerID); + if (deathType.size() < 30 && value == 0.0f) ExpectTruncatedFails(request); + } + } + } + } + } + } + } +} + +TEST_F(CombatMessagesTests, ResurrectAndRespawnMatchLegacy) { + for (const auto target : g_Targets) { + Entity entity(target, info); + GameMessages::Resurrect msg; + msg.target = target; + ExpectSameAsLegacyBroadcast([&](const SystemAddress&) { LegacyGameMessages::SendResurrect(&entity); }, msg); + for (const bool immediately : { false, true }) { + msg.bRezImmediately = immediately; + EXPECT_EQ(ReadWithLegacy(msg, LegacyGameMessages::ReadResurrect), immediately); + EXPECT_EQ(RoundTrip(msg).bRezImmediately, immediately); + ExpectTruncatedFails(msg); + + GameMessages::SetPlayerAllowedRespawn respawn; + respawn.target = target; + respawn.dontPromptForRespawn = immediately; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendPlayerAllowedRespawn(target, immediately, a); }, respawn, SendMode::SendToClient); + EXPECT_EQ(RoundTrip(respawn).dontPromptForRespawn, immediately); + } + + for (const int32_t armor : { -1, 0, 5 }) { + for (const int32_t health : { -1, 0, 7 }) { + for (const int32_t imagination : { -1, 0, std::numeric_limits::max() }) { + GameMessages::SetResurrectRestoreValues restore; + restore.target = target; + restore.iArmorRestore = armor; + restore.iHealthRestore = health; + restore.iImaginationRestore = imagination; + ExpectSameAsLegacyBroadcast([&](const SystemAddress&) { LegacyGameMessages::SendSetResurrectRestoreValues(&entity, armor, health, imagination); }, restore); + const auto copy = RoundTrip(restore); + EXPECT_EQ(copy.iArmorRestore, armor); + EXPECT_EQ(copy.iImaginationRestore, imagination); + } + } + } + } +} + +TEST_F(CombatMessagesTests, KnockbackAndSmashMatchLegacy) { + for (const auto target : g_Targets) { + Entity entity(target, info); + for (const auto caster : g_Ids) { + for (const auto originator : g_Ids) { + for (const int32_t time : { 0, 1000, -1 }) { + GameMessages::Knockback msg; + msg.target = target; + msg.Caster = caster; + msg.Originator = originator; + msg.iKnockBackTimeMS = time; + msg.vector = NiPoint3(-20.0f, 10.0f, 0.5f); + ExpectSameAsLegacyBroadcast([&](const SystemAddress&) { LegacyGameMessages::SendKnockback(target, caster, originator, time, msg.vector); }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.Caster, caster); + EXPECT_EQ(copy.iKnockBackTimeMS, time); + EXPECT_EQ(copy.vector, msg.vector); + } + } + for (const float force : g_Floats) { + for (const bool ignore : { false, true }) { + GameMessages::Smash smash; + smash.target = target; + smash.bIgnoreObjectVisibility = ignore; + smash.force = force; + smash.ghostOpacity = force / 2; + smash.killerID = caster; + ExpectSameAsLegacyBroadcast([&](const SystemAddress&) { LegacyGameMessages::SendSmash(&entity, force, force / 2, caster, ignore); }, smash); + EXPECT_EQ(RoundTrip(smash).killerID, caster); + } + GameMessages::UnSmash unsmash; + unsmash.target = target; + unsmash.builderID = caster; + unsmash.duration = force == 0.0f ? 3.0f : force; + ExpectSameAsLegacyBroadcast([&](const SystemAddress&) { LegacyGameMessages::SendUnSmash(&entity, caster, unsmash.duration); }, unsmash); + } + } + } +} + +TEST_F(CombatMessagesTests, StunsAndImmunitiesMatchLegacy) { + for (const auto target : g_Targets) { + for (const auto originator : g_Ids) { + for (const auto state : { eStateChangeType::PUSH, eStateChangeType::POP }) { + // 17 bools: walk each one alone, all of them, none, and alternating patterns. + std::vector masks = { 0, 0x1FFFF, 0x0AAAA, 0x15555 }; + for (int i = 0; i < 17; i++) masks.push_back(1u << i); + for (const auto mask : masks) { + GameMessages::SetStunned msg; + msg.target = target; + msg.Originator = originator; + msg.StateChangeType = state; + msg.bCantAttack = Bit(mask, 0); + msg.bCantEquip = Bit(mask, 1); + msg.bCantInteract = Bit(mask, 2); + msg.bCantJump = Bit(mask, 3); + msg.bCantMove = Bit(mask, 4); + msg.bCantTurn = Bit(mask, 5); + msg.bCantUseItem = Bit(mask, 6); + msg.bDontTerminateInteract = Bit(mask, 7); + msg.bIgnoreImmunity = Bit(mask, 8); + msg.bCantAttackOutChangeWasApplied = Bit(mask, 9); + msg.bCantEquipOutChangeWasApplied = Bit(mask, 10); + msg.bCantInteractOutChangeWasApplied = Bit(mask, 11); + msg.bCantJumpOutChangeWasApplied = Bit(mask, 12); + msg.bCantMoveOutChangeWasApplied = Bit(mask, 13); + msg.bCantTurnOutChangeWasApplied = Bit(mask, 14); + msg.bCantUseItemOutChangeWasApplied = Bit(mask, 15); + ExpectSameAsLegacy([&](const SystemAddress& a) { + LegacyGameMessages::SendSetStunned(target, state, a, originator, Bit(mask, 0), Bit(mask, 1), Bit(mask, 2), Bit(mask, 3), Bit(mask, 4), Bit(mask, 5), Bit(mask, 6), Bit(mask, 7), Bit(mask, 8), + Bit(mask, 9), Bit(mask, 10), Bit(mask, 11), Bit(mask, 12), Bit(mask, 13), Bit(mask, 14), Bit(mask, 15)); + }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.bCantUseItemOutChangeWasApplied, msg.bCantUseItemOutChangeWasApplied); + EXPECT_EQ(copy.Originator, originator); + + GameMessages::SetStunImmunity immunity; + immunity.target = target; + immunity.Caster = originator; + immunity.StateChangeType = state; + immunity.bImmuneToStunAttack = Bit(mask, 0); + immunity.bImmuneToStunEquip = Bit(mask, 1); + immunity.bImmuneToStunInteract = Bit(mask, 2); + immunity.bImmuneToStunJump = Bit(mask, 3); + immunity.bImmuneToStunMove = Bit(mask, 4); + immunity.bImmuneToStunTurn = Bit(mask, 5); + immunity.bImmuneToStunUseItem = Bit(mask, 6); + ExpectSameAsLegacy([&](const SystemAddress& a) { + LegacyGameMessages::SendSetStunImmunity(target, state, a, originator, Bit(mask, 0), Bit(mask, 1), Bit(mask, 2), Bit(mask, 3), Bit(mask, 4), Bit(mask, 5), Bit(mask, 6)); + }, immunity); + EXPECT_EQ(RoundTrip(immunity).bImmuneToStunUseItem, Bit(mask, 6)); + + GameMessages::SetStatusImmunity status; + status.target = target; + status.StateChangeType = state; + status.bImmuneToBasicAttack = Bit(mask, 0); + status.bImmuneToDOT = Bit(mask, 1); + status.bImmuneToKnockback = Bit(mask, 2); + status.bImmuneToInterrupt = Bit(mask, 3); + status.bImmuneToSpeed = Bit(mask, 4); + status.bImmuneToImaginationGain = Bit(mask, 5); + status.bImmuneToImaginationLoss = Bit(mask, 6); + status.bImmuneToQuickbuildInterrupt = Bit(mask, 7); + status.bImmuneToPullToPoint = Bit(mask, 8); + ExpectSameAsLegacy([&](const SystemAddress& a) { + LegacyGameMessages::SendSetStatusImmunity(target, state, a, Bit(mask, 0), Bit(mask, 1), Bit(mask, 2), Bit(mask, 3), Bit(mask, 4), Bit(mask, 5), Bit(mask, 6), Bit(mask, 7), Bit(mask, 8)); + }, status); + EXPECT_EQ(RoundTrip(status).bImmuneToPullToPoint, Bit(mask, 8)); + } + } + } + } +} + +TEST_F(CombatMessagesTests, BuffsMatchLegacy) { + for (const auto target : g_Targets) { + Entity entity(target, info); + for (const auto caster : g_Ids) { + for (const uint32_t buffID : { 0u, 42u, 0xFFFFFFFFu }) { + for (const uint32_t duration : { 0u, 5000u }) { + for (uint32_t mask = 0; mask < (1u << 11); mask += 37) { + GameMessages::AddBuff msg; + msg.target = target; + msg.bAddedByTeammate = Bit(mask, 0); + msg.bApplyOnTeammates = Bit(mask, 1); + msg.bCancelOnDamaged = Bit(mask, 2); + msg.bCancelOnDeath = Bit(mask, 3); + msg.bCancelOnLogOut = Bit(mask, 4); + msg.bCancelOnRemoveBuff = Bit(mask, 5); + msg.bCancelOnUI = Bit(mask, 6); + msg.bCancelOnUnEquip = Bit(mask, 7); + msg.bCancelOnZone = Bit(mask, 8); + msg.bIsImmunity = Bit(mask, 9); + msg.i64AddedBy = caster; + msg.uiBuffID = buffID; + msg.uiDurationMS = duration; + auto objectID = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { + LegacyGameMessages::SendAddBuff(objectID, caster, buffID, duration, Bit(mask, 9), Bit(mask, 2), Bit(mask, 3), Bit(mask, 4), Bit(mask, 5), Bit(mask, 6), Bit(mask, 7), Bit(mask, 8), Bit(mask, 0), Bit(mask, 1), a); + }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.uiDurationMS, duration); + EXPECT_EQ(copy.i64AddedBy, caster); + } + } + for (uint32_t mask = 0; mask < 4; mask++) { + GameMessages::RemoveBuff remove; + remove.target = target; + remove.bFromUnEquip = Bit(mask, 0); + remove.bRemoveImmunity = Bit(mask, 1); + remove.uiBuffID = buffID; + ExpectSameAsLegacyBroadcast([&](const SystemAddress&) { LegacyGameMessages::SendRemoveBuff(&entity, Bit(mask, 0), Bit(mask, 1), buffID); }, remove); + EXPECT_EQ(RoundTrip(remove).uiBuffID, buffID); + } + } + for (const uint32_t modifier : { 500u, 0u, 750u }) { + GameMessages::AddRunSpeedModifier add; + add.target = target; + add.i64Caster = caster; + add.uiModifier = modifier; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendAddRunSpeedModifier(target, caster, modifier, a); }, add); + EXPECT_EQ(RoundTrip(add).uiModifier, modifier); + + GameMessages::RemoveRunSpeedModifier remove; + remove.target = target; + remove.uiModifier = modifier; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendRemoveRunSpeedModifier(target, modifier, a); }, remove); + } + } + + GameMessages::DeactivateBubbleBuffFromServer deactivate; + deactivate.target = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendDeactivateBubbleBuffFromServer(target, a); }, deactivate); + } + + for (const auto& type : { std::u16string(u""), std::u16string(u"skunk"), std::u16string(u"energy") }) { + for (const bool special : { false, true }) { + GameMessages::ActivateBubbleBuff activate; + activate.bSpecialAnims = special; + activate.wszType = type; + const auto legacy = ReadWithLegacy(activate, LegacyGameMessages::ReadActivateBubbleBuff); + EXPECT_TRUE(legacy.read); + EXPECT_EQ(legacy.specialAnimations, special); + EXPECT_EQ(legacy.type, type); + EXPECT_EQ(RoundTrip(activate).wszType, type); + ExpectTruncatedFails(activate); + } + } + + RakNet::BitStream empty; + EXPECT_TRUE(GameMessages::DeactivateBubbleBuff().Deserialize(empty)); + EXPECT_TRUE(GameMessages::RequestSmashPlayer().Deserialize(empty)); + EXPECT_TRUE(GameMessages::RequestResurrect().Deserialize(empty)); +} + +// Independent of the legacy code: hand computed payloads (RakNet writes MSB first). +TEST_F(CombatMessagesTests, GoldenBytes) { + GameMessages::Die die; + die.killerID = 0x11; + die.deathType = u"A"; + // bClientDeath 0, bSpawnLoot 1, u32 1, 'A' u16, three zero floats, killType flag 0, killer, loot owner flag 0. + EXPECT_PACKET_EQ(FromHex("40 40 00 00 10 40 00 00 00 00 00 00 00 00 00 00 00 00 02 20 00 00 00 00 00 00 00", 212), Payload(die)); + + GameMessages::Knockback knockback; + knockback.iKnockBackTimeMS = 1000; + knockback.vector = NiPoint3(1.0f, 0.0f, 0.0f); + EXPECT_PACKET_EQ(FromHex("3d 00 60 00 00 00 10 07 e0 00 00 00 00 00 00 00 00", 131), Payload(knockback)); + + GameMessages::SetStunned stun; + stun.StateChangeType = eStateChangeType::POP; + stun.bCantMove = true; + // originator flag 0, u32 1, 14 flags (bCantMove is the 9th), bDontTerminateInteract 0, bIgnoreImmunity 1. + EXPECT_PACKET_EQ(FromHex("00 80 00 00 00 40 80", 49), Payload(stun)); + + GameMessages::RemoveBuff remove; + remove.bRemoveImmunity = true; + remove.uiBuffID = 3; + EXPECT_PACKET_EQ(FromHex("20 60 00 00 00", 35), Payload(remove)); +} diff --git a/tests/dGameTests/dGameMessagesTests/Legacy/CombatMessagesLegacy.h b/tests/dGameTests/dGameMessagesTests/Legacy/CombatMessagesLegacy.h new file mode 100644 index 000000000..a45da064e --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/Legacy/CombatMessagesLegacy.h @@ -0,0 +1,480 @@ +#ifndef COMBATMESSAGESLEGACY_H +#define COMBATMESSAGESLEGACY_H + +// FROZEN ORACLE - DO NOT EDIT. +// Verbatim copies of the hand written GameMessages functions that CombatMessages.h replaced +// (dGame/dGameMessages/GameMessages.cpp, branched from origin/main 129199e4). Only the namespace changed. +// The Read* functions are the read sequences of the replaced GameMessages::Handle* functions, verbatim up to +// the point where the handler starts using what it read. + +#include "BitStreamUtils.h" +#include "dCommonVars.h" +#include "dServer.h" +#include "DestroyableComponent.h" +#include "eKillType.h" +#include "eStateChangeType.h" +#include "Entity.h" +#include "Game.h" +#include "GameMessages.h" +#include "GeneralUtils.h" +#include "LevelProgressionComponent.h" +#include "MessageType/Client.h" +#include "MessageType/Game.h" +#include "NiPoint3.h" +#include "ServiceType.h" + +#include + +namespace LegacyGameMessages { + inline void SendPlayerAllowedRespawn(LWOOBJID entityID, bool doNotPromptRespawn, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entityID); + bitStream.Write(MessageType::Game::SET_PLAYER_ALLOWED_RESPAWN); + bitStream.Write(doNotPromptRespawn); + + SEND_PACKET; + } + + inline void SendKnockback(const LWOOBJID& objectID, const LWOOBJID& caster, const LWOOBJID& originator, int knockBackTimeMS, const NiPoint3& vector) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectID); + bitStream.Write(MessageType::Game::KNOCKBACK); + + bool casterFlag = caster != LWOOBJID_EMPTY; + bool originatorFlag = originator != LWOOBJID_EMPTY; + bool knockBackTimeMSFlag = knockBackTimeMS != 0; + + bitStream.Write(casterFlag); + if (casterFlag) bitStream.Write(caster); + bitStream.Write(originatorFlag); + if (originatorFlag) bitStream.Write(originator); + bitStream.Write(knockBackTimeMSFlag); + if (knockBackTimeMSFlag) bitStream.Write(knockBackTimeMS); + bitStream.Write(vector); + + SEND_PACKET_BROADCAST; + } + + inline void SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, const LWOOBJID& lootOwnerID, eKillType killType, std::u16string deathType, float directionRelative_AngleY, float directionRelative_AngleXZ, float directionRelative_Force, bool bClientDeath, bool bSpawnLoot) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(MessageType::Game::DIE); + + bitStream.Write(bClientDeath); + bitStream.Write(bSpawnLoot); + bitStream.Write(deathType.size()); + bitStream.Write(deathType); + bitStream.Write(directionRelative_AngleXZ); + bitStream.Write(directionRelative_AngleY); + bitStream.Write(directionRelative_Force); + + bitStream.Write(killType != eKillType::VIOLENT); + if (killType != eKillType::VIOLENT) bitStream.Write(killType); + + bitStream.Write(killerID); + bitStream.Write(lootOwnerID != LWOOBJID_EMPTY); + if (lootOwnerID != LWOOBJID_EMPTY) { + bitStream.Write(lootOwnerID); + } + + SEND_PACKET_BROADCAST; + } + + inline void SendDie(Entity* entity, const LWOOBJID& killerID, const LWOOBJID& lootOwnerID, bool bDieAccepted, eKillType killType, std::u16string deathType, float directionRelative_AngleY, float directionRelative_AngleXZ, float directionRelative_Force, bool bClientDeath, bool bSpawnLoot, float coinSpawnTime) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + + bitStream.Write(MessageType::Game::DIE); + + bitStream.Write(bClientDeath); + bitStream.Write(bSpawnLoot); + + //bitStream.Write(coinSpawnTime != -1.0f); + //if (coinSpawnTime != -1.0f) bitStream.Write(coinSpawnTime); + + uint32_t deathTypeLength = deathType.size(); + bitStream.Write(deathTypeLength); + for (uint32_t k = 0; k < deathTypeLength; k++) { + bitStream.Write(deathType[k]); + } + + bitStream.Write(directionRelative_AngleXZ); + bitStream.Write(directionRelative_AngleY); + bitStream.Write(directionRelative_Force); + + bitStream.Write(killType != eKillType::VIOLENT); + if (killType != eKillType::VIOLENT) bitStream.Write(killType); + + bitStream.Write(killerID); + + bitStream.Write(lootOwnerID != LWOOBJID_EMPTY); + if (lootOwnerID != LWOOBJID_EMPTY) { + bitStream.Write(lootOwnerID); + } + + SEND_PACKET_BROADCAST; + } + + inline void SendResurrect(Entity* entity) { + // Restore the players health after the animation for respawning has finished. + // This is when the health appered back in live, not immediately upon requesting respawn + // Add a half second in case someone decides to cheat and move during the death animation + // and just make sure the client has time to be ready. + constexpr float respawnTime = 3.66700005531311f + 0.5f; + entity->AddCallbackTimer(respawnTime, [=]() { + GameMessages::PlayerResurrectionFinished msg; + entity->NotifyPlayerResurrectionFinished(msg); + auto* destroyableComponent = entity->GetComponent(); + + if (destroyableComponent != nullptr && entity->GetLOT() == 1) { + destroyableComponent->SetIsDead(false); + auto* levelComponent = entity->GetComponent(); + if (levelComponent) { + int32_t healthToRestore = levelComponent->GetLevel() >= 45 ? 8 : 4; + if (healthToRestore > destroyableComponent->GetMaxHealth()) healthToRestore = destroyableComponent->GetMaxHealth(); + destroyableComponent->SetHealth(healthToRestore); + + int32_t imaginationToRestore = levelComponent->GetLevel() >= 45 ? 20 : 6; + if (imaginationToRestore > destroyableComponent->GetMaxImagination()) imaginationToRestore = destroyableComponent->GetMaxImagination(); + destroyableComponent->SetImagination(imaginationToRestore); + } + } + }); + + CBITSTREAM; + CMSGHEADER; + + bool bRezImmediately = false; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(MessageType::Game::RESURRECT); + bitStream.Write(bRezImmediately); + + SEND_PACKET_BROADCAST; + } + + inline void SendSetStunned(LWOOBJID objectId, eStateChangeType stateChangeType, const SystemAddress& sysAddr, + LWOOBJID originator, bool bCantAttack, bool bCantEquip, + bool bCantInteract, bool bCantJump, bool bCantMove, bool bCantTurn, + bool bCantUseItem, bool bDontTerminateInteract, bool bIgnoreImmunity, + bool bCantAttackOutChangeWasApplied, bool bCantEquipOutChangeWasApplied, + bool bCantInteractOutChangeWasApplied, bool bCantJumpOutChangeWasApplied, + bool bCantMoveOutChangeWasApplied, bool bCantTurnOutChangeWasApplied, + bool bCantUseItemOutChangeWasApplied) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::SET_STUNNED); + + bitStream.Write(originator != LWOOBJID_EMPTY); + if (originator != LWOOBJID_EMPTY) bitStream.Write(originator); + + bitStream.Write(stateChangeType); + + bitStream.Write(bCantAttack); + bitStream.Write(bCantAttackOutChangeWasApplied); + + bitStream.Write(bCantEquip); + bitStream.Write(bCantEquipOutChangeWasApplied); + + bitStream.Write(bCantInteract); + bitStream.Write(bCantInteractOutChangeWasApplied); + + bitStream.Write(bCantJump); + bitStream.Write(bCantJumpOutChangeWasApplied); + + bitStream.Write(bCantMove); + bitStream.Write(bCantMoveOutChangeWasApplied); + + bitStream.Write(bCantTurn); + bitStream.Write(bCantTurnOutChangeWasApplied); + + bitStream.Write(bCantUseItem); + bitStream.Write(bCantUseItemOutChangeWasApplied); + + bitStream.Write(bDontTerminateInteract); + + bitStream.Write(bIgnoreImmunity); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendSetStunImmunity(LWOOBJID target, eStateChangeType state, const SystemAddress& sysAddr, + LWOOBJID originator, + bool bImmuneToStunAttack, + bool bImmuneToStunEquip, + bool bImmuneToStunInteract, + bool bImmuneToStunJump, + bool bImmuneToStunMove, + bool bImmuneToStunTurn, + bool bImmuneToStunUseItem) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(target); + bitStream.Write(MessageType::Game::SET_STUN_IMMUNITY); + + bitStream.Write(originator != LWOOBJID_EMPTY); + if (originator != LWOOBJID_EMPTY) bitStream.Write(originator); + + bitStream.Write(state); + + bitStream.Write(bImmuneToStunAttack); + bitStream.Write(bImmuneToStunEquip); + bitStream.Write(bImmuneToStunInteract); + bitStream.Write(bImmuneToStunJump); + bitStream.Write(bImmuneToStunMove); + bitStream.Write(bImmuneToStunTurn); + bitStream.Write(bImmuneToStunUseItem); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendSetStatusImmunity(LWOOBJID objectId, eStateChangeType state, const SystemAddress& sysAddr, + bool bImmuneToBasicAttack, + bool bImmuneToDamageOverTime, + bool bImmuneToKnockback, + bool bImmuneToInterrupt, + bool bImmuneToSpeed, + bool bImmuneToImaginationGain, + bool bImmuneToImaginationLoss, + bool bImmuneToQuickbuildInterrupt, + bool bImmuneToPullToPoint) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::SET_STATUS_IMMUNITY); + + bitStream.Write(state); + + bitStream.Write(bImmuneToBasicAttack); + bitStream.Write(bImmuneToDamageOverTime); + bitStream.Write(bImmuneToKnockback); + bitStream.Write(bImmuneToInterrupt); + bitStream.Write(bImmuneToSpeed); + bitStream.Write(bImmuneToImaginationGain); + bitStream.Write(bImmuneToImaginationLoss); + bitStream.Write(bImmuneToQuickbuildInterrupt); + bitStream.Write(bImmuneToPullToPoint); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendAddRunSpeedModifier(LWOOBJID objectId, LWOOBJID caster, uint32_t modifier, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::ADD_RUN_SPEED_MODIFIER); + + bitStream.Write(caster != LWOOBJID_EMPTY); + if (caster != LWOOBJID_EMPTY) bitStream.Write(caster); + + bitStream.Write(modifier != 500); + if (modifier != 500) bitStream.Write(modifier); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendRemoveRunSpeedModifier(LWOOBJID objectId, uint32_t modifier, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::REMOVE_RUN_SPEED_MODIFIER); + + bitStream.Write(modifier != 500); + if (modifier != 500) bitStream.Write(modifier); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(MessageType::Game::REMOVE_BUFF); + + bitStream.Write(false); // bFromRemoveBehavior but setting this to true makes the GM not do anything on the client? + bitStream.Write(fromUnEquip); + bitStream.Write(removeImmunity); + bitStream.Write(buffId); + + SEND_PACKET_BROADCAST; + } + + inline void SendSetResurrectRestoreValues(Entity* targetEntity, int32_t armorRestore, int32_t healthRestore, int32_t imaginationRestore) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(targetEntity->GetObjectID()); + bitStream.Write(MessageType::Game::SET_RESURRECT_RESTORE_VALUES); + + bitStream.Write(armorRestore != -1); + if (armorRestore != -1) bitStream.Write(armorRestore); + + bitStream.Write(healthRestore != -1); + if (healthRestore != -1) bitStream.Write(healthRestore); + + bitStream.Write(imaginationRestore != -1); + if (imaginationRestore != -1) bitStream.Write(imaginationRestore); + + SEND_PACKET_BROADCAST; + } + + inline void SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uint32_t buffID, uint32_t msDuration, + bool addImmunity, bool cancelOnDamaged, bool cancelOnDeath, bool cancelOnLogout, + bool cancelOnRemoveBuff, bool cancelOnUi, bool cancelOnUnequip, bool cancelOnZone, bool addedByTeammate, bool applyOnTeammates, + const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectID); + bitStream.Write(MessageType::Game::ADD_BUFF); + + bitStream.Write(addedByTeammate); // Added by teammate + bitStream.Write(applyOnTeammates); // Apply on teammates + bitStream.Write(cancelOnDamaged); + bitStream.Write(cancelOnDeath); + bitStream.Write(cancelOnLogout); + + bitStream.Write(false); // Cancel on move + bitStream.Write(cancelOnRemoveBuff); + bitStream.Write(cancelOnUi); + bitStream.Write(cancelOnUnequip); + bitStream.Write(cancelOnZone); + + bitStream.Write(false); // Ignore immunities + bitStream.Write(addImmunity); + bitStream.Write(false); // Use ref count + + bitStream.Write(casterID != LWOOBJID_EMPTY); + if (casterID != LWOOBJID_EMPTY) bitStream.Write(casterID); + + bitStream.Write(buffID); + + bitStream.Write(msDuration != 0); + if (msDuration != 0) bitStream.Write(msDuration); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendDeactivateBubbleBuffFromServer(LWOOBJID objectId, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::DEACTIVATE_BUBBLE_BUFF_FROM_SERVER); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendSmash(Entity* entity, float force, float ghostOpacity, LWOOBJID killerID, bool ignoreObjectVisibility) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(MessageType::Game::SMASH); + + bitStream.Write(ignoreObjectVisibility); + bitStream.Write(force); + bitStream.Write(ghostOpacity); + bitStream.Write(killerID); + + SEND_PACKET_BROADCAST; + } + + inline void SendUnSmash(Entity* entity, LWOOBJID builderID, float duration) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(MessageType::Game::UN_SMASH); + + bitStream.Write(builderID != LWOOBJID_EMPTY); + if (builderID != LWOOBJID_EMPTY) bitStream.Write(builderID); + + bitStream.Write(duration != 3.0f); + if (duration != 3.0f) bitStream.Write(duration); + + SEND_PACKET_BROADCAST; + } + + // GameMessages::HandleRequestDie (the three argument overload the switch called) + struct LegacyRequestDie { bool bClientDeath{}; bool bSpawnLoot{}; std::u16string deathType{}; float directionRelativeAngleXZ{}; float directionRelativeAngleY{}; float directionRelativeForce{}; eKillType killType{}; LWOOBJID killerID{}; LWOOBJID lootOwnerID{}; }; + inline LegacyRequestDie ReadRequestDie(RakNet::BitStream& inStream) { + bool bClientDeath; + bool bSpawnLoot; + std::u16string deathType; + float directionRelativeAngleXZ; + float directionRelativeAngleY; + float directionRelativeForce; + eKillType killType = eKillType::VIOLENT; + LWOOBJID killerID; + LWOOBJID lootOwnerID = LWOOBJID_EMPTY; + + bClientDeath = inStream.ReadBit(); + bSpawnLoot = inStream.ReadBit(); + + uint32_t deathTypeLength = 0; + inStream.Read(deathTypeLength); + + for (size_t i = 0; i < deathTypeLength; i++) { + char16_t character; + inStream.Read(character); + + deathType.push_back(character); + } + + inStream.Read(directionRelativeAngleXZ); + inStream.Read(directionRelativeAngleY); + inStream.Read(directionRelativeForce); + + if (inStream.ReadBit()) { + inStream.Read(killType); + } + + inStream.Read(killerID); + + if (inStream.ReadBit()) { + inStream.Read(lootOwnerID); + } + return { bClientDeath, bSpawnLoot, deathType, directionRelativeAngleXZ, directionRelativeAngleY, directionRelativeForce, killType, killerID, lootOwnerID }; + } + + // GameMessages::HandleResurrect + inline bool ReadResurrect(RakNet::BitStream& inStream) { + bool immediate = inStream.ReadBit(); + return immediate; + } + + // GameMessages::HandleActivateBubbleBuff + struct LegacyActivateBubbleBuff { bool read{}; bool specialAnimations{}; std::u16string type{}; }; + inline LegacyActivateBubbleBuff ReadActivateBubbleBuff(RakNet::BitStream& inStream) { + bool specialAnimations; + if (!inStream.Read(specialAnimations)) return {}; + + std::u16string type = GeneralUtils::ReadWString(inStream); + return { true, specialAnimations, type }; + } +} + +#endif // COMBATMESSAGESLEGACY_H diff --git a/tests/dGameTests/dGameMessagesTests/UnSmashTests.cpp b/tests/dGameTests/dGameMessagesTests/UnSmashTests.cpp index 435b2ebd4..00e50f366 100644 --- a/tests/dGameTests/dGameMessagesTests/UnSmashTests.cpp +++ b/tests/dGameTests/dGameMessagesTests/UnSmashTests.cpp @@ -1,4 +1,5 @@ #include "GameMessages.h" +#include "CombatMessages.h" #include "GameDependencies.h" #include "PacketTestUtils.h"