diff --git a/dGame/dComponents/BaseRacingControlComponent.cpp b/dGame/dComponents/BaseRacingControlComponent.cpp deleted file mode 100644 index f948215a..00000000 --- a/dGame/dComponents/BaseRacingControlComponent.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "BaseRacingControlComponent.h" - -BaseRacingControlComponent::BaseRacingControlComponent(Entity* parent, int32_t componentId) : ScriptedActivityComponent(parent, componentId) { - -} diff --git a/dGame/dComponents/BaseRacingControlComponent.h b/dGame/dComponents/BaseRacingControlComponent.h deleted file mode 100644 index 935fb0c3..00000000 --- a/dGame/dComponents/BaseRacingControlComponent.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef __BASERACINGCONTROLCOMPONENT__H__ -#define __BASERACINGCONTROLCOMPONENT__H__ - -#include "ScriptedActivityComponent.h" -#include "eReplicaComponentType.h" - -class Entity; - -class BaseRacingControlComponent : public ScriptedActivityComponent { -public: - inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL; - BaseRacingControlComponent(Entity* parent, int32_t componentId); -}; - - -#endif //!__BASERACINGCONTROLCOMPONENT__H__ diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index 09849b7d..a91a683d 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -1,7 +1,7 @@ set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp" "ActivityComponent.cpp" "BaseCombatAIComponent.cpp" - "BaseRacingControlComponent.cpp" + "RacingControlComponent.cpp" "BouncerComponent.cpp" "BuffComponent.cpp" "BuildBorderComponent.cpp" @@ -34,7 +34,7 @@ set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp" "PropertyManagementComponent.cpp" "PropertyVendorComponent.cpp" "ProximityMonitorComponent.cpp" - "RacingControlComponent.cpp" + "VehicleRacingControlComponent.cpp" "RacingSoundTriggerComponent.cpp" "RacingStatsComponent.cpp" "RailActivatorComponent.cpp" diff --git a/dGame/dComponents/ComponentHeirachy.md b/dGame/dComponents/ComponentHeirachy.md index dc2992ab..3a316988 100644 --- a/dGame/dComponents/ComponentHeirachy.md +++ b/dGame/dComponents/ComponentHeirachy.md @@ -4,6 +4,9 @@ Legend ├~~ Loaded with Parent ├-> SubComponent ├-? idk lol, but related + +originalName -> newName + ``` @@ -14,8 +17,8 @@ LWOActivityComponent ├── LWOMiniGameControlComponent ├── LWOShootingGalleryComponent ├── LWOScriptedActivityComponent -| └── LWOBaseRacingControlComponent -| ├── LWORacingControlComponent +| └── LWOBaseRacingControlComponent -> RacingControlComponent +| ├── LWORacingControlComponent -> VehicleRacingControlComponent | └── LWOGateRushControlComponent LWOBaseCombatAIComponent ├~~ LWOPathfindingControlComponent diff --git a/dGame/dComponents/GateRushControlComponent.cpp b/dGame/dComponents/GateRushControlComponent.cpp index 1ce71624..83ff4f86 100644 --- a/dGame/dComponents/GateRushControlComponent.cpp +++ b/dGame/dComponents/GateRushControlComponent.cpp @@ -1,5 +1,5 @@ #include "GateRushControlComponent.h" -GateRushControlComponent::GateRushControlComponent(Entity* parent, int32_t componentId) : BaseRacingControlComponent(parent, componentId) { +GateRushControlComponent::GateRushControlComponent(Entity* parent, int32_t componentId) : RacingControlComponent(parent, componentId) { } diff --git a/dGame/dComponents/GateRushControlComponent.h b/dGame/dComponents/GateRushControlComponent.h index 73b95619..7e39a18a 100644 --- a/dGame/dComponents/GateRushControlComponent.h +++ b/dGame/dComponents/GateRushControlComponent.h @@ -1,12 +1,12 @@ #ifndef __GATERUSHCONTROLCOMPONENT__H__ #define __GATERUSHCONTROLCOMPONENT__H__ -#include "BaseRacingControlComponent.h" +#include "RacingControlComponent.h" #include "eReplicaComponentType.h" class Entity; -class GateRushControlComponent : public BaseRacingControlComponent { +class GateRushControlComponent : public RacingControlComponent { public: inline static const eReplicaComponentType ComponentType = eReplicaComponentType::GATE_RUSH_CONTROL; GateRushControlComponent(Entity* parent, int32_t componentId); diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 1f4f2d5a..2766c471 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -1,880 +1,5 @@ -/** - * Thanks to Simon for his early research on the racing system. - */ - #include "RacingControlComponent.h" -#include "CharacterComponent.h" -#include "DestroyableComponent.h" -#include "EntityManager.h" -#include "GameMessages.h" -#include "InventoryComponent.h" -#include "Item.h" -#include "MissionComponent.h" -#include "ModuleAssemblyComponent.h" -#include "Player.h" -#include "PossessableComponent.h" -#include "PossessorComponent.h" -#include "eRacingTaskParam.h" -#include "Spawner.h" -#include "dServer.h" -#include "dZoneManager.h" -#include "dConfig.h" -#include "Loot.h" -#include "eMissionTaskType.h" -#include "dZoneManager.h" -#include "CDActivitiesTable.h" +RacingControlComponent::RacingControlComponent(Entity* parent, int32_t componentId) : ScriptedActivityComponent(parent, componentId) { -#ifndef M_PI -#define M_PI 3.14159265358979323846264338327950288 -#endif - -RacingControlComponent::RacingControlComponent(Entity* parent, int32_t componentId) : BaseRacingControlComponent(parent, componentId) { - m_PathName = u"MainPath"; - m_RemainingLaps = 3; - m_LeadingPlayer = LWOOBJID_EMPTY; - m_RaceBestTime = 0; - m_RaceBestLap = 0; - m_Started = false; - m_StartTimer = 0; - m_Loaded = false; - m_LoadedPlayers = 0; - m_LoadTimer = 0; - m_Finished = 0; - m_StartTime = 0; - m_EmptyTimer = 0; - m_SoloRacing = Game::config->GetValue("solo_racing") == "1"; - - m_MainWorld = 1200; - const auto worldID = Game::server->GetZoneID(); - if (dZoneManager::Instance()->CheckIfAccessibleZone((worldID/10)*10)) m_MainWorld = (worldID/10)*10; - - m_ActivityID = 42; - CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable(); - std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.instanceMapID == worldID); }); - for (CDActivities activity : activities) m_ActivityID = activity.ActivityID; -} - -RacingControlComponent::~RacingControlComponent() {} - -void RacingControlComponent::OnPlayerLoaded(Entity* player) { - // If the race has already started, send the player back to the main world. - if (m_Loaded) { - auto* playerInstance = dynamic_cast(player); - - playerInstance->SendToZone(m_MainWorld); - - return; - } - - const auto objectID = player->GetObjectID(); - - m_LoadedPlayers++; - - Game::logger->Log("RacingControlComponent", "Loading player %i", - m_LoadedPlayers); - - m_LobbyPlayers.push_back(objectID); -} - -void RacingControlComponent::LoadPlayerVehicle(Entity* player, - uint32_t positionNumber, bool initialLoad) { - // Load the player's vehicle. - - if (player == nullptr) { - return; - } - - auto* inventoryComponent = player->GetComponent(); - - if (inventoryComponent == nullptr) { - return; - } - - // Find the player's vehicle. - - auto* item = inventoryComponent->FindItemByLot(8092); - - if (item == nullptr) { - Game::logger->Log("RacingControlComponent", "Failed to find item"); - - return; - } - - // Calculate the vehicle's starting position. - - auto* path = dZoneManager::Instance()->GetZone()->GetPath( - GeneralUtils::UTF16ToWTF8(m_PathName)); - - auto spawnPointEntities = EntityManager::Instance()->GetEntitiesByLOT(4843); - auto startPosition = NiPoint3::ZERO; - auto startRotation = NiQuaternion::IDENTITY; - const std::string placementAsString = std::to_string(positionNumber); - for (auto entity : spawnPointEntities) { - if (!entity) continue; - if (entity->GetVarAsString(u"placement") == placementAsString) { - startPosition = entity->GetPosition(); - startRotation = entity->GetRotation(); - break; - } - } - - // Make sure the player is at the correct position. - - GameMessages::SendTeleport(player->GetObjectID(), startPosition, - startRotation, player->GetSystemAddress(), true); - - // Spawn the vehicle entity. - - EntityInfo info{}; - info.lot = 8092; - info.pos = startPosition; - info.rot = startRotation; - info.spawnerID = m_ParentEntity->GetObjectID(); - - auto* carEntity = - EntityManager::Instance()->CreateEntity(info, nullptr, m_ParentEntity); - - // Make the vehicle a child of the racing controller. - m_ParentEntity->AddChild(carEntity); - - auto* destroyableComponent = carEntity->GetComponent(); - - // Setup the vehicle stats. - if (destroyableComponent != nullptr) { - destroyableComponent->SetMaxImagination(60); - destroyableComponent->SetImagination(0); - } - - // Setup the vehicle as being possessed by the player. - auto* possessableComponent = carEntity->GetComponent(); - - if (possessableComponent != nullptr) { - possessableComponent->SetPossessor(player->GetObjectID()); - } - - // Load the vehicle's assemblyPartLOTs for display. - auto* moduleAssemblyComponent = carEntity->GetComponent(); - - if (moduleAssemblyComponent) { - moduleAssemblyComponent->SetSubKey(item->GetSubKey()); - moduleAssemblyComponent->SetUseOptionalParts(false); - - for (auto* config : item->GetConfig()) { - if (config->GetKey() == u"assemblyPartLOTs") { - moduleAssemblyComponent->SetAssemblyPartsLOTs( - GeneralUtils::ASCIIToUTF16(config->GetValueAsString())); - } - } - } - - // Setup the player as possessing the vehicle. - auto* possessorComponent = player->GetComponent(); - - if (possessorComponent != nullptr) { - possessorComponent->SetPossessable(carEntity->GetObjectID()); - possessorComponent->SetPossessableType(ePossessionType::ATTACHED_VISIBLE); // for racing it's always Attached_Visible - } - - // Set the player's current activity as racing. - auto* characterComponent = player->GetComponent(); - - if (characterComponent != nullptr) { - characterComponent->SetIsRacing(true); - } - - // Init the player's racing entry. - if (initialLoad) { - m_RacingPlayers.push_back( - { player->GetObjectID(), - carEntity->GetObjectID(), - static_cast(m_RacingPlayers.size()), - false, - {}, - startPosition, - startRotation, - 0, - 0, - 0, - 0 }); - } - - // Construct and serialize everything when done. - - EntityManager::Instance()->ConstructEntity(carEntity); - EntityManager::Instance()->SerializeEntity(player); - EntityManager::Instance()->SerializeEntity(m_ParentEntity); - - GameMessages::SendRacingSetPlayerResetInfo( - m_ParentEntity->GetObjectID(), 0, 0, player->GetObjectID(), startPosition, 1, - UNASSIGNED_SYSTEM_ADDRESS); - - const auto playerID = player->GetObjectID(); - - // Reset the player to the start position during downtime, in case something - // went wrong. - m_ParentEntity->AddCallbackTimer(1, [this, playerID]() { - auto* player = EntityManager::Instance()->GetEntity(playerID); - - if (player == nullptr) { - return; - } - - GameMessages::SendRacingResetPlayerToLastReset( - m_ParentEntity->GetObjectID(), playerID, UNASSIGNED_SYSTEM_ADDRESS); - }); - - GameMessages::SendSetJetPackMode(player, false); - - // Set the vehicle's state. - GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), - m_ParentEntity->GetObjectID(), - UNASSIGNED_SYSTEM_ADDRESS); - - GameMessages::SendVehicleSetWheelLockState(carEntity->GetObjectID(), false, - initialLoad, - UNASSIGNED_SYSTEM_ADDRESS); - - // Make sure everything has the correct position. - GameMessages::SendTeleport(player->GetObjectID(), startPosition, - startRotation, player->GetSystemAddress(), true); - GameMessages::SendTeleport(carEntity->GetObjectID(), startPosition, - startRotation, player->GetSystemAddress(), true); -} - -void RacingControlComponent::OnRacingClientReady(Entity* player) { - // Notify the other players that this player is ready. - - for (auto& racingPlayer : m_RacingPlayers) { - if (racingPlayer.playerID != player->GetObjectID()) { - if (racingPlayer.playerLoaded) { - GameMessages::SendRacingPlayerLoaded( - m_ParentEntity->GetObjectID(), racingPlayer.playerID, - racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); - } - - continue; - } - - racingPlayer.playerLoaded = true; - - GameMessages::SendRacingPlayerLoaded( - m_ParentEntity->GetObjectID(), racingPlayer.playerID, - racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); - } - - EntityManager::Instance()->SerializeEntity(m_ParentEntity); -} - -void RacingControlComponent::OnRequestDie(Entity* player) { - // Sent by the client when they collide with something which should smash - // them. - - for (auto& racingPlayer : m_RacingPlayers) { - if (racingPlayer.playerID != player->GetObjectID()) { - continue; - } - - auto* vehicle = - EntityManager::Instance()->GetEntity(racingPlayer.vehicleID); - - if (!vehicle) return; - - if (!racingPlayer.noSmashOnReload) { - racingPlayer.smashedTimes++; - GameMessages::SendDie(vehicle, vehicle->GetObjectID(), LWOOBJID_EMPTY, true, - eKillType::VIOLENT, u"", 0, 0, 90.0f, false, true, 0); - - auto* destroyableComponent = vehicle->GetComponent(); - uint32_t respawnImagination = 0; - // Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live. - // 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); - } - - // 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, [=]() { - if (!vehicle || !this->m_ParentEntity) return; - GameMessages::SendRacingResetPlayerToLastReset( - m_ParentEntity->GetObjectID(), racingPlayer.playerID, - UNASSIGNED_SYSTEM_ADDRESS); - - GameMessages::SendVehicleStopBoost(vehicle, player->GetSystemAddress(), true); - - GameMessages::SendRacingSetPlayerResetInfo( - m_ParentEntity->GetObjectID(), racingPlayer.lap, - racingPlayer.respawnIndex, player->GetObjectID(), - racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1, - UNASSIGNED_SYSTEM_ADDRESS); - - GameMessages::SendResurrect(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); - EntityManager::Instance()->SerializeEntity(vehicle); - }); - - auto* characterComponent = player->GetComponent(); - if (characterComponent != nullptr) { - characterComponent->UpdatePlayerStatistic(RacingTimesWrecked); - } - } else { - GameMessages::SendRacingSetPlayerResetInfo( - m_ParentEntity->GetObjectID(), racingPlayer.lap, - racingPlayer.respawnIndex, player->GetObjectID(), - racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1, - UNASSIGNED_SYSTEM_ADDRESS); - GameMessages::SendRacingResetPlayerToLastReset( - m_ParentEntity->GetObjectID(), racingPlayer.playerID, - UNASSIGNED_SYSTEM_ADDRESS); - } - } -} - -void RacingControlComponent::OnRacingPlayerInfoResetFinished(Entity* player) { - // When the player has respawned. - - for (auto& racingPlayer : m_RacingPlayers) { - if (racingPlayer.playerID != player->GetObjectID()) { - continue; - } - - auto* vehicle = - EntityManager::Instance()->GetEntity(racingPlayer.vehicleID); - - if (vehicle == nullptr) { - return; - } - - racingPlayer.noSmashOnReload = false; - - return; - } -} - -void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id) { - auto* data = GetPlayerData(player->GetObjectID()); - - if (data == nullptr) { - return; - } - - if (id == "rewardButton") { - if (data->collectedRewards) { - return; - } - - data->collectedRewards = true; - - // Calculate the score, different loot depending on player count - const auto score = m_LoadedPlayers * 10 + data->finished; - - LootGenerator::Instance().GiveActivityLoot(player, m_ParentEntity, m_ActivityID, score); - - // Giving rewards - GameMessages::SendNotifyRacingClient( - m_ParentEntity->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"", - player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - - auto* missionComponent = player->GetComponent(); - - if (missionComponent == nullptr) return; - - missionComponent->Progress(eMissionTaskType::RACING, 0, (LWOOBJID)eRacingTaskParam::COMPETED_IN_RACE); // Progress task for competing in a race - missionComponent->Progress(eMissionTaskType::RACING, data->smashedTimes, (LWOOBJID)eRacingTaskParam::SAFE_DRIVER); // Finish a race without being smashed. - - // If solo racing is enabled OR if there are 3 players in the race, progress placement tasks. - if (m_SoloRacing || m_LoadedPlayers > 2) { - missionComponent->Progress(eMissionTaskType::RACING, data->finished, (LWOOBJID)eRacingTaskParam::FINISH_WITH_PLACEMENT); // Finish in 1st place on a race - if (data->finished == 1) { - missionComponent->Progress(eMissionTaskType::RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)eRacingTaskParam::FIRST_PLACE_MULTIPLE_TRACKS); // Finish in 1st place on multiple tracks. - missionComponent->Progress(eMissionTaskType::RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)eRacingTaskParam::WIN_RACE_IN_WORLD); // Finished first place in specific world. - } - if (data->finished == m_LoadedPlayers) { - missionComponent->Progress(eMissionTaskType::RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)eRacingTaskParam::LAST_PLACE_FINISH); // Finished first place in specific world. - } - } - } else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) { - auto* vehicle = EntityManager::Instance()->GetEntity(data->vehicleID); - - if (vehicle == nullptr) { - return; - } - - // Exiting race - GameMessages::SendNotifyRacingClient( - m_ParentEntity->GetObjectID(), 3, 0, LWOOBJID_EMPTY, u"", - player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - - auto* playerInstance = dynamic_cast(player); - - playerInstance->SendToZone(m_MainWorld); - - vehicle->Kill(); - } -} - -void RacingControlComponent::Serialize(RakNet::BitStream* outBitStream, - bool bIsInitialUpdate, - unsigned int& flags) { - // BEGIN Scripted Activity - - outBitStream->Write1(); - - outBitStream->Write(static_cast(m_RacingPlayers.size())); - for (const auto& player : m_RacingPlayers) { - outBitStream->Write(player.playerID); - - for (int i = 0; i < 10; i++) { - outBitStream->Write(player.data[i]); - } - } - - // END Scripted Activity - - outBitStream->Write1(); // Dirty? - outBitStream->Write(static_cast(m_RacingPlayers.size())); - - outBitStream->Write(!m_RacingPlayers.empty()); - if (!m_RacingPlayers.empty()) { - for (const auto& player : m_RacingPlayers) { - outBitStream->Write1(); // Has more date - - outBitStream->Write(player.playerID); - outBitStream->Write(player.vehicleID); - outBitStream->Write(player.playerIndex); - outBitStream->Write(player.playerLoaded); - } - - outBitStream->Write0(); // No more data - } - - outBitStream->Write(!m_RacingPlayers.empty()); - if (!m_RacingPlayers.empty()) { - for (const auto& player : m_RacingPlayers) { - outBitStream->Write1(); // Has more date - - outBitStream->Write(player.playerID); - outBitStream->Write(0); - } - - outBitStream->Write0(); // No more data - } - - outBitStream->Write1(); // Dirty? - - outBitStream->Write(m_RemainingLaps); - - outBitStream->Write(static_cast(m_PathName.size())); - for (const auto character : m_PathName) { - outBitStream->Write(character); - } - - outBitStream->Write1(); // ??? - outBitStream->Write1(); // ??? - - outBitStream->Write(m_LeadingPlayer); - outBitStream->Write(m_RaceBestLap); - outBitStream->Write(m_RaceBestTime); -} - -RacingPlayerInfo* RacingControlComponent::GetPlayerData(LWOOBJID playerID) { - for (auto& player : m_RacingPlayers) { - if (player.playerID == playerID) { - return &player; - } - } - - return nullptr; -} - -void RacingControlComponent::Update(float deltaTime) { - // This method is a mess. - - // Pre-load routine - if (!m_Loaded) { - // Check if any players has disconnected before loading in - for (size_t i = 0; i < m_LobbyPlayers.size(); i++) { - auto* playerEntity = - EntityManager::Instance()->GetEntity(m_LobbyPlayers[i]); - - if (playerEntity == nullptr) { - --m_LoadedPlayers; - - m_LobbyPlayers.erase(m_LobbyPlayers.begin() + i); - - return; - } - } - - if (m_LoadedPlayers >= 2 || (m_LoadedPlayers == 1 && m_SoloRacing)) { - m_LoadTimer += deltaTime; - } else { - m_EmptyTimer += deltaTime; - } - - // If a player happens to be left alone for more then 30 seconds without - // anyone else loading in, send them back to the main world - if (m_EmptyTimer >= 30) { - for (const auto player : m_LobbyPlayers) { - auto* playerEntity = - EntityManager::Instance()->GetEntity(player); - - if (playerEntity == nullptr) { - continue; - } - - auto* playerInstance = dynamic_cast(playerEntity); - - playerInstance->SendToZone(m_MainWorld); - } - - m_LobbyPlayers.clear(); - } - - // From the first 2 players loading in the rest have a max of 15 seconds - // to load in, can raise this if it's too low - if (m_LoadTimer >= 15) { - Game::logger->Log("RacingControlComponent", - "Loading all players..."); - - for (size_t positionNumber = 0; positionNumber < m_LobbyPlayers.size(); positionNumber++) { - Game::logger->Log("RacingControlComponent", - "Loading player now!"); - - auto* player = - EntityManager::Instance()->GetEntity(m_LobbyPlayers[positionNumber]); - - if (player == nullptr) { - return; - } - - Game::logger->Log("RacingControlComponent", - "Loading player now NOW!"); - - LoadPlayerVehicle(player, positionNumber + 1, true); - - m_Loaded = true; - } - - m_Loaded = true; - } - - return; - } - - // The players who will be participating have loaded - if (!m_Started) { - // Check if anyone has disconnected during this period - for (size_t i = 0; i < m_RacingPlayers.size(); i++) { - auto* playerEntity = EntityManager::Instance()->GetEntity( - m_RacingPlayers[i].playerID); - - if (playerEntity == nullptr) { - m_RacingPlayers.erase(m_RacingPlayers.begin() + i); - - --m_LoadedPlayers; - - return; - } - } - - // If less then 2 players are left, send the rest back to the main world - if (m_LoadedPlayers < 2 && !(m_LoadedPlayers == 1 && m_SoloRacing)) { - for (const auto player : m_LobbyPlayers) { - auto* playerEntity = - EntityManager::Instance()->GetEntity(player); - - if (playerEntity == nullptr) { - continue; - } - - auto* playerInstance = dynamic_cast(playerEntity); - - playerInstance->SendToZone(m_MainWorld); - } - - return; - } - - // Check if all players have send a ready message - - int32_t readyPlayers = 0; - - for (const auto& player : m_RacingPlayers) { - if (player.playerLoaded) { - ++readyPlayers; - } - } - - if (readyPlayers >= m_LoadedPlayers) { - // Setup for racing - if (m_StartTimer == 0) { - GameMessages::SendNotifyRacingClient( - m_ParentEntity->GetObjectID(), 1, 0, LWOOBJID_EMPTY, u"", - LWOOBJID_EMPTY, UNASSIGNED_SYSTEM_ADDRESS); - - for (const auto& player : m_RacingPlayers) { - auto* vehicle = - EntityManager::Instance()->GetEntity(player.vehicleID); - auto* playerEntity = - EntityManager::Instance()->GetEntity(player.playerID); - - if (vehicle != nullptr && playerEntity != nullptr) { - GameMessages::SendTeleport( - player.playerID, player.respawnPosition, - player.respawnRotation, - playerEntity->GetSystemAddress(), true); - - vehicle->SetPosition(player.respawnPosition); - vehicle->SetRotation(player.respawnRotation); - - auto* destroyableComponent = vehicle->GetComponent(); - - if (destroyableComponent != nullptr) { - destroyableComponent->SetImagination(0); - } - - EntityManager::Instance()->SerializeEntity(vehicle); - EntityManager::Instance()->SerializeEntity( - playerEntity); - } - } - - // Spawn imagination pickups - auto* minSpawner = dZoneManager::Instance()->GetSpawnersByName( - "ImaginationSpawn_Min")[0]; - auto* medSpawner = dZoneManager::Instance()->GetSpawnersByName( - "ImaginationSpawn_Med")[0]; - auto* maxSpawner = dZoneManager::Instance()->GetSpawnersByName( - "ImaginationSpawn_Max")[0]; - - minSpawner->Activate(); - - if (m_LoadedPlayers > 2) { - medSpawner->Activate(); - } - - if (m_LoadedPlayers > 4) { - maxSpawner->Activate(); - } - - // Reset players to their start location, without smashing them - for (auto& player : m_RacingPlayers) { - auto* vehicleEntity = - EntityManager::Instance()->GetEntity(player.vehicleID); - auto* playerEntity = - EntityManager::Instance()->GetEntity(player.playerID); - - if (vehicleEntity == nullptr || playerEntity == nullptr) { - continue; - } - - player.noSmashOnReload = true; - - OnRequestDie(playerEntity); - } - } - // This 6 seconds seems to be hardcoded in the client, start race - // after that amount of time - else if (m_StartTimer >= 6) { - // Activate the players movement - for (auto& player : m_RacingPlayers) { - auto* vehicleEntity = - EntityManager::Instance()->GetEntity(player.vehicleID); - auto* playerEntity = - EntityManager::Instance()->GetEntity(player.playerID); - - if (vehicleEntity == nullptr || playerEntity == nullptr) { - continue; - } - - GameMessages::SendVehicleUnlockInput( - player.vehicleID, false, UNASSIGNED_SYSTEM_ADDRESS); - } - - // Start the race - GameMessages::SendActivityStart(m_ParentEntity->GetObjectID(), - UNASSIGNED_SYSTEM_ADDRESS); - - m_Started = true; - - Game::logger->Log("RacingControlComponent", "Starting race"); - - EntityManager::Instance()->SerializeEntity(m_ParentEntity); - - m_StartTime = std::time(nullptr); - } - - m_StartTimer += deltaTime; - } else { - m_StartTimer = 0; - } - - return; - } - - // Race routines - auto* path = dZoneManager::Instance()->GetZone()->GetPath( - GeneralUtils::UTF16ToWTF8(m_PathName)); - - for (auto& player : m_RacingPlayers) { - auto* vehicle = EntityManager::Instance()->GetEntity(player.vehicleID); - auto* playerEntity = - EntityManager::Instance()->GetEntity(player.playerID); - - if (vehicle == nullptr || playerEntity == nullptr) { - continue; - } - - const auto vehiclePosition = vehicle->GetPosition(); - - // 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_ParentEntity->GetObjectID(), - LWOOBJID_EMPTY, true, eKillType::VIOLENT, u"", 0, 0, 0, - true, false, 0); - - OnRequestDie(playerEntity); - - continue; - } - - // Loop through all the waypoints and see if the player has reached a - // new checkpoint - uint32_t respawnIndex = 0; - for (const auto& waypoint : path->pathWaypoints) { - if (player.lap == 3) { - break; - } - - if (player.respawnIndex == respawnIndex) { - ++respawnIndex; - - continue; - } - - const auto& position = waypoint.position; - - if (std::abs((int)respawnIndex - (int)player.respawnIndex) > 10 && - player.respawnIndex != path->pathWaypoints.size() - 1) { - ++respawnIndex; - - continue; - } - - if (Vector3::DistanceSquared(position, vehiclePosition) > 50 * 50) { - ++respawnIndex; - - continue; - } - - // Only go upwards, except if we've lapped - // Not sure how we are supposed to check if they've reach a - // checkpoint, within 50 units seems safe - if (!(respawnIndex > player.respawnIndex || - player.respawnIndex == path->pathWaypoints.size() - 1)) { - ++respawnIndex; - - continue; - } - - // Some offset up to make they don't fall through the terrain on a - // respawn, seems to fix itself to the track anyhow - player.respawnPosition = position + NiPoint3::UNIT_Y * 5; - player.respawnRotation = vehicle->GetRotation(); - player.respawnIndex = respawnIndex; - - // Reached the start point, lapped - if (respawnIndex == 0) { - time_t lapTime = std::time(nullptr) - (player.lap == 0 ? m_StartTime : player.lapTime); - - // Cheating check - if (lapTime < 40) { - continue; - } - - player.lap++; - - player.lapTime = std::time(nullptr); - - if (player.bestLapTime == 0 || player.bestLapTime > lapTime) { - player.bestLapTime = lapTime; - - Game::logger->Log("RacingControlComponent", - "Best lap time (%llu)", lapTime); - } - - auto* missionComponent = playerEntity->GetComponent(); - - if (missionComponent != nullptr) { - - // Progress lap time tasks - missionComponent->Progress(eMissionTaskType::RACING, (lapTime) * 1000, (LWOOBJID)eRacingTaskParam::LAP_TIME); - - if (player.lap == 3) { - m_Finished++; - player.finished = m_Finished; - - const auto raceTime = - (std::time(nullptr) - m_StartTime); - - player.raceTime = raceTime; - - Game::logger->Log("RacingControlComponent", - "Completed time %llu, %llu", - raceTime, raceTime * 1000); - - // Entire race time - missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, (LWOOBJID)eRacingTaskParam::TOTAL_TRACK_TIME); - - auto* characterComponent = playerEntity->GetComponent(); - if (characterComponent != nullptr) { - characterComponent->TrackRaceCompleted(m_Finished == 1); - } - - // TODO: Figure out how to update the GUI leaderboard. - } - } - - Game::logger->Log("RacingControlComponent", - "Lapped (%i) in (%llu)", player.lap, - lapTime); - } - - Game::logger->Log("RacingControlComponent", - "Reached point (%i)/(%i)", player.respawnIndex, - path->pathWaypoints.size()); - - break; - } - } -} - -std::string RacingControlComponent::FormatTimeString(time_t time) { - int32_t min = time / 60; - time -= min * 60; - int32_t sec = time; - - std::string minText; - std::string secText; - - if (min <= 0) { - minText = "0"; - } else { - minText = std::to_string(min); - } - - if (sec <= 0) { - secText = "00"; - } else if (sec <= 9) { - secText = "0" + std::to_string(sec); - } else { - secText = std::to_string(sec); - } - - return minText + ":" + secText + ".00"; } diff --git a/dGame/dComponents/RacingControlComponent.h b/dGame/dComponents/RacingControlComponent.h index b5dcedd4..7e7eef61 100644 --- a/dGame/dComponents/RacingControlComponent.h +++ b/dGame/dComponents/RacingControlComponent.h @@ -1,254 +1,16 @@ -/** - * Thanks to Simon for his early research on the racing system. - */ +#ifndef __RACINGCONTROLCOMPONENT__H__ +#define __RACINGCONTROLCOMPONENT__H__ -#pragma once - -#include "BitStream.h" -#include "Entity.h" -#include "BaseRacingControlComponent.h" +#include "ScriptedActivityComponent.h" #include "eReplicaComponentType.h" - /** - * Information for each player in the race - */ -struct RacingPlayerInfo { +class Entity; - /** - * The ID of the player - */ - LWOOBJID playerID; - - /** - * The ID of the car the player is driving - */ - LWOOBJID vehicleID; - - /** - * The index of this player in the list of players - */ - uint32_t playerIndex; - - /** - * Whether the player has finished loading or not - */ - bool playerLoaded; - - /** - * Scripted activity component score - */ - float data[10]{}; - - /** - * Point that the player will respawn at if they smash their car - */ - NiPoint3 respawnPosition; - - /** - * Rotation that the player will respawn at if they smash their car - */ - NiQuaternion respawnRotation; - - /** - * The index in the respawn point the player is now at - */ - uint32_t respawnIndex; - - /** - * The number of laps the player has completed - */ - uint32_t lap; - - /** - * Whether or not the player has finished the race - */ - uint32_t finished; - - /** - * Unused - */ - uint16_t reachedPoints; - - /** - * The fastest lap time of the player - */ - time_t bestLapTime = 0; - - /** - * The current lap time of the player - */ - time_t lapTime = 0; - - /** - * The number of times this player smashed their car - */ - uint32_t smashedTimes = 0; - - /** - * Whether or not the player should be smashed if the game is reloaded - */ - bool noSmashOnReload = false; - - /** - * Whether or not this player has collected their rewards from completing the race - */ - bool collectedRewards = false; - - /** - * Unused - */ - time_t raceTime = 0; -}; - -/** - * Component that's attached to a manager entity in each race zone that loads player vehicles, keep scores, etc. - */ -class RacingControlComponent : public BaseRacingControlComponent { +class RacingControlComponent : public ScriptedActivityComponent { public: inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL; - - RacingControlComponent(Entity* parentEntity, int32_t componentId); - ~RacingControlComponent(); - - void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); - void Update(float deltaTime); - - /** - * Invoked when a player loads into the zone. - */ - void OnPlayerLoaded(Entity* player); - - /** - * Initalize the player's vehicle. - * - * @param player The player who's vehicle to initialize. - * @param initialLoad Is this the first time the player is loading in this race? - */ - void LoadPlayerVehicle(Entity* player, uint32_t positionNumber, bool initialLoad = false); - - /** - * Invoked when the client says it has loaded in. - */ - void OnRacingClientReady(Entity* player); - - /** - * Invoked when the client says it should be smashed. - */ - void OnRequestDie(Entity* player); - - /** - * Invoked when the player has finished respawning. - */ - void OnRacingPlayerInfoResetFinished(Entity* player); - - /** - * Invoked when the player responds to the GUI. - */ - void HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id); - - /** - * Get the racing data from a player's LWOOBJID. - */ - RacingPlayerInfo* GetPlayerData(LWOOBJID playerID); - - /** - * Formats a time to a string, currently unused - * @param time the time to format - * @return the time formatted as string - */ - static std::string FormatTimeString(time_t time); - -private: - - /** - * The players that are currently racing - */ - std::vector m_RacingPlayers; - - /** - * The paths that are followed for the camera scenes - */ - std::u16string m_PathName; - - /** - * The ID of the activity for participating in this race - */ - uint32_t m_ActivityID; - - /** - * The world the players return to when they finish the race - */ - uint32_t m_MainWorld; - - /** - * The number of laps that are remaining for the winning player - */ - uint16_t m_RemainingLaps; - - /** - * The ID of the player that's currently winning the race - */ - LWOOBJID m_LeadingPlayer; - - /** - * The overall best lap from all the players - */ - float m_RaceBestLap; - - /** - * The overall best time from all the players - */ - float m_RaceBestTime; - - /** - * Whether or not the race has started - */ - bool m_Started; - - /** - * The time left until the race will start - */ - float m_StartTimer; - - /** - * The time left for loading the players - */ - float m_LoadTimer; - - /** - * Whether or not all players have loaded - */ - bool m_Loaded; - - /** - * The number of loaded players - */ - uint32_t m_LoadedPlayers; - - /** - * All the players that are in the lobby, loaded or not - */ - std::vector m_LobbyPlayers; - - /** - * The number of players that have finished the race - */ - uint32_t m_Finished; - - /** - * The time the race was started - */ - time_t m_StartTime; - - /** - * Timer for tracking how long a player was alone in this race - */ - float m_EmptyTimer; - - bool m_SoloRacing; - - /** - * Value for message box response to know if we are exiting the race via the activity dialogue - */ - const int32_t m_ActivityExitConfirm = 1; + RacingControlComponent(Entity* parent, int32_t componentId); }; + + +#endif //!__RACINGCONTROLCOMPONENT__H__ diff --git a/dGame/dComponents/VehicleRacingControlComponent.cpp b/dGame/dComponents/VehicleRacingControlComponent.cpp new file mode 100644 index 00000000..b4ee272e --- /dev/null +++ b/dGame/dComponents/VehicleRacingControlComponent.cpp @@ -0,0 +1,880 @@ +/** + * Thanks to Simon for his early research on the racing system. + */ + +#include "VehicleRacingControlComponent.h" + +#include "CharacterComponent.h" +#include "DestroyableComponent.h" +#include "EntityManager.h" +#include "GameMessages.h" +#include "InventoryComponent.h" +#include "Item.h" +#include "MissionComponent.h" +#include "ModuleAssemblyComponent.h" +#include "Player.h" +#include "PossessableComponent.h" +#include "PossessorComponent.h" +#include "eRacingTaskParam.h" +#include "Spawner.h" +#include "dServer.h" +#include "dZoneManager.h" +#include "dConfig.h" +#include "Loot.h" +#include "eMissionTaskType.h" +#include "dZoneManager.h" +#include "CDActivitiesTable.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 +#endif + +VehicleRacingControlComponent::VehicleRacingControlComponent(Entity* parent, int32_t componentId) : RacingControlComponent(parent, componentId) { + m_PathName = u"MainPath"; + m_RemainingLaps = 3; + m_LeadingPlayer = LWOOBJID_EMPTY; + m_RaceBestTime = 0; + m_RaceBestLap = 0; + m_Started = false; + m_StartTimer = 0; + m_Loaded = false; + m_LoadedPlayers = 0; + m_LoadTimer = 0; + m_Finished = 0; + m_StartTime = 0; + m_EmptyTimer = 0; + m_SoloRacing = Game::config->GetValue("solo_racing") == "1"; + + m_MainWorld = 1200; + const auto worldID = Game::server->GetZoneID(); + if (dZoneManager::Instance()->CheckIfAccessibleZone((worldID/10)*10)) m_MainWorld = (worldID/10)*10; + + m_ActivityID = 42; + CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable(); + std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.instanceMapID == worldID); }); + for (CDActivities activity : activities) m_ActivityID = activity.ActivityID; +} + +VehicleRacingControlComponent::~VehicleRacingControlComponent() {} + +void VehicleRacingControlComponent::OnPlayerLoaded(Entity* player) { + // If the race has already started, send the player back to the main world. + if (m_Loaded) { + auto* playerInstance = dynamic_cast(player); + + playerInstance->SendToZone(m_MainWorld); + + return; + } + + const auto objectID = player->GetObjectID(); + + m_LoadedPlayers++; + + Game::logger->Log("VehicleRacingControlComponent", "Loading player %i", + m_LoadedPlayers); + + m_LobbyPlayers.push_back(objectID); +} + +void VehicleRacingControlComponent::LoadPlayerVehicle(Entity* player, + uint32_t positionNumber, bool initialLoad) { + // Load the player's vehicle. + + if (player == nullptr) { + return; + } + + auto* inventoryComponent = player->GetComponent(); + + if (inventoryComponent == nullptr) { + return; + } + + // Find the player's vehicle. + + auto* item = inventoryComponent->FindItemByLot(8092); + + if (item == nullptr) { + Game::logger->Log("VehicleRacingControlComponent", "Failed to find item"); + + return; + } + + // Calculate the vehicle's starting position. + + auto* path = dZoneManager::Instance()->GetZone()->GetPath( + GeneralUtils::UTF16ToWTF8(m_PathName)); + + auto spawnPointEntities = EntityManager::Instance()->GetEntitiesByLOT(4843); + auto startPosition = NiPoint3::ZERO; + auto startRotation = NiQuaternion::IDENTITY; + const std::string placementAsString = std::to_string(positionNumber); + for (auto entity : spawnPointEntities) { + if (!entity) continue; + if (entity->GetVarAsString(u"placement") == placementAsString) { + startPosition = entity->GetPosition(); + startRotation = entity->GetRotation(); + break; + } + } + + // Make sure the player is at the correct position. + + GameMessages::SendTeleport(player->GetObjectID(), startPosition, + startRotation, player->GetSystemAddress(), true); + + // Spawn the vehicle entity. + + EntityInfo info{}; + info.lot = 8092; + info.pos = startPosition; + info.rot = startRotation; + info.spawnerID = m_ParentEntity->GetObjectID(); + + auto* carEntity = + EntityManager::Instance()->CreateEntity(info, nullptr, m_ParentEntity); + + // Make the vehicle a child of the racing controller. + m_ParentEntity->AddChild(carEntity); + + auto* destroyableComponent = carEntity->GetComponent(); + + // Setup the vehicle stats. + if (destroyableComponent != nullptr) { + destroyableComponent->SetMaxImagination(60); + destroyableComponent->SetImagination(0); + } + + // Setup the vehicle as being possessed by the player. + auto* possessableComponent = carEntity->GetComponent(); + + if (possessableComponent != nullptr) { + possessableComponent->SetPossessor(player->GetObjectID()); + } + + // Load the vehicle's assemblyPartLOTs for display. + auto* moduleAssemblyComponent = carEntity->GetComponent(); + + if (moduleAssemblyComponent) { + moduleAssemblyComponent->SetSubKey(item->GetSubKey()); + moduleAssemblyComponent->SetUseOptionalParts(false); + + for (auto* config : item->GetConfig()) { + if (config->GetKey() == u"assemblyPartLOTs") { + moduleAssemblyComponent->SetAssemblyPartsLOTs( + GeneralUtils::ASCIIToUTF16(config->GetValueAsString())); + } + } + } + + // Setup the player as possessing the vehicle. + auto* possessorComponent = player->GetComponent(); + + if (possessorComponent != nullptr) { + possessorComponent->SetPossessable(carEntity->GetObjectID()); + possessorComponent->SetPossessableType(ePossessionType::ATTACHED_VISIBLE); // for racing it's always Attached_Visible + } + + // Set the player's current activity as racing. + auto* characterComponent = player->GetComponent(); + + if (characterComponent != nullptr) { + characterComponent->SetIsRacing(true); + } + + // Init the player's racing entry. + if (initialLoad) { + m_RacingPlayers.push_back( + { player->GetObjectID(), + carEntity->GetObjectID(), + static_cast(m_RacingPlayers.size()), + false, + {}, + startPosition, + startRotation, + 0, + 0, + 0, + 0 }); + } + + // Construct and serialize everything when done. + + EntityManager::Instance()->ConstructEntity(carEntity); + EntityManager::Instance()->SerializeEntity(player); + EntityManager::Instance()->SerializeEntity(m_ParentEntity); + + GameMessages::SendRacingSetPlayerResetInfo( + m_ParentEntity->GetObjectID(), 0, 0, player->GetObjectID(), startPosition, 1, + UNASSIGNED_SYSTEM_ADDRESS); + + const auto playerID = player->GetObjectID(); + + // Reset the player to the start position during downtime, in case something + // went wrong. + m_ParentEntity->AddCallbackTimer(1, [this, playerID]() { + auto* player = EntityManager::Instance()->GetEntity(playerID); + + if (player == nullptr) { + return; + } + + GameMessages::SendRacingResetPlayerToLastReset( + m_ParentEntity->GetObjectID(), playerID, UNASSIGNED_SYSTEM_ADDRESS); + }); + + GameMessages::SendSetJetPackMode(player, false); + + // Set the vehicle's state. + GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), + m_ParentEntity->GetObjectID(), + UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendVehicleSetWheelLockState(carEntity->GetObjectID(), false, + initialLoad, + UNASSIGNED_SYSTEM_ADDRESS); + + // Make sure everything has the correct position. + GameMessages::SendTeleport(player->GetObjectID(), startPosition, + startRotation, player->GetSystemAddress(), true); + GameMessages::SendTeleport(carEntity->GetObjectID(), startPosition, + startRotation, player->GetSystemAddress(), true); +} + +void VehicleRacingControlComponent::OnRacingClientReady(Entity* player) { + // Notify the other players that this player is ready. + + for (auto& racingPlayer : m_RacingPlayers) { + if (racingPlayer.playerID != player->GetObjectID()) { + if (racingPlayer.playerLoaded) { + GameMessages::SendRacingPlayerLoaded( + m_ParentEntity->GetObjectID(), racingPlayer.playerID, + racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); + } + + continue; + } + + racingPlayer.playerLoaded = true; + + GameMessages::SendRacingPlayerLoaded( + m_ParentEntity->GetObjectID(), racingPlayer.playerID, + racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); + } + + EntityManager::Instance()->SerializeEntity(m_ParentEntity); +} + +void VehicleRacingControlComponent::OnRequestDie(Entity* player) { + // Sent by the client when they collide with something which should smash + // them. + + for (auto& racingPlayer : m_RacingPlayers) { + if (racingPlayer.playerID != player->GetObjectID()) { + continue; + } + + auto* vehicle = + EntityManager::Instance()->GetEntity(racingPlayer.vehicleID); + + if (!vehicle) return; + + if (!racingPlayer.noSmashOnReload) { + racingPlayer.smashedTimes++; + GameMessages::SendDie(vehicle, vehicle->GetObjectID(), LWOOBJID_EMPTY, true, + eKillType::VIOLENT, u"", 0, 0, 90.0f, false, true, 0); + + auto* destroyableComponent = vehicle->GetComponent(); + uint32_t respawnImagination = 0; + // Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live. + // 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); + } + + // 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, [=]() { + if (!vehicle || !this->m_ParentEntity) return; + GameMessages::SendRacingResetPlayerToLastReset( + m_ParentEntity->GetObjectID(), racingPlayer.playerID, + UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendVehicleStopBoost(vehicle, player->GetSystemAddress(), true); + + GameMessages::SendRacingSetPlayerResetInfo( + m_ParentEntity->GetObjectID(), racingPlayer.lap, + racingPlayer.respawnIndex, player->GetObjectID(), + racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1, + UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendResurrect(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); + EntityManager::Instance()->SerializeEntity(vehicle); + }); + + auto* characterComponent = player->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->UpdatePlayerStatistic(RacingTimesWrecked); + } + } else { + GameMessages::SendRacingSetPlayerResetInfo( + m_ParentEntity->GetObjectID(), racingPlayer.lap, + racingPlayer.respawnIndex, player->GetObjectID(), + racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1, + UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::SendRacingResetPlayerToLastReset( + m_ParentEntity->GetObjectID(), racingPlayer.playerID, + UNASSIGNED_SYSTEM_ADDRESS); + } + } +} + +void VehicleRacingControlComponent::OnRacingPlayerInfoResetFinished(Entity* player) { + // When the player has respawned. + + for (auto& racingPlayer : m_RacingPlayers) { + if (racingPlayer.playerID != player->GetObjectID()) { + continue; + } + + auto* vehicle = + EntityManager::Instance()->GetEntity(racingPlayer.vehicleID); + + if (vehicle == nullptr) { + return; + } + + racingPlayer.noSmashOnReload = false; + + return; + } +} + +void VehicleRacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id) { + auto* data = GetPlayerData(player->GetObjectID()); + + if (data == nullptr) { + return; + } + + if (id == "rewardButton") { + if (data->collectedRewards) { + return; + } + + data->collectedRewards = true; + + // Calculate the score, different loot depending on player count + const auto score = m_LoadedPlayers * 10 + data->finished; + + LootGenerator::Instance().GiveActivityLoot(player, m_ParentEntity, m_ActivityID, score); + + // Giving rewards + GameMessages::SendNotifyRacingClient( + m_ParentEntity->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"", + player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + + auto* missionComponent = player->GetComponent(); + + if (missionComponent == nullptr) return; + + missionComponent->Progress(eMissionTaskType::RACING, 0, (LWOOBJID)eRacingTaskParam::COMPETED_IN_RACE); // Progress task for competing in a race + missionComponent->Progress(eMissionTaskType::RACING, data->smashedTimes, (LWOOBJID)eRacingTaskParam::SAFE_DRIVER); // Finish a race without being smashed. + + // If solo racing is enabled OR if there are 3 players in the race, progress placement tasks. + if (m_SoloRacing || m_LoadedPlayers > 2) { + missionComponent->Progress(eMissionTaskType::RACING, data->finished, (LWOOBJID)eRacingTaskParam::FINISH_WITH_PLACEMENT); // Finish in 1st place on a race + if (data->finished == 1) { + missionComponent->Progress(eMissionTaskType::RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)eRacingTaskParam::FIRST_PLACE_MULTIPLE_TRACKS); // Finish in 1st place on multiple tracks. + missionComponent->Progress(eMissionTaskType::RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)eRacingTaskParam::WIN_RACE_IN_WORLD); // Finished first place in specific world. + } + if (data->finished == m_LoadedPlayers) { + missionComponent->Progress(eMissionTaskType::RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)eRacingTaskParam::LAST_PLACE_FINISH); // Finished first place in specific world. + } + } + } else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) { + auto* vehicle = EntityManager::Instance()->GetEntity(data->vehicleID); + + if (vehicle == nullptr) { + return; + } + + // Exiting race + GameMessages::SendNotifyRacingClient( + m_ParentEntity->GetObjectID(), 3, 0, LWOOBJID_EMPTY, u"", + player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + + auto* playerInstance = dynamic_cast(player); + + playerInstance->SendToZone(m_MainWorld); + + vehicle->Kill(); + } +} + +void VehicleRacingControlComponent::Serialize(RakNet::BitStream* outBitStream, + bool bIsInitialUpdate, + unsigned int& flags) { + // BEGIN Scripted Activity + + outBitStream->Write1(); + + outBitStream->Write(static_cast(m_RacingPlayers.size())); + for (const auto& player : m_RacingPlayers) { + outBitStream->Write(player.playerID); + + for (int i = 0; i < 10; i++) { + outBitStream->Write(player.data[i]); + } + } + + // END Scripted Activity + + outBitStream->Write1(); // Dirty? + outBitStream->Write(static_cast(m_RacingPlayers.size())); + + outBitStream->Write(!m_RacingPlayers.empty()); + if (!m_RacingPlayers.empty()) { + for (const auto& player : m_RacingPlayers) { + outBitStream->Write1(); // Has more date + + outBitStream->Write(player.playerID); + outBitStream->Write(player.vehicleID); + outBitStream->Write(player.playerIndex); + outBitStream->Write(player.playerLoaded); + } + + outBitStream->Write0(); // No more data + } + + outBitStream->Write(!m_RacingPlayers.empty()); + if (!m_RacingPlayers.empty()) { + for (const auto& player : m_RacingPlayers) { + outBitStream->Write1(); // Has more date + + outBitStream->Write(player.playerID); + outBitStream->Write(0); + } + + outBitStream->Write0(); // No more data + } + + outBitStream->Write1(); // Dirty? + + outBitStream->Write(m_RemainingLaps); + + outBitStream->Write(static_cast(m_PathName.size())); + for (const auto character : m_PathName) { + outBitStream->Write(character); + } + + outBitStream->Write1(); // ??? + outBitStream->Write1(); // ??? + + outBitStream->Write(m_LeadingPlayer); + outBitStream->Write(m_RaceBestLap); + outBitStream->Write(m_RaceBestTime); +} + +RacingPlayerInfo* VehicleRacingControlComponent::GetPlayerData(LWOOBJID playerID) { + for (auto& player : m_RacingPlayers) { + if (player.playerID == playerID) { + return &player; + } + } + + return nullptr; +} + +void VehicleRacingControlComponent::Update(float deltaTime) { + // This method is a mess. + + // Pre-load routine + if (!m_Loaded) { + // Check if any players has disconnected before loading in + for (size_t i = 0; i < m_LobbyPlayers.size(); i++) { + auto* playerEntity = + EntityManager::Instance()->GetEntity(m_LobbyPlayers[i]); + + if (playerEntity == nullptr) { + --m_LoadedPlayers; + + m_LobbyPlayers.erase(m_LobbyPlayers.begin() + i); + + return; + } + } + + if (m_LoadedPlayers >= 2 || (m_LoadedPlayers == 1 && m_SoloRacing)) { + m_LoadTimer += deltaTime; + } else { + m_EmptyTimer += deltaTime; + } + + // If a player happens to be left alone for more then 30 seconds without + // anyone else loading in, send them back to the main world + if (m_EmptyTimer >= 30) { + for (const auto player : m_LobbyPlayers) { + auto* playerEntity = + EntityManager::Instance()->GetEntity(player); + + if (playerEntity == nullptr) { + continue; + } + + auto* playerInstance = dynamic_cast(playerEntity); + + playerInstance->SendToZone(m_MainWorld); + } + + m_LobbyPlayers.clear(); + } + + // From the first 2 players loading in the rest have a max of 15 seconds + // to load in, can raise this if it's too low + if (m_LoadTimer >= 15) { + Game::logger->Log("VehicleRacingControlComponent", + "Loading all players..."); + + for (size_t positionNumber = 0; positionNumber < m_LobbyPlayers.size(); positionNumber++) { + Game::logger->Log("VehicleRacingControlComponent", + "Loading player now!"); + + auto* player = + EntityManager::Instance()->GetEntity(m_LobbyPlayers[positionNumber]); + + if (player == nullptr) { + return; + } + + Game::logger->Log("VehicleRacingControlComponent", + "Loading player now NOW!"); + + LoadPlayerVehicle(player, positionNumber + 1, true); + + m_Loaded = true; + } + + m_Loaded = true; + } + + return; + } + + // The players who will be participating have loaded + if (!m_Started) { + // Check if anyone has disconnected during this period + for (size_t i = 0; i < m_RacingPlayers.size(); i++) { + auto* playerEntity = EntityManager::Instance()->GetEntity( + m_RacingPlayers[i].playerID); + + if (playerEntity == nullptr) { + m_RacingPlayers.erase(m_RacingPlayers.begin() + i); + + --m_LoadedPlayers; + + return; + } + } + + // If less then 2 players are left, send the rest back to the main world + if (m_LoadedPlayers < 2 && !(m_LoadedPlayers == 1 && m_SoloRacing)) { + for (const auto player : m_LobbyPlayers) { + auto* playerEntity = + EntityManager::Instance()->GetEntity(player); + + if (playerEntity == nullptr) { + continue; + } + + auto* playerInstance = dynamic_cast(playerEntity); + + playerInstance->SendToZone(m_MainWorld); + } + + return; + } + + // Check if all players have send a ready message + + int32_t readyPlayers = 0; + + for (const auto& player : m_RacingPlayers) { + if (player.playerLoaded) { + ++readyPlayers; + } + } + + if (readyPlayers >= m_LoadedPlayers) { + // Setup for racing + if (m_StartTimer == 0) { + GameMessages::SendNotifyRacingClient( + m_ParentEntity->GetObjectID(), 1, 0, LWOOBJID_EMPTY, u"", + LWOOBJID_EMPTY, UNASSIGNED_SYSTEM_ADDRESS); + + for (const auto& player : m_RacingPlayers) { + auto* vehicle = + EntityManager::Instance()->GetEntity(player.vehicleID); + auto* playerEntity = + EntityManager::Instance()->GetEntity(player.playerID); + + if (vehicle != nullptr && playerEntity != nullptr) { + GameMessages::SendTeleport( + player.playerID, player.respawnPosition, + player.respawnRotation, + playerEntity->GetSystemAddress(), true); + + vehicle->SetPosition(player.respawnPosition); + vehicle->SetRotation(player.respawnRotation); + + auto* destroyableComponent = vehicle->GetComponent(); + + if (destroyableComponent != nullptr) { + destroyableComponent->SetImagination(0); + } + + EntityManager::Instance()->SerializeEntity(vehicle); + EntityManager::Instance()->SerializeEntity( + playerEntity); + } + } + + // Spawn imagination pickups + auto* minSpawner = dZoneManager::Instance()->GetSpawnersByName( + "ImaginationSpawn_Min")[0]; + auto* medSpawner = dZoneManager::Instance()->GetSpawnersByName( + "ImaginationSpawn_Med")[0]; + auto* maxSpawner = dZoneManager::Instance()->GetSpawnersByName( + "ImaginationSpawn_Max")[0]; + + minSpawner->Activate(); + + if (m_LoadedPlayers > 2) { + medSpawner->Activate(); + } + + if (m_LoadedPlayers > 4) { + maxSpawner->Activate(); + } + + // Reset players to their start location, without smashing them + for (auto& player : m_RacingPlayers) { + auto* vehicleEntity = + EntityManager::Instance()->GetEntity(player.vehicleID); + auto* playerEntity = + EntityManager::Instance()->GetEntity(player.playerID); + + if (vehicleEntity == nullptr || playerEntity == nullptr) { + continue; + } + + player.noSmashOnReload = true; + + OnRequestDie(playerEntity); + } + } + // This 6 seconds seems to be hardcoded in the client, start race + // after that amount of time + else if (m_StartTimer >= 6) { + // Activate the players movement + for (auto& player : m_RacingPlayers) { + auto* vehicleEntity = + EntityManager::Instance()->GetEntity(player.vehicleID); + auto* playerEntity = + EntityManager::Instance()->GetEntity(player.playerID); + + if (vehicleEntity == nullptr || playerEntity == nullptr) { + continue; + } + + GameMessages::SendVehicleUnlockInput( + player.vehicleID, false, UNASSIGNED_SYSTEM_ADDRESS); + } + + // Start the race + GameMessages::SendActivityStart(m_ParentEntity->GetObjectID(), + UNASSIGNED_SYSTEM_ADDRESS); + + m_Started = true; + + Game::logger->Log("VehicleRacingControlComponent", "Starting race"); + + EntityManager::Instance()->SerializeEntity(m_ParentEntity); + + m_StartTime = std::time(nullptr); + } + + m_StartTimer += deltaTime; + } else { + m_StartTimer = 0; + } + + return; + } + + // Race routines + auto* path = dZoneManager::Instance()->GetZone()->GetPath( + GeneralUtils::UTF16ToWTF8(m_PathName)); + + for (auto& player : m_RacingPlayers) { + auto* vehicle = EntityManager::Instance()->GetEntity(player.vehicleID); + auto* playerEntity = + EntityManager::Instance()->GetEntity(player.playerID); + + if (vehicle == nullptr || playerEntity == nullptr) { + continue; + } + + const auto vehiclePosition = vehicle->GetPosition(); + + // 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_ParentEntity->GetObjectID(), + LWOOBJID_EMPTY, true, eKillType::VIOLENT, u"", 0, 0, 0, + true, false, 0); + + OnRequestDie(playerEntity); + + continue; + } + + // Loop through all the waypoints and see if the player has reached a + // new checkpoint + uint32_t respawnIndex = 0; + for (const auto& waypoint : path->pathWaypoints) { + if (player.lap == 3) { + break; + } + + if (player.respawnIndex == respawnIndex) { + ++respawnIndex; + + continue; + } + + const auto& position = waypoint.position; + + if (std::abs((int)respawnIndex - (int)player.respawnIndex) > 10 && + player.respawnIndex != path->pathWaypoints.size() - 1) { + ++respawnIndex; + + continue; + } + + if (Vector3::DistanceSquared(position, vehiclePosition) > 50 * 50) { + ++respawnIndex; + + continue; + } + + // Only go upwards, except if we've lapped + // Not sure how we are supposed to check if they've reach a + // checkpoint, within 50 units seems safe + if (!(respawnIndex > player.respawnIndex || + player.respawnIndex == path->pathWaypoints.size() - 1)) { + ++respawnIndex; + + continue; + } + + // Some offset up to make they don't fall through the terrain on a + // respawn, seems to fix itself to the track anyhow + player.respawnPosition = position + NiPoint3::UNIT_Y * 5; + player.respawnRotation = vehicle->GetRotation(); + player.respawnIndex = respawnIndex; + + // Reached the start point, lapped + if (respawnIndex == 0) { + time_t lapTime = std::time(nullptr) - (player.lap == 0 ? m_StartTime : player.lapTime); + + // Cheating check + if (lapTime < 40) { + continue; + } + + player.lap++; + + player.lapTime = std::time(nullptr); + + if (player.bestLapTime == 0 || player.bestLapTime > lapTime) { + player.bestLapTime = lapTime; + + Game::logger->Log("VehicleRacingControlComponent", + "Best lap time (%llu)", lapTime); + } + + auto* missionComponent = playerEntity->GetComponent(); + + if (missionComponent != nullptr) { + + // Progress lap time tasks + missionComponent->Progress(eMissionTaskType::RACING, (lapTime) * 1000, (LWOOBJID)eRacingTaskParam::LAP_TIME); + + if (player.lap == 3) { + m_Finished++; + player.finished = m_Finished; + + const auto raceTime = + (std::time(nullptr) - m_StartTime); + + player.raceTime = raceTime; + + Game::logger->Log("VehicleRacingControlComponent", + "Completed time %llu, %llu", + raceTime, raceTime * 1000); + + // Entire race time + missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, (LWOOBJID)eRacingTaskParam::TOTAL_TRACK_TIME); + + auto* characterComponent = playerEntity->GetComponent(); + if (characterComponent != nullptr) { + characterComponent->TrackRaceCompleted(m_Finished == 1); + } + + // TODO: Figure out how to update the GUI leaderboard. + } + } + + Game::logger->Log("VehicleRacingControlComponent", + "Lapped (%i) in (%llu)", player.lap, + lapTime); + } + + Game::logger->Log("VehicleRacingControlComponent", + "Reached point (%i)/(%i)", player.respawnIndex, + path->pathWaypoints.size()); + + break; + } + } +} + +std::string VehicleRacingControlComponent::FormatTimeString(time_t time) { + int32_t min = time / 60; + time -= min * 60; + int32_t sec = time; + + std::string minText; + std::string secText; + + if (min <= 0) { + minText = "0"; + } else { + minText = std::to_string(min); + } + + if (sec <= 0) { + secText = "00"; + } else if (sec <= 9) { + secText = "0" + std::to_string(sec); + } else { + secText = std::to_string(sec); + } + + return minText + ":" + secText + ".00"; +} diff --git a/dGame/dComponents/VehicleRacingControlComponent.h b/dGame/dComponents/VehicleRacingControlComponent.h new file mode 100644 index 00000000..ed6b15f0 --- /dev/null +++ b/dGame/dComponents/VehicleRacingControlComponent.h @@ -0,0 +1,254 @@ +/** + * Thanks to Simon for his early research on the racing system. + */ + +#pragma once + +#include "BitStream.h" +#include "Entity.h" +#include "RacingControlComponent.h" +#include "eReplicaComponentType.h" + + /** + * Information for each player in the race + */ +struct RacingPlayerInfo { + + /** + * The ID of the player + */ + LWOOBJID playerID; + + /** + * The ID of the car the player is driving + */ + LWOOBJID vehicleID; + + /** + * The index of this player in the list of players + */ + uint32_t playerIndex; + + /** + * Whether the player has finished loading or not + */ + bool playerLoaded; + + /** + * Scripted activity component score + */ + float data[10]{}; + + /** + * Point that the player will respawn at if they smash their car + */ + NiPoint3 respawnPosition; + + /** + * Rotation that the player will respawn at if they smash their car + */ + NiQuaternion respawnRotation; + + /** + * The index in the respawn point the player is now at + */ + uint32_t respawnIndex; + + /** + * The number of laps the player has completed + */ + uint32_t lap; + + /** + * Whether or not the player has finished the race + */ + uint32_t finished; + + /** + * Unused + */ + uint16_t reachedPoints; + + /** + * The fastest lap time of the player + */ + time_t bestLapTime = 0; + + /** + * The current lap time of the player + */ + time_t lapTime = 0; + + /** + * The number of times this player smashed their car + */ + uint32_t smashedTimes = 0; + + /** + * Whether or not the player should be smashed if the game is reloaded + */ + bool noSmashOnReload = false; + + /** + * Whether or not this player has collected their rewards from completing the race + */ + bool collectedRewards = false; + + /** + * Unused + */ + time_t raceTime = 0; +}; + +/** + * Component that's attached to a manager entity in each race zone that loads player vehicles, keep scores, etc. + */ +class VehicleRacingControlComponent : public RacingControlComponent { +public: + inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL; + + VehicleRacingControlComponent(Entity* parentEntity, int32_t componentId); + ~VehicleRacingControlComponent(); + + void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); + void Update(float deltaTime); + + /** + * Invoked when a player loads into the zone. + */ + void OnPlayerLoaded(Entity* player); + + /** + * Initalize the player's vehicle. + * + * @param player The player who's vehicle to initialize. + * @param initialLoad Is this the first time the player is loading in this race? + */ + void LoadPlayerVehicle(Entity* player, uint32_t positionNumber, bool initialLoad = false); + + /** + * Invoked when the client says it has loaded in. + */ + void OnRacingClientReady(Entity* player); + + /** + * Invoked when the client says it should be smashed. + */ + void OnRequestDie(Entity* player); + + /** + * Invoked when the player has finished respawning. + */ + void OnRacingPlayerInfoResetFinished(Entity* player); + + /** + * Invoked when the player responds to the GUI. + */ + void HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id); + + /** + * Get the racing data from a player's LWOOBJID. + */ + RacingPlayerInfo* GetPlayerData(LWOOBJID playerID); + + /** + * Formats a time to a string, currently unused + * @param time the time to format + * @return the time formatted as string + */ + static std::string FormatTimeString(time_t time); + +private: + + /** + * The players that are currently racing + */ + std::vector m_RacingPlayers; + + /** + * The paths that are followed for the camera scenes + */ + std::u16string m_PathName; + + /** + * The ID of the activity for participating in this race + */ + uint32_t m_ActivityID; + + /** + * The world the players return to when they finish the race + */ + uint32_t m_MainWorld; + + /** + * The number of laps that are remaining for the winning player + */ + uint16_t m_RemainingLaps; + + /** + * The ID of the player that's currently winning the race + */ + LWOOBJID m_LeadingPlayer; + + /** + * The overall best lap from all the players + */ + float m_RaceBestLap; + + /** + * The overall best time from all the players + */ + float m_RaceBestTime; + + /** + * Whether or not the race has started + */ + bool m_Started; + + /** + * The time left until the race will start + */ + float m_StartTimer; + + /** + * The time left for loading the players + */ + float m_LoadTimer; + + /** + * Whether or not all players have loaded + */ + bool m_Loaded; + + /** + * The number of loaded players + */ + uint32_t m_LoadedPlayers; + + /** + * All the players that are in the lobby, loaded or not + */ + std::vector m_LobbyPlayers; + + /** + * The number of players that have finished the race + */ + uint32_t m_Finished; + + /** + * The time the race was started + */ + time_t m_StartTime; + + /** + * Timer for tracking how long a player was alone in this race + */ + float m_EmptyTimer; + + bool m_SoloRacing; + + /** + * Value for message box response to know if we are exiting the race via the activity dialogue + */ + const int32_t m_ActivityExitConfirm = 1; +}; diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index cffaa9a8..a612566a 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -25,7 +25,7 @@ #include "CDClientManager.h" #include "CDSkillBehaviorTable.h" #include "SkillComponent.h" -#include "RacingControlComponent.h" +#include "VehicleRacingControlComponent.h" #include "RequestServerProjectileImpact.h" #include "SyncSkill.h" #include "StartSkill.h" @@ -126,10 +126,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System std::vector racingControllers = EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); for (Entity* racingController : racingControllers) { - auto* racingComponent = racingController->GetComponent(); - if (racingComponent != nullptr) { - racingComponent->OnPlayerLoaded(entity); - } + auto* vehicleRacingControlComponent = racingController->GetComponent(); + if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnPlayerLoaded(entity); } Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity(); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 6440bfee..ea7e60c0 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -73,7 +73,7 @@ #include "RenderComponent.h" #include "PossessableComponent.h" #include "PossessorComponent.h" -#include "RacingControlComponent.h" +#include "VehicleRacingControlComponent.h" #include "RailActivatorComponent.h" #include "LevelProgressionComponent.h" @@ -3889,11 +3889,9 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream* inStream, Entity* scriptedActivityComponent->HandleMessageBoxResponse(userEntity, GeneralUtils::UTF16ToWTF8(identifier)); } - auto* racingControlComponent = entity->GetComponent(); + auto* vehicleRacingControlComponent = entity->GetComponent(); - if (racingControlComponent != nullptr) { - racingControlComponent->HandleMessageBoxResponse(userEntity, iButton, GeneralUtils::UTF16ToWTF8(identifier)); - } + if (vehicleRacingControlComponent) vehicleRacingControlComponent->HandleMessageBoxResponse(userEntity, iButton, GeneralUtils::UTF16ToWTF8(identifier)); for (auto* shootingGallery : EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::SHOOTING_GALLERY)) { shootingGallery->OnMessageBoxResponse(userEntity, iButton, identifier, userData); @@ -4137,13 +4135,11 @@ void GameMessages::HandleRacingClientReady(RakNet::BitStream* inStream, Entity* return; } - auto* racingControlComponent = dZoneManager::Instance()->GetZoneControlObject()->GetComponent(); + auto* vehicleRacingControlComponent = dZoneManager::Instance()->GetZoneControlObject()->GetComponent(); - if (racingControlComponent == nullptr) { - return; - } + if (!vehicleRacingControlComponent) return; - racingControlComponent->OnRacingClientReady(player); + vehicleRacingControlComponent->OnRacingClientReady(player); } @@ -4187,23 +4183,20 @@ void GameMessages::HandleRequestDie(RakNet::BitStream* inStream, Entity* entity, auto* zoneController = dZoneManager::Instance()->GetZoneControlObject(); - auto* racingControlComponent = zoneController->GetComponent(); + auto* vehicleRacingControlComponent = zoneController->GetComponent(); Game::logger->Log("HandleRequestDie", "Got die request: %i", entity->GetLOT()); - if (racingControlComponent != nullptr) { - auto* possessableComponent = entity->GetComponent(); + if (!vehicleRacingControlComponent) return; + auto* possessableComponent = entity->GetComponent(); - if (possessableComponent != nullptr) { - entity = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor()); + if (possessableComponent) { + entity = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor()); - if (entity == nullptr) { - return; - } - } - - racingControlComponent->OnRequestDie(entity); + if (!entity) return; } + + vehicleRacingControlComponent->OnRequestDie(entity); } @@ -4230,13 +4223,11 @@ void GameMessages::HandleRacingPlayerInfoResetFinished(RakNet::BitStream* inStre auto* zoneController = dZoneManager::Instance()->GetZoneControlObject(); - auto* racingControlComponent = zoneController->GetComponent(); + auto* vehicleRacingControlComponent = zoneController->GetComponent(); Game::logger->Log("HandleRacingPlayerInfoResetFinished", "Got finished: %i", entity->GetLOT()); - if (racingControlComponent != nullptr) { - racingControlComponent->OnRacingPlayerInfoResetFinished(player); - } + if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRacingPlayerInfoResetFinished(player); } void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr) { diff --git a/dScripts/02_server/Map/FV/Racing/RaceMaelstromGeiser.cpp b/dScripts/02_server/Map/FV/Racing/RaceMaelstromGeiser.cpp index 155be92b..a5063738 100644 --- a/dScripts/02_server/Map/FV/Racing/RaceMaelstromGeiser.cpp +++ b/dScripts/02_server/Map/FV/Racing/RaceMaelstromGeiser.cpp @@ -3,7 +3,7 @@ #include "PossessableComponent.h" #include "PossessorComponent.h" #include "EntityManager.h" -#include "RacingControlComponent.h" +#include "VehicleRacingControlComponent.h" #include "dZoneManager.h" void RaceMaelstromGeiser::OnStartup(Entity* self) { @@ -59,11 +59,9 @@ void RaceMaelstromGeiser::OnProximityUpdate(Entity* self, Entity* entering, std: auto* zoneController = dZoneManager::Instance()->GetZoneControlObject(); - auto* racingControlComponent = zoneController->GetComponent(); + auto* vehicleRacingControlComponent = zoneController->GetComponent(); - if (racingControlComponent != nullptr) { - racingControlComponent->OnRequestDie(player); - } + if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRequestDie(player); } void RaceMaelstromGeiser::OnTimerDone(Entity* self, std::string timerName) { diff --git a/dScripts/ai/ACT/ActVehicleDeathTrigger.cpp b/dScripts/ai/ACT/ActVehicleDeathTrigger.cpp index 76c0289e..368dcab2 100644 --- a/dScripts/ai/ACT/ActVehicleDeathTrigger.cpp +++ b/dScripts/ai/ACT/ActVehicleDeathTrigger.cpp @@ -1,7 +1,7 @@ #include "ActVehicleDeathTrigger.h" #include "PossessableComponent.h" #include "GameMessages.h" -#include "RacingControlComponent.h" +#include "VehicleRacingControlComponent.h" #include "dZoneManager.h" #include "EntityManager.h" #include "PossessorComponent.h" @@ -44,9 +44,7 @@ void ActVehicleDeathTrigger::OnCollisionPhantom(Entity* self, Entity* target) { auto* zoneController = dZoneManager::Instance()->GetZoneControlObject(); - auto* racingControlComponent = zoneController->GetComponent(); + auto* vehicleRacingControlComponent = zoneController->GetComponent(); - if (racingControlComponent != nullptr) { - racingControlComponent->OnRequestDie(player); - } + if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRequestDie(player); }