diff --git a/dCommon/dEnums/eRacingClientNotificationType.h b/dCommon/dEnums/eRacingClientNotificationType.h new file mode 100644 index 000000000..09f7cd1a1 --- /dev/null +++ b/dCommon/dEnums/eRacingClientNotificationType.h @@ -0,0 +1,17 @@ +#ifndef ERACINGCLIENTNOTIFICATIONTYPE_H +#define ERACINGCLIENTNOTIFICATIONTYPE_H + +#include + +// NotifyRacingClient event types, as in the client. Sent on the wire as an int32. +enum class eRacingClientNotificationType : int32_t { + INVALID = 0, + ACTIVITY_START, + REWARD_PLAYER, + EXIT, + REPLAY, + REMOVE_PLAYER, + LEADERBOARD_UPDATED, +}; + +#endif // ERACINGCLIENTNOTIFICATIONTYPE_H diff --git a/dGame/dBehaviors/CarBoostBehavior.cpp b/dGame/dBehaviors/CarBoostBehavior.cpp index fdbcba714..0e402b68e 100644 --- a/dGame/dBehaviors/CarBoostBehavior.cpp +++ b/dGame/dBehaviors/CarBoostBehavior.cpp @@ -1,6 +1,7 @@ #include "CarBoostBehavior.h" #include "BehaviorBranchContext.h" #include "GameMessages.h" +#include "RacingMessages.h" #include "EntityManager.h" #include "BehaviorContext.h" #include "CharacterComponent.h" @@ -9,7 +10,9 @@ #include "PossessableComponent.h" void CarBoostBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStream, BehaviorBranchContext branch) { - GameMessages::SendVehicleAddPassiveBoostAction(branch.target, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::VehicleAddPassiveBoostAction addBoost; + addBoost.target = branch.target; + addBoost.Send(UNASSIGNED_SYSTEM_ADDRESS); auto* entity = Game::entityManager->GetEntity(context->originator); @@ -37,7 +40,9 @@ void CarBoostBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitSt m_Action->Handle(context, bitStream, branch); entity->AddCallbackTimer(m_Time, [entity]() { - GameMessages::SendVehicleRemovePassiveBoostAction(entity->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::VehicleRemovePassiveBoostAction removeBoost; + removeBoost.target = entity->GetObjectID(); + removeBoost.Send(UNASSIGNED_SYSTEM_ADDRESS); }); } diff --git a/dGame/dComponents/PossessorComponent.cpp b/dGame/dComponents/PossessorComponent.cpp index a5a4fcdd6..d95bb79b3 100644 --- a/dGame/dComponents/PossessorComponent.cpp +++ b/dGame/dComponents/PossessorComponent.cpp @@ -4,6 +4,7 @@ #include "HavokVehiclePhysicsComponent.h" #include "EntityManager.h" #include "GameMessages.h" +#include "RacingMessages.h" #include "eUnequippableActiveType.h" PossessorComponent::PossessorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { @@ -60,7 +61,10 @@ void PossessorComponent::Mount(Entity* mount) { if (mount->GetComponent()) { auto characterComponent = m_Parent->GetComponent(); if (characterComponent) characterComponent->SetIsRacing(true); - GameMessages::SendVehicleUnlockInput(mount->GetObjectID(), false, m_Parent->GetSystemAddress()); + GameMessages::VehicleUnlockInput unlockInput; + unlockInput.target = mount->GetObjectID(); + unlockInput.bLockWheels = false; + unlockInput.Send(m_Parent->GetSystemAddress()); } Game::entityManager->SerializeEntity(m_Parent); diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 297a3aac0..9f5e29ddf 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -9,6 +9,7 @@ #include "EntityManager.h" #include "GameMessages.h" #include "ActivityMessages.h" +#include "RacingMessages.h" #include "InventoryComponent.h" #include "Item.h" #include "MissionComponent.h" @@ -226,9 +227,14 @@ void RacingControlComponent::LoadPlayerVehicle(Entity* player, Game::entityManager->SerializeEntity(player); Game::entityManager->SerializeEntity(m_Parent); - GameMessages::SendRacingSetPlayerResetInfo( - m_Parent->GetObjectID(), 0, 0, player->GetObjectID(), startPosition, 1, - UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::RacingSetPlayerResetInfo resetInfo; + resetInfo.target = m_Parent->GetObjectID(); + resetInfo.currentLap = 0; + resetInfo.furthestResetPlane = 0; + resetInfo.playerID = player->GetObjectID(); + resetInfo.respawnPos = startPosition; + resetInfo.upcomingPlane = 1; + resetInfo.Send(UNASSIGNED_SYSTEM_ADDRESS); const auto playerID = player->GetObjectID(); @@ -241,20 +247,25 @@ void RacingControlComponent::LoadPlayerVehicle(Entity* player, return; } - GameMessages::SendRacingResetPlayerToLastReset( - m_Parent->GetObjectID(), playerID, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::RacingResetPlayerToLastReset resetPlayer; + resetPlayer.target = m_Parent->GetObjectID(); + resetPlayer.playerID = playerID; + resetPlayer.Send(UNASSIGNED_SYSTEM_ADDRESS); }); GameMessages::SendSetJetPackMode(player, false); // Set the vehicle's state. - GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), - m_Parent->GetObjectID(), - UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::NotifyVehicleOfRacingObject notifyVehicle; + notifyVehicle.target = carEntity->GetObjectID(); + notifyVehicle.racingObjectID = m_Parent->GetObjectID(); + notifyVehicle.Send(UNASSIGNED_SYSTEM_ADDRESS); - GameMessages::SendVehicleSetWheelLockState(carEntity->GetObjectID(), false, - initialLoad, - UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::VehicleSetWheelLockState wheelLockState; + wheelLockState.target = carEntity->GetObjectID(); + wheelLockState.bExtraFriction = false; + wheelLockState.bLocked = initialLoad; + wheelLockState.Send(UNASSIGNED_SYSTEM_ADDRESS); // Make sure everything has the correct position. GameMessages::SendTeleport(player->GetObjectID(), startPosition, @@ -267,11 +278,14 @@ void RacingControlComponent::OnRacingClientReady(Entity* player) { // Notify the other players that this player is ready. for (auto& racingPlayer : m_RacingPlayers) { + GameMessages::RacingPlayerLoaded playerLoaded; + playerLoaded.target = m_Parent->GetObjectID(); + playerLoaded.playerID = racingPlayer.playerID; + playerLoaded.vehicleID = racingPlayer.vehicleID; + if (racingPlayer.playerID != player->GetObjectID()) { if (racingPlayer.playerLoaded) { - GameMessages::SendRacingPlayerLoaded( - m_Parent->GetObjectID(), racingPlayer.playerID, - racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); + playerLoaded.Send(UNASSIGNED_SYSTEM_ADDRESS); } continue; @@ -279,9 +293,7 @@ void RacingControlComponent::OnRacingClientReady(Entity* player) { racingPlayer.playerLoaded = true; - GameMessages::SendRacingPlayerLoaded( - m_Parent->GetObjectID(), racingPlayer.playerID, - racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); + playerLoaded.Send(UNASSIGNED_SYSTEM_ADDRESS); } Game::entityManager->SerializeEntity(m_Parent); @@ -319,17 +331,24 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string& // Respawn the player in 2 seconds, as was done in live. Not sure if this value is in a setting somewhere else... vehicle->AddCallbackTimer(2.0f, [=, this]() { if (!vehicle || !this->m_Parent) return; - GameMessages::SendRacingResetPlayerToLastReset( - m_Parent->GetObjectID(), racingPlayer.playerID, - UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::RacingResetPlayerToLastReset resetPlayer; + resetPlayer.target = m_Parent->GetObjectID(); + resetPlayer.playerID = racingPlayer.playerID; + resetPlayer.Send(UNASSIGNED_SYSTEM_ADDRESS); - GameMessages::SendVehicleStopBoost(vehicle, player->GetSystemAddress(), true); + GameMessages::VehicleStopBoost stopBoost; + stopBoost.target = vehicle->GetObjectID(); + stopBoost.bAffectPassive = true; + stopBoost.Send(UNASSIGNED_SYSTEM_ADDRESS); // always a broadcast, as before - GameMessages::SendRacingSetPlayerResetInfo( - m_Parent->GetObjectID(), racingPlayer.lap, - racingPlayer.respawnIndex, player->GetObjectID(), - racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1, - UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::RacingSetPlayerResetInfo resetInfo; + resetInfo.target = m_Parent->GetObjectID(); + resetInfo.currentLap = static_cast(racingPlayer.lap); + resetInfo.furthestResetPlane = racingPlayer.respawnIndex; + resetInfo.playerID = player->GetObjectID(); + resetInfo.respawnPos = racingPlayer.respawnPosition; + resetInfo.upcomingPlane = racingPlayer.respawnIndex + 1; + resetInfo.Send(UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendResurrect(vehicle); auto* destroyableComponent = vehicle->GetComponent(); @@ -343,14 +362,19 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string& characterComponent->UpdatePlayerStatistic(RacingTimesWrecked); } } else { - GameMessages::SendRacingSetPlayerResetInfo( - m_Parent->GetObjectID(), racingPlayer.lap, - racingPlayer.respawnIndex, player->GetObjectID(), - racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1, - UNASSIGNED_SYSTEM_ADDRESS); - GameMessages::SendRacingResetPlayerToLastReset( - m_Parent->GetObjectID(), racingPlayer.playerID, - UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::RacingSetPlayerResetInfo resetInfo; + resetInfo.target = m_Parent->GetObjectID(); + resetInfo.currentLap = static_cast(racingPlayer.lap); + resetInfo.furthestResetPlane = racingPlayer.respawnIndex; + resetInfo.playerID = player->GetObjectID(); + resetInfo.respawnPos = racingPlayer.respawnPosition; + resetInfo.upcomingPlane = racingPlayer.respawnIndex + 1; + resetInfo.Send(UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::RacingResetPlayerToLastReset resetPlayer; + resetPlayer.target = m_Parent->GetObjectID(); + resetPlayer.playerID = racingPlayer.playerID; + resetPlayer.Send(UNASSIGNED_SYSTEM_ADDRESS); } } } @@ -398,9 +422,11 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t bu Loot::GiveActivityLoot(player, m_Parent->GetObjectID(), m_ActivityID, score); // Giving rewards - GameMessages::SendNotifyRacingClient( - m_Parent->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"", - player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::NotifyRacingClient notifyRacingClient; + notifyRacingClient.target = m_Parent->GetObjectID(); + notifyRacingClient.eventType = eRacingClientNotificationType::REWARD_PLAYER; + notifyRacingClient.singleClient = player->GetObjectID(); + notifyRacingClient.Send(UNASSIGNED_SYSTEM_ADDRESS); } else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) { auto* vehicle = Game::entityManager->GetEntity(data->vehicleID); @@ -409,9 +435,11 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t bu } // Exiting race - GameMessages::SendNotifyRacingClient( - m_Parent->GetObjectID(), 3, 0, LWOOBJID_EMPTY, u"", - player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::NotifyRacingClient notifyRacingClient; + notifyRacingClient.target = m_Parent->GetObjectID(); + notifyRacingClient.eventType = eRacingClientNotificationType::EXIT; + notifyRacingClient.singleClient = player->GetObjectID(); + notifyRacingClient.Send(UNASSIGNED_SYSTEM_ADDRESS); auto* characterComponent = player->GetComponent(); @@ -630,9 +658,10 @@ void RacingControlComponent::Update(float deltaTime) { if (readyPlayers >= m_LoadedPlayers) { // Setup for racing if (m_StartTimer == 0) { - GameMessages::SendNotifyRacingClient( - m_Parent->GetObjectID(), 1, 0, LWOOBJID_EMPTY, u"", - LWOOBJID_EMPTY, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::NotifyRacingClient notifyRacingClient; + notifyRacingClient.target = m_Parent->GetObjectID(); + notifyRacingClient.eventType = eRacingClientNotificationType::ACTIVITY_START; + notifyRacingClient.Send(UNASSIGNED_SYSTEM_ADDRESS); for (const auto& player : m_RacingPlayers) { auto* vehicle = @@ -696,8 +725,10 @@ void RacingControlComponent::Update(float deltaTime) { continue; } - GameMessages::SendVehicleUnlockInput( - player.vehicleID, false, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::VehicleUnlockInput unlockInput; + unlockInput.target = player.vehicleID; + unlockInput.bLockWheels = false; + unlockInput.Send(UNASSIGNED_SYSTEM_ADDRESS); } // Start the race diff --git a/dGame/dGameMessages/CMakeLists.txt b/dGame/dGameMessages/CMakeLists.txt index 946b84af3..6ac06cf80 100644 --- a/dGame/dGameMessages/CMakeLists.txt +++ b/dGame/dGameMessages/CMakeLists.txt @@ -2,6 +2,7 @@ set(DGAME_DGAMEMESSAGES_SOURCES "ActivityMessages.cpp" "GameMessageHandler.cpp" "GameMessages.cpp" + "RacingMessages.cpp" "PropertyDataMessage.cpp" "PropertySelectQueryProperty.cpp") diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 760eb9124..59a3664fd 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -31,6 +31,7 @@ #include "EchoStartSkill.h" #include "EchoSyncSkill.h" #include "ActivityMessages.h" +#include "RacingMessages.h" #include "eMissionTaskType.h" #include "eReplicaComponentType.h" #include "ServiceType.h" @@ -51,6 +52,16 @@ namespace { { SHOOTING_GALLERY_FIRE, []() { return std::make_unique(); } }, { PICKUP_ITEM, []() { return std::make_unique(); } }, { REQUEST_ACTIVITY_EXIT, []() { return std::make_unique(); } }, + + // Racing + { MODULE_ASSEMBLY_QUERY_DATA, []() { return std::make_unique(); } }, + { VEHICLE_SET_WHEEL_LOCK_STATE, []() { return std::make_unique(); } }, + { MODULAR_ASSEMBLY_NIF_COMPLETED, []() { return std::make_unique(); } }, + { RACING_CLIENT_READY, []() { return std::make_unique(); } }, + { NOTIFY_SERVER_VEHICLE_ADD_PASSIVE_BOOST_ACTION, []() { return std::make_unique(); } }, + { NOTIFY_SERVER_VEHICLE_REMOVE_PASSIVE_BOOST_ACTION, []() { return std::make_unique(); } }, + { RACING_PLAYER_INFO_RESET_FINISHED, []() { return std::make_unique(); } }, + { VEHICLE_NOTIFY_HIT_IMAGINATION_SERVER, []() { return std::make_unique(); } }, }; }; @@ -614,46 +625,14 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System GameMessages::HandleSetPropertyAccess(inStream, entity, sysAddr); break; - // Racing - case MessageType::Game::MODULE_ASSEMBLY_QUERY_DATA: - GameMessages::HandleModuleAssemblyQueryData(inStream, entity, sysAddr); - break; - + // Racing: most racing messages are registered in g_MessageHandlers case MessageType::Game::ACKNOWLEDGE_POSSESSION: GameMessages::HandleAcknowledgePossession(inStream, entity, sysAddr); break; - case MessageType::Game::VEHICLE_SET_WHEEL_LOCK_STATE: - GameMessages::HandleVehicleSetWheelLockState(inStream, entity, sysAddr); - break; - - case MessageType::Game::MODULAR_ASSEMBLY_NIF_COMPLETED: - GameMessages::HandleModularAssemblyNIFCompleted(inStream, entity, sysAddr); - break; - - case MessageType::Game::RACING_CLIENT_READY: - GameMessages::HandleRacingClientReady(inStream, entity, sysAddr); - break; - case MessageType::Game::REQUEST_DIE: GameMessages::HandleRequestDie(inStream, entity, sysAddr); break; - - case MessageType::Game::NOTIFY_SERVER_VEHICLE_ADD_PASSIVE_BOOST_ACTION: - GameMessages::HandleVehicleNotifyServerAddPassiveBoostAction(inStream, entity, sysAddr); - break; - - case MessageType::Game::NOTIFY_SERVER_VEHICLE_REMOVE_PASSIVE_BOOST_ACTION: - GameMessages::HandleVehicleNotifyServerRemovePassiveBoostAction(inStream, entity, sysAddr); - break; - - case MessageType::Game::RACING_PLAYER_INFO_RESET_FINISHED: - GameMessages::HandleRacingPlayerInfoResetFinished(inStream, entity, sysAddr); - break; - - case MessageType::Game::VEHICLE_NOTIFY_HIT_IMAGINATION_SERVER: - GameMessages::HandleVehicleNotifyHitImaginationServer(inStream, entity, sysAddr); - break; case MessageType::Game::UPDATE_PROPERTY_PERFORMANCE_COST: GameMessages::HandleUpdatePropertyPerformanceCost(inStream, entity, sysAddr); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index d76edc510..9f57d5555 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -3974,51 +3974,8 @@ void GameMessages::HandleAcknowledgePossession(RakNet::BitStream& inStream, Enti //Racing -void GameMessages::HandleModuleAssemblyQueryData(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - auto* moduleAssemblyComponent = entity->GetComponent(); - - LOG("Got Query from %i", entity->GetLOT()); - - if (moduleAssemblyComponent != nullptr) { - LOG("Returning assembly %s", GeneralUtils::UTF16ToWTF8(moduleAssemblyComponent->GetAssemblyPartsLOTs()).c_str()); - - SendModuleAssemblyDBDataForClient(entity->GetObjectID(), moduleAssemblyComponent->GetSubKey(), moduleAssemblyComponent->GetAssemblyPartsLOTs(), UNASSIGNED_SYSTEM_ADDRESS); - } -} -void GameMessages::HandleModularAssemblyNIFCompleted(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - LWOOBJID objectID; - - inStream.Read(objectID); -} - - -void GameMessages::HandleVehicleSetWheelLockState(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - bool bExtraFriction = inStream.ReadBit(); - bool bLocked = inStream.ReadBit(); -} - - -void GameMessages::HandleRacingClientReady(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - LWOOBJID playerID; - - inStream.Read(playerID); - - auto* player = Game::entityManager->GetEntity(playerID); - - if (player == nullptr) { - return; - } - - auto* racingControlComponent = Game::zoneManager->GetZoneControlObject()->GetComponent(); - - if (racingControlComponent == nullptr) { - return; - } - - racingControlComponent->OnRacingClientReady(player); -} void GameMessages::HandleRequestDie(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -4087,38 +4044,8 @@ void GameMessages::HandleRequestDie(RakNet::BitStream& inStream, Entity* entity, } -void GameMessages::HandleVehicleNotifyServerAddPassiveBoostAction(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - //SendVehicleAddPassiveBoostAction(entity->GetObjectID(), sysAddr); -} -void GameMessages::HandleVehicleNotifyServerRemovePassiveBoostAction(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - //SendVehicleRemovePassiveBoostAction(entity->GetObjectID(), sysAddr); -} - - -void GameMessages::HandleRacingPlayerInfoResetFinished(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - LWOOBJID playerID; - - inStream.Read(playerID); - - auto* player = Game::entityManager->GetEntity(playerID); - - if (player == nullptr) { - return; - } - - auto* zoneController = Game::zoneManager->GetZoneControlObject(); - - auto* racingControlComponent = zoneController->GetComponent(); - - LOG("Got finished: %i", entity->GetLOT()); - - if (racingControlComponent != nullptr) { - racingControlComponent->OnRacingPlayerInfoResetFinished(player); - } -} - void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -4153,164 +4080,12 @@ void GameMessages::HandleUpdatePropertyPerformanceCost(RakNet::BitStream& inStre } } -void GameMessages::HandleVehicleNotifyHitImaginationServer(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - LWOOBJID pickupObjID = LWOOBJID_EMPTY; - LWOOBJID pickupSpawnerID = LWOOBJID_EMPTY; - int32_t pickupSpawnerIndex = -1; - NiPoint3 vehiclePosition = NiPoint3Constant::ZERO; - - if (inStream.ReadBit()) inStream.Read(pickupObjID); - if (inStream.ReadBit()) inStream.Read(pickupSpawnerID); - if (inStream.ReadBit()) inStream.Read(pickupSpawnerIndex); - if (inStream.ReadBit()) inStream.Read(vehiclePosition); - - auto* pickup = Game::entityManager->GetEntity(pickupObjID); - - if (pickup == nullptr) { - return; - } - - auto* possessableComponent = entity->GetComponent(); - - if (possessableComponent != nullptr) { - entity = Game::entityManager->GetEntity(possessableComponent->GetPossessor()); - - if (entity == nullptr) { - return; - } - } - - auto* characterComponent = entity->GetComponent(); - if (characterComponent != nullptr) { - characterComponent->UpdatePlayerStatistic(RacingImaginationPowerUpsCollected); - } - - pickup->OnFireEventServerSide(entity, "powerup"); - - pickup->Kill(entity); -} -void GameMessages::SendModuleAssemblyDBDataForClient(LWOOBJID objectId, LWOOBJID assemblyID, const std::u16string& data, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::MODULE_ASSEMBLY_DB_DATA_FOR_CLIENT); - - bitStream.Write(assemblyID); - - bitStream.Write(data.size()); - for (auto character : data) { - bitStream.Write(character); - } - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} -void GameMessages::SendNotifyVehicleOfRacingObject(LWOOBJID objectId, LWOOBJID racingObjectID, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::NOTIFY_VEHICLE_OF_RACING_OBJECT); - - bitStream.Write(racingObjectID != LWOOBJID_EMPTY); - if (racingObjectID != LWOOBJID_EMPTY) bitStream.Write(racingObjectID); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} -void GameMessages::SendRacingPlayerLoaded(LWOOBJID objectId, LWOOBJID playerID, LWOOBJID vehicleID, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::RACING_PLAYER_LOADED); - - bitStream.Write(playerID); - bitStream.Write(vehicleID); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendVehicleUnlockInput(LWOOBJID objectId, bool bLockWheels, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::VEHICLE_UNLOCK_INPUT); - - bitStream.Write(bLockWheels); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendVehicleSetWheelLockState(LWOOBJID objectId, bool bExtraFriction, bool bLocked, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::VEHICLE_SET_WHEEL_LOCK_STATE); - - bitStream.Write(bExtraFriction); - bitStream.Write(bLocked); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendRacingSetPlayerResetInfo(LWOOBJID objectId, int32_t currentLap, uint32_t furthestResetPlane, LWOOBJID playerID, NiPoint3 respawnPos, uint32_t upcomingPlane, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::RACING_SET_PLAYER_RESET_INFO); - - bitStream.Write(currentLap); - bitStream.Write(furthestResetPlane); - bitStream.Write(playerID); - bitStream.Write(respawnPos); - bitStream.Write(upcomingPlane); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendRacingResetPlayerToLastReset(LWOOBJID objectId, LWOOBJID playerID, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::RACING_RESET_PLAYER_TO_LAST_RESET); - - bitStream.Write(playerID); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - -void GameMessages::SendVehicleStopBoost(Entity* targetEntity, const SystemAddress& playerSysAddr, bool affectPassive) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(targetEntity->GetObjectID()); - bitStream.Write(MessageType::Game::VEHICLE_STOP_BOOST); - - bitStream.Write(affectPassive); - - SEND_PACKET_BROADCAST; -} void GameMessages::SendSetResurrectRestoreValues(Entity* targetEntity, int32_t armorRestore, int32_t healthRestore, int32_t imaginationRestore) { CBITSTREAM; @@ -4331,70 +4106,12 @@ void GameMessages::SendSetResurrectRestoreValues(Entity* targetEntity, int32_t a SEND_PACKET_BROADCAST; } -void GameMessages::SendNotifyRacingClient(LWOOBJID objectId, int32_t eventType, int32_t param1, LWOOBJID paramObj, std::u16string paramStr, LWOOBJID singleClient, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::NOTIFY_RACING_CLIENT); - - bitStream.Write(eventType != 0); - if (eventType != 0) bitStream.Write(eventType); - - bitStream.Write(param1); - - bitStream.Write(paramObj); - - bitStream.Write(paramStr.size()); - for (auto character : paramStr) { - bitStream.Write(character); - } - - bitStream.Write(singleClient); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} -void GameMessages::SendVehicleAddPassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::VEHICLE_ADD_PASSIVE_BOOST_ACTION); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendVehicleRemovePassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::VEHICLE_REMOVE_PASSIVE_BOOST_ACTION); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - - -void GameMessages::SendVehicleNotifyFinishedRace(LWOOBJID objectId, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::VEHICLE_NOTIFY_FINISHED_RACE); - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} void GameMessages::SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uint32_t buffID, uint32_t msDuration, bool addImmunity, bool cancelOnDamaged, bool cancelOnDeath, bool cancelOnLogout, diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 40c1a9699..9c4aa8a5f 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -134,8 +134,6 @@ namespace GameMessages { 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); - // https://lcdruniverse.org/lu_packets/lu_packets/world/gm/client/struct.VehicleStopBoost.html - void SendVehicleStopBoost(Entity* targetEntity, const SystemAddress& playerSysAddr, bool affectPassive); void SendStartArrangingWithItem( Entity* entity, const SystemAddress& sysAddr, @@ -584,40 +582,8 @@ namespace GameMessages { void SendGetHotPropertyData(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); //Racing: - void HandleModuleAssemblyQueryData(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleModularAssemblyNIFCompleted(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleVehicleSetWheelLockState(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleRacingClientReady(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - void HandleRequestDie(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - void HandleVehicleNotifyServerAddPassiveBoostAction(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleVehicleNotifyServerRemovePassiveBoostAction(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleRacingPlayerInfoResetFinished(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void HandleVehicleNotifyHitImaginationServer(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); - - void SendModuleAssemblyDBDataForClient(LWOOBJID objectId, LWOOBJID assemblyID, const std::u16string& data, const SystemAddress& sysAddr); - - void SendNotifyVehicleOfRacingObject(LWOOBJID objectId, LWOOBJID racingObjectID, const SystemAddress& sysAddr); - - void SendRacingPlayerLoaded(LWOOBJID objectId, LWOOBJID playerID, LWOOBJID vehicleID, const SystemAddress& sysAddr); - - void SendVehicleUnlockInput(LWOOBJID objectId, bool bLockWheels, const SystemAddress& sysAddr); - - void SendVehicleSetWheelLockState(LWOOBJID objectId, bool bExtraFriction, bool bLocked, const SystemAddress& sysAddr); - - void SendRacingSetPlayerResetInfo(LWOOBJID objectId, int32_t currentLap, uint32_t furthestResetPlane, LWOOBJID playerID, NiPoint3 respawnPos, uint32_t upcomingPlane, const SystemAddress& sysAddr); - - void SendRacingResetPlayerToLastReset(LWOOBJID objectId, LWOOBJID playerID, const SystemAddress& sysAddr); - - void SendNotifyRacingClient(LWOOBJID objectId, int32_t eventType, int32_t param1, LWOOBJID paramObj, std::u16string paramStr, LWOOBJID singleClient, const SystemAddress& sysAddr); - void SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, eUseItemResponse itemResponse); // SG: @@ -657,12 +623,6 @@ namespace GameMessages { void HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandleActivityStateChangeRequest(RakNet::BitStream& inStream, Entity* entity); - void SendVehicleAddPassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr); - - void SendVehicleRemovePassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr); - - void SendVehicleNotifyFinishedRace(LWOOBJID objectId, const SystemAddress& sysAddr); - //NT: void HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); diff --git a/dGame/dGameMessages/RacingMessages.cpp b/dGame/dGameMessages/RacingMessages.cpp new file mode 100644 index 000000000..32b7679cb --- /dev/null +++ b/dGame/dGameMessages/RacingMessages.cpp @@ -0,0 +1,239 @@ +#include "RacingMessages.h" + +#include "BitStreamUtils.h" +#include "CharacterComponent.h" +#include "Entity.h" +#include "EntityManager.h" +#include "Game.h" +#include "GeneralUtils.h" +#include "Logger.h" +#include "ModuleAssemblyComponent.h" +#include "PossessableComponent.h" +#include "RacingControlComponent.h" +#include "dZoneManager.h" + +namespace GameMessages { + void ModuleAssemblyDBDataForClient::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(assemblyID); + BitStreamUtils::WriteLengthPrefixed(bitStream, blob); + } + + bool ModuleAssemblyDBDataForClient::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(assemblyID)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, blob)); + return true; + } + + void ModuleAssemblyQueryData::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* moduleAssemblyComponent = entity.GetComponent(); + + LOG("Got Query from %i", entity.GetLOT()); + + if (moduleAssemblyComponent != nullptr) { + LOG("Returning assembly %s", GeneralUtils::UTF16ToWTF8(moduleAssemblyComponent->GetAssemblyPartsLOTs()).c_str()); + + ModuleAssemblyDBDataForClient response; + response.target = entity.GetObjectID(); + response.assemblyID = moduleAssemblyComponent->GetSubKey(); + response.blob = moduleAssemblyComponent->GetAssemblyPartsLOTs(); + response.Send(UNASSIGNED_SYSTEM_ADDRESS); + } + } + + void ModularAssemblyNIFCompleted::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(objectID); + } + + bool ModularAssemblyNIFCompleted::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(objectID)); + return true; + } + + void NotifyVehicleOfRacingObject::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, racingObjectID, LWOOBJID_EMPTY); + } + + bool NotifyVehicleOfRacingObject::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, racingObjectID, LWOOBJID_EMPTY)); + return true; + } + + void RacingPlayerLoaded::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(playerID); + bitStream.Write(vehicleID); + } + + bool RacingPlayerLoaded::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(playerID)); + VALIDATE_READ(bitStream.Read(vehicleID)); + return true; + } + + void VehicleUnlockInput::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bLockWheels); + } + + bool VehicleUnlockInput::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bLockWheels)); + return true; + } + + void VehicleSetWheelLockState::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bExtraFriction); + bitStream.Write(bLocked); + } + + bool VehicleSetWheelLockState::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bExtraFriction)); + VALIDATE_READ(bitStream.Read(bLocked)); + return true; + } + + void RacingSetPlayerResetInfo::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(currentLap); + bitStream.Write(furthestResetPlane); + bitStream.Write(playerID); + bitStream.Write(respawnPos); + bitStream.Write(upcomingPlane); + } + + bool RacingSetPlayerResetInfo::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(currentLap)); + VALIDATE_READ(bitStream.Read(furthestResetPlane)); + VALIDATE_READ(bitStream.Read(playerID)); + VALIDATE_READ(bitStream.Read(respawnPos)); + VALIDATE_READ(bitStream.Read(upcomingPlane)); + return true; + } + + void RacingResetPlayerToLastReset::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(playerID); + } + + bool RacingResetPlayerToLastReset::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(playerID)); + return true; + } + + void VehicleStopBoost::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(bAffectPassive); + } + + bool VehicleStopBoost::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(bAffectPassive)); + return true; + } + + void NotifyRacingClient::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, eventType, eRacingClientNotificationType::INVALID); + bitStream.Write(param1); + bitStream.Write(paramObj); + BitStreamUtils::WriteLengthPrefixed(bitStream, paramStr); + bitStream.Write(singleClient); + } + + bool NotifyRacingClient::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, eventType, eRacingClientNotificationType::INVALID)); + VALIDATE_READ(bitStream.Read(param1)); + VALIDATE_READ(bitStream.Read(paramObj)); + VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, paramStr)); + VALIDATE_READ(bitStream.Read(singleClient)); + return true; + } + + void RacingClientReady::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(playerID); + } + + bool RacingClientReady::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(playerID)); + return true; + } + + void RacingClientReady::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* player = Game::entityManager->GetEntity(playerID); + + if (player == nullptr) { + return; + } + + auto* racingControlComponent = Game::zoneManager->GetZoneControlObject()->GetComponent(); + + if (racingControlComponent == nullptr) { + return; + } + + racingControlComponent->OnRacingClientReady(player); + } + + void RacingPlayerInfoResetFinished::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(playerID); + } + + bool RacingPlayerInfoResetFinished::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(playerID)); + return true; + } + + void RacingPlayerInfoResetFinished::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* player = Game::entityManager->GetEntity(playerID); + + if (player == nullptr) { + return; + } + + auto* zoneController = Game::zoneManager->GetZoneControlObject(); + + auto* racingControlComponent = zoneController->GetComponent(); + + LOG("Got finished: %i", entity.GetLOT()); + + if (racingControlComponent != nullptr) { + racingControlComponent->OnRacingPlayerInfoResetFinished(player); + } + } + + void VehicleNotifyHitImaginationServer::Serialize(RakNet::BitStream& bitStream) const { + BitStreamUtils::WriteOptional(bitStream, pickupObjID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, pickupSpawnerID, LWOOBJID_EMPTY); + BitStreamUtils::WriteOptional(bitStream, pickupSpawnerIndex, -1); + BitStreamUtils::WriteOptional(bitStream, vehiclePosition, NiPoint3Constant::ZERO); + } + + bool VehicleNotifyHitImaginationServer::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, pickupObjID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, pickupSpawnerID, LWOOBJID_EMPTY)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, pickupSpawnerIndex, -1)); + VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, vehiclePosition, NiPoint3Constant::ZERO)); + return true; + } + + void VehicleNotifyHitImaginationServer::Handle(Entity& vehicle, const SystemAddress& sysAddr) { + auto* pickup = Game::entityManager->GetEntity(pickupObjID); + + if (pickup == nullptr) { + return; + } + + // The sender is the vehicle; credit its driver if it has one. + Entity* entity = &vehicle; + auto* possessableComponent = entity->GetComponent(); + + if (possessableComponent != nullptr) { + entity = Game::entityManager->GetEntity(possessableComponent->GetPossessor()); + + if (entity == nullptr) { + return; + } + } + + auto* characterComponent = entity->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->UpdatePlayerStatistic(RacingImaginationPowerUpsCollected); + } + + pickup->OnFireEventServerSide(entity, "powerup"); + + pickup->Kill(entity); + } +} diff --git a/dGame/dGameMessages/RacingMessages.h b/dGame/dGameMessages/RacingMessages.h new file mode 100644 index 000000000..10e68e4c4 --- /dev/null +++ b/dGame/dGameMessages/RacingMessages.h @@ -0,0 +1,179 @@ +#ifndef RACINGMESSAGES_H +#define RACINGMESSAGES_H + +#include "GameMessages.h" +#include "NiPoint3.h" +#include "eRacingClientNotificationType.h" + +#include + +// Game messages for racing, vehicles and modular assembly (car building). +// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order. +namespace GameMessages { + // Server -> client. + struct ModuleAssemblyDBDataForClient : public NetGameMsg { + ModuleAssemblyDBDataForClient() : NetGameMsg(MessageType::Game::MODULE_ASSEMBLY_DB_DATA_FOR_CLIENT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID assemblyID{}; + std::u16string blob{}; + }; + + // Client -> server. No payload. + struct ModuleAssemblyQueryData : public NetGameMsg { + ModuleAssemblyQueryData() : NetGameMsg(MessageType::Game::MODULE_ASSEMBLY_QUERY_DATA) {} + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + }; + + // Client -> server. + struct ModularAssemblyNIFCompleted : public NetGameMsg { + ModularAssemblyNIFCompleted() : NetGameMsg(MessageType::Game::MODULAR_ASSEMBLY_NIF_COMPLETED) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID objectID{}; + }; + + // Server -> client. + struct NotifyVehicleOfRacingObject : public NetGameMsg { + NotifyVehicleOfRacingObject() : NetGameMsg(MessageType::Game::NOTIFY_VEHICLE_OF_RACING_OBJECT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID racingObjectID{ LWOOBJID_EMPTY }; // optional + }; + + // Server -> client. + struct RacingPlayerLoaded : public NetGameMsg { + RacingPlayerLoaded() : NetGameMsg(MessageType::Game::RACING_PLAYER_LOADED) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID playerID{}; + LWOOBJID vehicleID{}; + }; + + // Server -> client. + struct VehicleUnlockInput : public NetGameMsg { + VehicleUnlockInput() : NetGameMsg(MessageType::Game::VEHICLE_UNLOCK_INPUT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bLockWheels{}; + }; + + // Both directions. DLU ignores it when received. + struct VehicleSetWheelLockState : public NetGameMsg { + VehicleSetWheelLockState() : NetGameMsg(MessageType::Game::VEHICLE_SET_WHEEL_LOCK_STATE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bExtraFriction{}; + bool bLocked{}; + }; + + // Server -> client. + struct RacingSetPlayerResetInfo : public NetGameMsg { + RacingSetPlayerResetInfo() : NetGameMsg(MessageType::Game::RACING_SET_PLAYER_RESET_INFO) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + int32_t currentLap{}; + uint32_t furthestResetPlane{}; + LWOOBJID playerID{}; + NiPoint3 respawnPos{}; + uint32_t upcomingPlane{}; + }; + + // Server -> client. + struct RacingResetPlayerToLastReset : public NetGameMsg { + RacingResetPlayerToLastReset() : NetGameMsg(MessageType::Game::RACING_RESET_PLAYER_TO_LAST_RESET) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + LWOOBJID playerID{}; + }; + + // Server -> client. + struct VehicleStopBoost : public NetGameMsg { + VehicleStopBoost() : NetGameMsg(MessageType::Game::VEHICLE_STOP_BOOST) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + bool bAffectPassive{}; + }; + + // Server -> client. + struct NotifyRacingClient : public NetGameMsg { + NotifyRacingClient() : NetGameMsg(MessageType::Game::NOTIFY_RACING_CLIENT) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + + eRacingClientNotificationType eventType{ eRacingClientNotificationType::INVALID }; // optional + int32_t param1{}; + LWOOBJID paramObj{}; + std::u16string paramStr{}; + LWOOBJID singleClient{}; + }; + + // Server -> client. No payload. + struct VehicleAddPassiveBoostAction : public NetGameMsg { + VehicleAddPassiveBoostAction() : NetGameMsg(MessageType::Game::VEHICLE_ADD_PASSIVE_BOOST_ACTION) {} + }; + + // Server -> client. No payload. + struct VehicleRemovePassiveBoostAction : public NetGameMsg { + VehicleRemovePassiveBoostAction() : NetGameMsg(MessageType::Game::VEHICLE_REMOVE_PASSIVE_BOOST_ACTION) {} + }; + + // Server -> client. No payload. + struct VehicleNotifyFinishedRace : public NetGameMsg { + VehicleNotifyFinishedRace() : NetGameMsg(MessageType::Game::VEHICLE_NOTIFY_FINISHED_RACE) {} + }; + + // Client -> server. No payload. DLU ignores it. + struct VehicleNotifyServerAddPassiveBoostAction : public NetGameMsg { + VehicleNotifyServerAddPassiveBoostAction() : NetGameMsg(MessageType::Game::NOTIFY_SERVER_VEHICLE_ADD_PASSIVE_BOOST_ACTION) {} + }; + + // Client -> server. No payload. DLU ignores it. + struct VehicleNotifyServerRemovePassiveBoostAction : public NetGameMsg { + VehicleNotifyServerRemovePassiveBoostAction() : NetGameMsg(MessageType::Game::NOTIFY_SERVER_VEHICLE_REMOVE_PASSIVE_BOOST_ACTION) {} + }; + + // Client -> server. + struct RacingClientReady : public NetGameMsg { + RacingClientReady() : NetGameMsg(MessageType::Game::RACING_CLIENT_READY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + LWOOBJID playerID{}; + }; + + // Client -> server. + struct RacingPlayerInfoResetFinished : public NetGameMsg { + RacingPlayerInfoResetFinished() : NetGameMsg(MessageType::Game::RACING_PLAYER_INFO_RESET_FINISHED) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + LWOOBJID playerID{}; + }; + + // Client -> server. Sent when a vehicle drives through an imagination pickup. + struct VehicleNotifyHitImaginationServer : public NetGameMsg { + VehicleNotifyHitImaginationServer() : NetGameMsg(MessageType::Game::VEHICLE_NOTIFY_HIT_IMAGINATION_SERVER) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + LWOOBJID pickupObjID{ LWOOBJID_EMPTY }; // optional + LWOOBJID pickupSpawnerID{ LWOOBJID_EMPTY }; // optional + int32_t pickupSpawnerIndex{ -1 }; // optional + NiPoint3 vehiclePosition{ NiPoint3Constant::ZERO }; // optional + }; +}; + +#endif // RACINGMESSAGES_H diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 54b0be853..2a6ed24ca 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -6,6 +6,7 @@ #include "AssetManager.h" #include "Character.h" #include "ChatPackets.h" +#include "RacingMessages.h" #include "dConfig.h" #include "dNavMesh.h" #include "dpWorld.h" @@ -1176,14 +1177,20 @@ namespace DEVGMCommands { ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time."); return; } else { - GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::VehicleAddPassiveBoostAction addBoost; + addBoost.target = vehicle->GetObjectID(); + addBoost.Send(UNASSIGNED_SYSTEM_ADDRESS); entity->AddCallbackTimer(time.value(), [vehicle]() { if (!vehicle) return; - GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::VehicleRemovePassiveBoostAction removeBoost; + removeBoost.target = vehicle->GetObjectID(); + removeBoost.Send(UNASSIGNED_SYSTEM_ADDRESS); }); } } else { - GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::VehicleAddPassiveBoostAction addBoost; + addBoost.target = vehicle->GetObjectID(); + addBoost.Send(UNASSIGNED_SYSTEM_ADDRESS); } } @@ -1194,7 +1201,9 @@ namespace DEVGMCommands { auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); if (vehicle == nullptr) return; - GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::VehicleRemovePassiveBoostAction removeBoost; + removeBoost.target = vehicle->GetObjectID(); + removeBoost.Send(UNASSIGNED_SYSTEM_ADDRESS); } void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { diff --git a/tests/dGameTests/dGameMessagesTests/ActivityMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/ActivityMessagesTests.cpp index bdcf41034..66afac67b 100644 --- a/tests/dGameTests/dGameMessagesTests/ActivityMessagesTests.cpp +++ b/tests/dGameTests/dGameMessagesTests/ActivityMessagesTests.cpp @@ -1,6 +1,6 @@ #include "ActivityMessages.h" #include "GameDependencies.h" -#include "PacketTestUtils.h" +#include "GameMessageTestUtils.h" #include "Legacy/ActivityMessagesLegacy.h" #include @@ -10,60 +10,7 @@ #include -using namespace PacketTestUtils; - -namespace { - SystemAddress ClientAddress() { - SystemAddress address; - address.binaryAddress = 0x0100007f; - address.port = 2003; - return address; - } - - const std::array g_Targets = { LWOOBJID_EMPTY, 0x1000000000000001LL, 0x0102030405060708LL }; - const std::array g_Addresses = { ClientAddress(), UNASSIGNED_SYSTEM_ADDRESS }; - - PacketBytes StructPacket(const GameMessages::NetGameMsg& msg) { - RakNet::BitStream bitStream; - msg.WritePacket(bitStream); - return FromBitStream(bitStream); - } - - // Sends the same message through the frozen legacy function and through the struct, to one client and as a - // broadcast, and requires identical bytes and the same effective destination. - void ExpectSameAsLegacy(const std::function& legacySend, const GameMessages::NetGameMsg& msg) { - for (const auto& address : g_Addresses) { - SCOPED_TRACE(address == UNASSIGNED_SYSTEM_ADDRESS ? "broadcast" : "single client"); - const auto legacyPackets = Capture([&] { legacySend(address); }); - const auto newPackets = Capture([&] { msg.Send(address); }); - - ASSERT_FALSE(legacyPackets.empty()); - ASSERT_EQ(newPackets.size(), 1); - // The legacy broadcast path also did a second Send(UNASSIGNED_SYSTEM_ADDRESS, broadcast = false), - // which RakPeer::Send rejects without sending anything. Every copy must still match byte for byte. - for (const auto& legacyPacket : legacyPackets) { - EXPECT_PACKET_EQ(FromCapture(legacyPacket), FromCapture(newPackets[0])); - } - EXPECT_EQ(legacyPackets[0].broadcast, newPackets[0].broadcast); - EXPECT_EQ(legacyPackets[0].sysAddr, newPackets[0].sysAddr); - EXPECT_PACKET_EQ(FromCapture(newPackets[0]), StructPacket(msg)); - } - } - - // Serializes msg, reads it back into a fresh T and checks the fresh copy serializes to the same bytes. - template - T RoundTrip(const T& msg) { - RakNet::BitStream bitStream; - msg.Serialize(bitStream); - T copy; - EXPECT_TRUE(copy.Deserialize(bitStream)); - EXPECT_EQ(bitStream.GetNumberOfUnreadBits(), 0); - RakNet::BitStream again; - copy.Serialize(again); - EXPECT_PACKET_EQ(FromBitStream(bitStream), FromBitStream(again)); - return copy; - } -} +using namespace GameMessageTestUtils; class ActivityMessagesTests : public GameDependenciesTest { protected: diff --git a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt index b8c45be4e..487e7b038 100644 --- a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt +++ b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt @@ -2,6 +2,7 @@ SET(DGAMEMESSAGES_TESTS "ActivityMessagesTests.cpp" "GameMessageTests.cpp" "GameMsgSplitTests.cpp" + "RacingMessagesTests.cpp" "UnSmashTests.cpp" "LegacyGameMessageTests.cpp") diff --git a/tests/dGameTests/dGameMessagesTests/GameMessageTestUtils.h b/tests/dGameTests/dGameMessagesTests/GameMessageTestUtils.h new file mode 100644 index 000000000..c9c323c61 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/GameMessageTestUtils.h @@ -0,0 +1,86 @@ +#ifndef GAMEMESSAGETESTUTILS_H +#define GAMEMESSAGETESTUTILS_H + +// Shared helpers for the byte-equality tests of converted game messages (see docs/PacketArchitecture.md). + +#include "GameMessages.h" +#include "PacketTestUtils.h" + +#include +#include + +#include + +namespace GameMessageTestUtils { + using namespace PacketTestUtils; + + inline SystemAddress ClientAddress() { + SystemAddress address; + address.binaryAddress = 0x0100007f; + address.port = 2003; + return address; + } + + // Synthetic object IDs: empty, a player-style ID and one with every byte different. + inline const std::array g_Targets = { LWOOBJID_EMPTY, 0x1000000000000001LL, 0x0102030405060708LL }; + inline const std::array g_Addresses = { ClientAddress(), UNASSIGNED_SYSTEM_ADDRESS }; + + // The complete packet msg.Send(sysAddr) puts on the wire. + inline PacketBytes StructPacket(const GameMessages::NetGameMsg& msg) { + RakNet::BitStream bitStream; + msg.WritePacket(bitStream); + return FromBitStream(bitStream); + } + + // Sends the same message through the frozen legacy function and through the struct, to one client and as a + // broadcast, and requires identical bytes and the same effective destination. + // legacySend receives the address to send to. + inline void ExpectSameAsLegacy(const std::function& legacySend, const GameMessages::NetGameMsg& msg) { + for (const auto& address : g_Addresses) { + SCOPED_TRACE(address == UNASSIGNED_SYSTEM_ADDRESS ? "broadcast" : "single client"); + const auto legacyPackets = Capture([&] { legacySend(address); }); + const auto newPackets = Capture([&] { msg.Send(address); }); + + ASSERT_FALSE(legacyPackets.empty()); + ASSERT_EQ(newPackets.size(), 1); + // The legacy broadcast path also did a second Send(UNASSIGNED_SYSTEM_ADDRESS, broadcast = false), + // which RakPeer::Send rejects without sending anything. Every copy must still match byte for byte. + for (const auto& legacyPacket : legacyPackets) { + EXPECT_PACKET_EQ(FromCapture(legacyPacket), FromCapture(newPackets[0])); + } + EXPECT_EQ(legacyPackets[0].broadcast, newPackets[0].broadcast); + EXPECT_EQ(legacyPackets[0].sysAddr, newPackets[0].sysAddr); + EXPECT_PACKET_EQ(FromCapture(newPackets[0]), StructPacket(msg)); + } + } + + // Serializes msg, reads it back into a fresh T and checks the copy consumed every bit and serializes to the + // same bytes. Returns the copy so callers can compare fields. + template + T RoundTrip(const T& msg) { + RakNet::BitStream bitStream; + msg.Serialize(bitStream); + T copy; + EXPECT_TRUE(copy.Deserialize(bitStream)); + EXPECT_EQ(bitStream.GetNumberOfUnreadBits(), 0); + RakNet::BitStream again; + copy.Serialize(again); + EXPECT_PACKET_EQ(FromBitStream(bitStream), FromBitStream(again)); + return copy; + } + + // Checks that every strict prefix of msg's serialized payload fails to deserialize. + template + void ExpectTruncatedFails(const T& msg) { + RakNet::BitStream full; + msg.Serialize(full); + for (uint32_t bits = 0; bits < full.GetNumberOfBitsUsed(); bits++) { + RakNet::BitStream prefix; + prefix.WriteBits(full.GetData(), bits, false); + T copy; + EXPECT_FALSE(copy.Deserialize(prefix)) << "deserialized from only " << bits << " bits"; + } + } +} + +#endif // GAMEMESSAGETESTUTILS_H diff --git a/tests/dGameTests/dGameMessagesTests/Legacy/RacingMessagesLegacy.h b/tests/dGameTests/dGameMessagesTests/Legacy/RacingMessagesLegacy.h new file mode 100644 index 000000000..60fa011e5 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/Legacy/RacingMessagesLegacy.h @@ -0,0 +1,240 @@ +#ifndef RACINGMESSAGESLEGACY_H +#define RACINGMESSAGESLEGACY_H + +// FROZEN ORACLE - DO NOT EDIT. +// Verbatim copies of the hand written GameMessages::Send* functions that RacingMessages.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 "Entity.h" +#include "Game.h" +#include "MessageType/Client.h" +#include "MessageType/Game.h" +#include "NiPoint3.h" +#include "ServiceType.h" + +#include + +namespace LegacyGameMessages { + inline void SendModuleAssemblyDBDataForClient(LWOOBJID objectId, LWOOBJID assemblyID, const std::u16string& data, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::MODULE_ASSEMBLY_DB_DATA_FOR_CLIENT); + + bitStream.Write(assemblyID); + + bitStream.Write(data.size()); + for (auto character : data) { + bitStream.Write(character); + } + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendNotifyVehicleOfRacingObject(LWOOBJID objectId, LWOOBJID racingObjectID, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::NOTIFY_VEHICLE_OF_RACING_OBJECT); + + bitStream.Write(racingObjectID != LWOOBJID_EMPTY); + if (racingObjectID != LWOOBJID_EMPTY) bitStream.Write(racingObjectID); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendRacingPlayerLoaded(LWOOBJID objectId, LWOOBJID playerID, LWOOBJID vehicleID, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::RACING_PLAYER_LOADED); + + bitStream.Write(playerID); + bitStream.Write(vehicleID); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendVehicleUnlockInput(LWOOBJID objectId, bool bLockWheels, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::VEHICLE_UNLOCK_INPUT); + + bitStream.Write(bLockWheels); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendVehicleSetWheelLockState(LWOOBJID objectId, bool bExtraFriction, bool bLocked, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::VEHICLE_SET_WHEEL_LOCK_STATE); + + bitStream.Write(bExtraFriction); + bitStream.Write(bLocked); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendRacingSetPlayerResetInfo(LWOOBJID objectId, int32_t currentLap, uint32_t furthestResetPlane, LWOOBJID playerID, NiPoint3 respawnPos, uint32_t upcomingPlane, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::RACING_SET_PLAYER_RESET_INFO); + + bitStream.Write(currentLap); + bitStream.Write(furthestResetPlane); + bitStream.Write(playerID); + bitStream.Write(respawnPos); + bitStream.Write(upcomingPlane); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendRacingResetPlayerToLastReset(LWOOBJID objectId, LWOOBJID playerID, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::RACING_RESET_PLAYER_TO_LAST_RESET); + + bitStream.Write(playerID); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendVehicleStopBoost(Entity* targetEntity, const SystemAddress& playerSysAddr, bool affectPassive) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(targetEntity->GetObjectID()); + bitStream.Write(MessageType::Game::VEHICLE_STOP_BOOST); + + bitStream.Write(affectPassive); + + SEND_PACKET_BROADCAST; + } + + inline void SendNotifyRacingClient(LWOOBJID objectId, int32_t eventType, int32_t param1, LWOOBJID paramObj, std::u16string paramStr, LWOOBJID singleClient, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::NOTIFY_RACING_CLIENT); + + bitStream.Write(eventType != 0); + if (eventType != 0) bitStream.Write(eventType); + + bitStream.Write(param1); + + bitStream.Write(paramObj); + + bitStream.Write(paramStr.size()); + for (auto character : paramStr) { + bitStream.Write(character); + } + + bitStream.Write(singleClient); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendVehicleAddPassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::VEHICLE_ADD_PASSIVE_BOOST_ACTION); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendVehicleRemovePassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::VEHICLE_REMOVE_PASSIVE_BOOST_ACTION); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + inline void SendVehicleNotifyFinishedRace(LWOOBJID objectId, const SystemAddress& sysAddr) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(MessageType::Game::VEHICLE_NOTIFY_FINISHED_RACE); + + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; + } + + // GameMessages::HandleModularAssemblyNIFCompleted + inline LWOOBJID ReadModularAssemblyNIFCompleted(RakNet::BitStream& inStream) { + LWOOBJID objectID; + + inStream.Read(objectID); + return objectID; + } + + // GameMessages::HandleVehicleSetWheelLockState + inline std::pair ReadVehicleSetWheelLockState(RakNet::BitStream& inStream) { + bool bExtraFriction = inStream.ReadBit(); + bool bLocked = inStream.ReadBit(); + return { bExtraFriction, bLocked }; + } + + // GameMessages::HandleRacingClientReady and GameMessages::HandleRacingPlayerInfoResetFinished + inline LWOOBJID ReadRacingPlayerID(RakNet::BitStream& inStream) { + LWOOBJID playerID; + + inStream.Read(playerID); + return playerID; + } + + // GameMessages::HandleVehicleNotifyHitImaginationServer + struct LegacyHitImagination { + LWOOBJID pickupObjID = LWOOBJID_EMPTY; + LWOOBJID pickupSpawnerID = LWOOBJID_EMPTY; + int32_t pickupSpawnerIndex = -1; + NiPoint3 vehiclePosition = NiPoint3Constant::ZERO; + }; + inline LegacyHitImagination ReadVehicleNotifyHitImaginationServer(RakNet::BitStream& inStream) { + LWOOBJID pickupObjID = LWOOBJID_EMPTY; + LWOOBJID pickupSpawnerID = LWOOBJID_EMPTY; + int32_t pickupSpawnerIndex = -1; + NiPoint3 vehiclePosition = NiPoint3Constant::ZERO; + + if (inStream.ReadBit()) inStream.Read(pickupObjID); + if (inStream.ReadBit()) inStream.Read(pickupSpawnerID); + if (inStream.ReadBit()) inStream.Read(pickupSpawnerIndex); + if (inStream.ReadBit()) inStream.Read(vehiclePosition); + return { pickupObjID, pickupSpawnerID, pickupSpawnerIndex, vehiclePosition }; + } +} + +#endif // RACINGMESSAGESLEGACY_H diff --git a/tests/dGameTests/dGameMessagesTests/RacingMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/RacingMessagesTests.cpp new file mode 100644 index 000000000..c6b25cd10 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/RacingMessagesTests.cpp @@ -0,0 +1,320 @@ +#include "RacingMessages.h" +#include "GameDependencies.h" +#include "GameMessageTestUtils.h" +#include "Legacy/RacingMessagesLegacy.h" + +#include +#include +#include +#include +#include + +#include + +using namespace GameMessageTestUtils; + +// eRacingClientNotificationType goes on the wire; pin its values. +static_assert(static_cast(eRacingClientNotificationType::INVALID) == 0); +static_assert(static_cast(eRacingClientNotificationType::ACTIVITY_START) == 1); +static_assert(static_cast(eRacingClientNotificationType::REWARD_PLAYER) == 2); +static_assert(static_cast(eRacingClientNotificationType::EXIT) == 3); +static_assert(static_cast(eRacingClientNotificationType::REPLAY) == 4); +static_assert(static_cast(eRacingClientNotificationType::REMOVE_PLAYER) == 5); +static_assert(static_cast(eRacingClientNotificationType::LEADERBOARD_UPDATED) == 6); + +namespace { + const std::vector g_Strings = { u"", u"a", u"1:8092;2:4880;3:7503;", std::u16string(300, u'é') }; + const std::vector g_Points = { NiPoint3Constant::ZERO, NiPoint3(1.0f, -2.5f, 1e6f), NiPoint3(0.0f, 0.0f, -0.0f) }; +} + +class RacingMessagesTests : public GameDependenciesTest { +protected: + void SetUp() override { SetUpDependencies(); } + void TearDown() override { TearDownDependencies(); } +}; + +TEST_F(RacingMessagesTests, NoPayloadMessagesMatchLegacy) { + for (const auto target : g_Targets) { + GameMessages::VehicleAddPassiveBoostAction add; + add.target = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendVehicleAddPassiveBoostAction(target, a); }, add); + + GameMessages::VehicleRemovePassiveBoostAction remove; + remove.target = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendVehicleRemovePassiveBoostAction(target, a); }, remove); + + GameMessages::VehicleNotifyFinishedRace finished; + finished.target = target; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendVehicleNotifyFinishedRace(target, a); }, finished); + } +} + +TEST_F(RacingMessagesTests, ModuleAssemblyDBDataForClientMatchesLegacy) { + for (const auto target : g_Targets) { + for (const auto assemblyID : g_Targets) { + for (const auto& blob : g_Strings) { + GameMessages::ModuleAssemblyDBDataForClient msg; + msg.target = target; + msg.assemblyID = assemblyID; + msg.blob = blob; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendModuleAssemblyDBDataForClient(target, assemblyID, blob, a); }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.assemblyID, assemblyID); + EXPECT_EQ(copy.blob, blob); + } + } + } +} + +TEST_F(RacingMessagesTests, NotifyVehicleOfRacingObjectMatchesLegacy) { + for (const auto target : g_Targets) { + for (const auto racingObjectID : g_Targets) { + GameMessages::NotifyVehicleOfRacingObject msg; + msg.target = target; + msg.racingObjectID = racingObjectID; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendNotifyVehicleOfRacingObject(target, racingObjectID, a); }, msg); + EXPECT_EQ(RoundTrip(msg).racingObjectID, racingObjectID); + } + } +} + +TEST_F(RacingMessagesTests, RacingPlayerLoadedMatchesLegacy) { + for (const auto target : g_Targets) { + for (const auto playerID : g_Targets) { + for (const auto vehicleID : g_Targets) { + GameMessages::RacingPlayerLoaded msg; + msg.target = target; + msg.playerID = playerID; + msg.vehicleID = vehicleID; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendRacingPlayerLoaded(target, playerID, vehicleID, a); }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.playerID, playerID); + EXPECT_EQ(copy.vehicleID, vehicleID); + } + } + } +} + +TEST_F(RacingMessagesTests, VehicleUnlockInputMatchesLegacy) { + for (const auto target : g_Targets) { + for (const bool bLockWheels : { false, true }) { + GameMessages::VehicleUnlockInput msg; + msg.target = target; + msg.bLockWheels = bLockWheels; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendVehicleUnlockInput(target, bLockWheels, a); }, msg); + EXPECT_EQ(RoundTrip(msg).bLockWheels, bLockWheels); + } + } +} + +TEST_F(RacingMessagesTests, VehicleSetWheelLockStateMatchesLegacy) { + for (const auto target : g_Targets) { + for (const bool bExtraFriction : { false, true }) { + for (const bool bLocked : { false, true }) { + GameMessages::VehicleSetWheelLockState msg; + msg.target = target; + msg.bExtraFriction = bExtraFriction; + msg.bLocked = bLocked; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendVehicleSetWheelLockState(target, bExtraFriction, bLocked, a); }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.bExtraFriction, bExtraFriction); + EXPECT_EQ(copy.bLocked, bLocked); + + // Inbound: same read as the old handler. + RakNet::BitStream wire; + msg.Serialize(wire); + RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + const auto [legacyFriction, legacyLocked] = LegacyGameMessages::ReadVehicleSetWheelLockState(legacyStream); + EXPECT_EQ(legacyFriction, bExtraFriction); + EXPECT_EQ(legacyLocked, bLocked); + } + } + } +} + +TEST_F(RacingMessagesTests, RacingSetPlayerResetInfoMatchesLegacy) { + for (const auto target : g_Targets) { + for (const int32_t currentLap : { 0, 3, -1, std::numeric_limits::max() }) { + for (const uint32_t furthestResetPlane : { 0u, 7u, std::numeric_limits::max() }) { + for (const auto& respawnPos : g_Points) { + const LWOOBJID playerID = 0x1000000000000001LL; + const uint32_t upcomingPlane = furthestResetPlane + 1; + GameMessages::RacingSetPlayerResetInfo msg; + msg.target = target; + msg.currentLap = currentLap; + msg.furthestResetPlane = furthestResetPlane; + msg.playerID = playerID; + msg.respawnPos = respawnPos; + msg.upcomingPlane = upcomingPlane; + ExpectSameAsLegacy([&](const SystemAddress& a) { + LegacyGameMessages::SendRacingSetPlayerResetInfo(target, currentLap, furthestResetPlane, playerID, respawnPos, upcomingPlane, a); + }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.currentLap, currentLap); + EXPECT_EQ(copy.furthestResetPlane, furthestResetPlane); + EXPECT_EQ(copy.respawnPos, respawnPos); + EXPECT_EQ(copy.upcomingPlane, upcomingPlane); + } + } + } + } +} + +TEST_F(RacingMessagesTests, RacingResetPlayerToLastResetMatchesLegacy) { + for (const auto target : g_Targets) { + for (const auto playerID : g_Targets) { + GameMessages::RacingResetPlayerToLastReset msg; + msg.target = target; + msg.playerID = playerID; + ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendRacingResetPlayerToLastReset(target, playerID, a); }, msg); + EXPECT_EQ(RoundTrip(msg).playerID, playerID); + } + } +} + +TEST_F(RacingMessagesTests, VehicleStopBoostMatchesLegacy) { + // The legacy function always broadcast, whatever address it was given; callers now pass UNASSIGNED explicitly. + for (const auto target : g_Targets) { + for (const bool bAffectPassive : { false, true }) { + Entity vehicle(target, info); + GameMessages::VehicleStopBoost msg; + msg.target = target; + msg.bAffectPassive = bAffectPassive; + + const auto legacyPackets = Capture([&] { LegacyGameMessages::SendVehicleStopBoost(&vehicle, ClientAddress(), bAffectPassive); }); + const auto newPackets = Capture([&] { msg.Send(UNASSIGNED_SYSTEM_ADDRESS); }); + ASSERT_EQ(legacyPackets.size(), 1); + ASSERT_EQ(newPackets.size(), 1); + EXPECT_TRUE(legacyPackets[0].broadcast); + EXPECT_TRUE(newPackets[0].broadcast); + EXPECT_PACKET_EQ(FromCapture(legacyPackets[0]), FromCapture(newPackets[0])); + EXPECT_EQ(RoundTrip(msg).bAffectPassive, bAffectPassive); + } + } +} + +TEST_F(RacingMessagesTests, NotifyRacingClientMatchesLegacy) { + for (const auto target : g_Targets) { + for (const int32_t eventType : { 0, 1, 2, 3, 6, -5 }) { + for (const int32_t param1 : { 0, 1, -1 }) { + for (const auto paramObj : g_Targets) { + for (const auto& paramStr : g_Strings) { + const LWOOBJID singleClient = paramObj == LWOOBJID_EMPTY ? 0x1000000000000001LL : LWOOBJID_EMPTY; + GameMessages::NotifyRacingClient msg; + msg.target = target; + msg.eventType = static_cast(eventType); + msg.param1 = param1; + msg.paramObj = paramObj; + msg.paramStr = paramStr; + msg.singleClient = singleClient; + ExpectSameAsLegacy([&](const SystemAddress& a) { + LegacyGameMessages::SendNotifyRacingClient(target, eventType, param1, paramObj, paramStr, singleClient, a); + }, msg); + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.eventType, msg.eventType); + EXPECT_EQ(copy.param1, param1); + EXPECT_EQ(copy.paramObj, paramObj); + EXPECT_EQ(copy.paramStr, paramStr); + EXPECT_EQ(copy.singleClient, singleClient); + } + } + } + } + } +} + +// Independent of the legacy code: hand computed payloads (RakNet writes MSB first). +TEST_F(RacingMessagesTests, GoldenBytes) { + auto payload = [](const GameMessages::NetGameMsg& msg) { + RakNet::BitStream bitStream; + msg.Serialize(bitStream); + return FromBitStream(bitStream); + }; + + GameMessages::NotifyVehicleOfRacingObject notifyVehicle; + notifyVehicle.racingObjectID = 0x0102030405060708LL; + EXPECT_PACKET_EQ(FromHex("84 03 83 02 82 01 81 00 80", 65), payload(notifyVehicle)); + notifyVehicle.racingObjectID = LWOOBJID_EMPTY; + EXPECT_PACKET_EQ(FromHex("00", 1), payload(notifyVehicle)); + + GameMessages::NotifyRacingClient notify; + notify.eventType = eRacingClientNotificationType::EXIT; + notify.param1 = 5; + notify.paramObj = 0x11; + notify.paramStr = u"ab"; + notify.singleClient = 0x22; + EXPECT_PACKET_EQ(FromHex("81 80 00 00 02 80 00 00 08 80 00 00 00 00 00 00 01 00 00 00 30 80 31 00 11 00 00 00 00 00 00 00 00", 257), payload(notify)); + GameMessages::NotifyRacingClient notifyDefault; + EXPECT_PACKET_EQ(FromHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", 193), payload(notifyDefault)); + + GameMessages::RacingSetPlayerResetInfo reset; + reset.currentLap = 2; + reset.furthestResetPlane = 7; + reset.playerID = 0x0102030405060708LL; + reset.respawnPos = NiPoint3(1.0f, 2.0f, -3.0f); + reset.upcomingPlane = 8; + EXPECT_PACKET_EQ(FromHex("02 00 00 00 07 00 00 00 08 07 06 05 04 03 02 01 00 00 80 3f 00 00 00 40 00 00 40 c0 08 00 00 00"), payload(reset)); + + // Full packet: 53 | CLIENT | GAME_MSG | 00 | target | msgId | payload + GameMessages::RacingPlayerLoaded loaded; + loaded.target = 0x0102030405060708LL; + loaded.playerID = 0x11; + loaded.vehicleID = 0x22; + const auto msgId = static_cast(MessageType::Game::RACING_PLAYER_LOADED); + char idHex[8]; + std::snprintf(idHex, sizeof(idHex), "%02x %02x", msgId & 0xff, msgId >> 8); + EXPECT_PACKET_EQ(FromHex(std::string("53 05 00 0c 00 00 00 00 08 07 06 05 04 03 02 01 ") + idHex + " 11 00 00 00 00 00 00 00 22 00 00 00 00 00 00 00"), StructPacket(loaded)); +} + +TEST_F(RacingMessagesTests, InboundReadsLikeLegacy) { + for (const auto id : g_Targets) { + { + GameMessages::ModularAssemblyNIFCompleted msg; + msg.objectID = id; + RakNet::BitStream wire; + msg.Serialize(wire); + RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + EXPECT_EQ(LegacyGameMessages::ReadModularAssemblyNIFCompleted(legacyStream), id); + EXPECT_EQ(RoundTrip(msg).objectID, id); + ExpectTruncatedFails(msg); + } + { + GameMessages::RacingClientReady ready; + ready.playerID = id; + GameMessages::RacingPlayerInfoResetFinished finished; + finished.playerID = id; + for (const GameMessages::NetGameMsg* msg : std::initializer_list{ &ready, &finished }) { + RakNet::BitStream wire; + msg->Serialize(wire); + RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + EXPECT_EQ(LegacyGameMessages::ReadRacingPlayerID(legacyStream), id); + } + EXPECT_EQ(RoundTrip(ready).playerID, id); + EXPECT_EQ(RoundTrip(finished).playerID, id); + ExpectTruncatedFails(ready); + ExpectTruncatedFails(finished); + } + } + + // VehicleNotifyHitImaginationServer: every combination of present/absent optional fields. + for (int mask = 0; mask < 16; mask++) { + GameMessages::VehicleNotifyHitImaginationServer msg; + if (mask & 1) msg.pickupObjID = 0x0102030405060708LL; + if (mask & 2) msg.pickupSpawnerID = 0x1000000000000001LL; + if (mask & 4) msg.pickupSpawnerIndex = 12; + if (mask & 8) msg.vehiclePosition = NiPoint3(1.0f, 2.0f, 3.0f); + + RakNet::BitStream wire; + msg.Serialize(wire); + RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false); + const auto legacy = LegacyGameMessages::ReadVehicleNotifyHitImaginationServer(legacyStream); + EXPECT_EQ(legacyStream.GetReadOffset(), wire.GetNumberOfBitsUsed()); // both read exactly the same bits + + const auto copy = RoundTrip(msg); + EXPECT_EQ(copy.pickupObjID, legacy.pickupObjID); + EXPECT_EQ(copy.pickupSpawnerID, legacy.pickupSpawnerID); + EXPECT_EQ(copy.pickupSpawnerIndex, legacy.pickupSpawnerIndex); + EXPECT_EQ(copy.vehiclePosition, legacy.vehiclePosition); + ExpectTruncatedFails(msg); + } +}