DarkflameServer/dGame/dComponents/RacingControlComponent.cpp

888 lines
25 KiB
C++
Raw Normal View History

/**
* 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 "VehiclePhysicsComponent.h"
#include "dServer.h"
#include "dZoneManager.h"
#include "dConfig.h"
#include "Loot.h"
#include "eMissionTaskType.h"
#include "LeaderboardManager.h"
#include "dZoneManager.h"
#include "CDActivitiesTable.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif
2022-07-28 13:39:57 +00:00
RacingControlComponent::RacingControlComponent(Entity* parent)
: Component(parent) {
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;
2022-07-28 13:39:57 +00:00
const auto worldID = Game::server->GetZoneID();
if (dZoneManager::Instance()->CheckIfAccessibleZone((worldID / 10) * 10)) m_MainWorld = (worldID / 10) * 10;
2022-07-28 13:39:57 +00:00
m_ActivityID = 42;
CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable<CDActivitiesTable>();
std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.instanceMapID == worldID); });
for (CDActivities activity : activities) m_ActivityID = activity.ActivityID;
}
RacingControlComponent::~RacingControlComponent() {}
2022-07-28 13:39:57 +00:00
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*>(player);
2022-07-28 13:39:57 +00:00
playerInstance->SendToZone(m_MainWorld);
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
const auto objectID = player->GetObjectID();
2022-07-28 13:39:57 +00:00
m_LoadedPlayers++;
2022-07-28 13:39:57 +00:00
Game::logger->Log("RacingControlComponent", "Loading player %i",
m_LoadedPlayers);
2022-07-28 13:39:57 +00:00
m_LobbyPlayers.push_back(objectID);
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent::LoadPlayerVehicle(Entity* player,
2022-12-22 13:16:18 +00:00
uint32_t positionNumber, bool initialLoad) {
2022-07-28 13:39:57 +00:00
// Load the player's vehicle.
2022-07-28 13:39:57 +00:00
if (player == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
2022-07-28 13:39:57 +00:00
if (inventoryComponent == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
// Find the player's vehicle.
2022-07-28 13:39:57 +00:00
auto* item = inventoryComponent->FindItemByLot(8092);
2022-07-28 13:39:57 +00:00
if (item == nullptr) {
Game::logger->Log("RacingControlComponent", "Failed to find item");
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
// Calculate the vehicle's starting position.
2022-07-28 13:39:57 +00:00
auto* path = dZoneManager::Instance()->GetZone()->GetPath(
GeneralUtils::UTF16ToWTF8(m_PathName));
2022-12-22 13:16:18 +00:00
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;
}
}
2022-07-28 13:39:57 +00:00
// Make sure the player is at the correct position.
2022-07-28 13:39:57 +00:00
GameMessages::SendTeleport(player->GetObjectID(), startPosition,
startRotation, player->GetSystemAddress(), true);
2022-07-28 13:39:57 +00:00
// Spawn the vehicle entity.
2022-07-28 13:39:57 +00:00
EntityInfo info{};
info.lot = 8092;
info.pos = startPosition;
info.rot = startRotation;
info.spawnerID = m_Parent->GetObjectID();
2022-07-28 13:39:57 +00:00
auto* carEntity =
EntityManager::Instance()->CreateEntity(info, nullptr, m_Parent);
2022-07-28 13:39:57 +00:00
// Make the vehicle a child of the racing controller.
m_Parent->AddChild(carEntity);
2022-07-28 13:39:57 +00:00
auto* destroyableComponent =
carEntity->GetComponent<DestroyableComponent>();
2022-07-28 13:39:57 +00:00
// Setup the vehicle stats.
if (destroyableComponent != nullptr) {
destroyableComponent->SetMaxImagination(60);
destroyableComponent->SetImagination(0);
}
2022-07-28 13:39:57 +00:00
// Setup the vehicle as being possessed by the player.
auto* possessableComponent =
carEntity->GetComponent<PossessableComponent>();
2022-07-28 13:39:57 +00:00
if (possessableComponent != nullptr) {
possessableComponent->SetPossessor(player->GetObjectID());
}
2022-07-28 13:39:57 +00:00
// Load the vehicle's assemblyPartLOTs for display.
auto* moduleAssemblyComponent =
carEntity->GetComponent<ModuleAssemblyComponent>();
2022-07-28 13:39:57 +00:00
if (moduleAssemblyComponent) {
moduleAssemblyComponent->SetSubKey(item->GetSubKey());
moduleAssemblyComponent->SetUseOptionalParts(false);
2022-07-28 13:39:57 +00:00
for (auto* config : item->GetConfig()) {
if (config->GetKey() == u"assemblyPartLOTs") {
moduleAssemblyComponent->SetAssemblyPartsLOTs(
GeneralUtils::ASCIIToUTF16(config->GetValueAsString()));
}
}
}
2022-07-28 13:39:57 +00:00
// Setup the player as possessing the vehicle.
auto* possessorComponent = player->GetComponent<PossessorComponent>();
2022-07-28 13:39:57 +00:00
if (possessorComponent != nullptr) {
possessorComponent->SetPossessable(carEntity->GetObjectID());
possessorComponent->SetPossessableType(ePossessionType::ATTACHED_VISIBLE); // for racing it's always Attached_Visible
}
2022-07-28 13:39:57 +00:00
// Set the player's current activity as racing.
auto* characterComponent = player->GetComponent<CharacterComponent>();
2022-07-28 13:39:57 +00:00
if (characterComponent != nullptr) {
characterComponent->SetIsRacing(true);
}
2022-07-28 13:39:57 +00:00
// Init the player's racing entry.
if (initialLoad) {
m_RacingPlayers.push_back(
{ player->GetObjectID(),
carEntity->GetObjectID(),
static_cast<uint32_t>(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_Parent);
GameMessages::SendRacingSetPlayerResetInfo(
m_Parent->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_Parent->AddCallbackTimer(1, [this, playerID]() {
auto* player = EntityManager::Instance()->GetEntity(playerID);
if (player == nullptr) {
return;
}
GameMessages::SendRacingResetPlayerToLastReset(
m_Parent->GetObjectID(), playerID, UNASSIGNED_SYSTEM_ADDRESS);
});
GameMessages::SendSetJetPackMode(player, false);
// Set the vehicle's state.
GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(),
m_Parent->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);
2022-07-28 13:39:57 +00:00
GameMessages::SendTeleport(carEntity->GetObjectID(), startPosition,
startRotation, player->GetSystemAddress(), true);
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent::OnRacingClientReady(Entity* player) {
// Notify the other players that this player is ready.
2022-07-28 13:39:57 +00:00
for (auto& racingPlayer : m_RacingPlayers) {
if (racingPlayer.playerID != player->GetObjectID()) {
if (racingPlayer.playerLoaded) {
GameMessages::SendRacingPlayerLoaded(
m_Parent->GetObjectID(), racingPlayer.playerID,
racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS);
}
2022-07-28 13:39:57 +00:00
continue;
}
2022-07-28 13:39:57 +00:00
racingPlayer.playerLoaded = true;
2022-07-28 13:39:57 +00:00
GameMessages::SendRacingPlayerLoaded(
m_Parent->GetObjectID(), racingPlayer.playerID,
racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS);
}
2022-07-28 13:39:57 +00:00
EntityManager::Instance()->SerializeEntity(m_Parent);
}
2022-07-28 13:39:57 +00:00
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;
2022-07-28 13:39:57 +00:00
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<DestroyableComponent>();
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<int32_t>(ceil(destroyableComponent->GetImagination() / 2.0f / 10.0f)) * 10.0f;
GameMessages::SendSetResurrectRestoreValues(vehicle, -1, -1, respawnImagination);
}
2022-07-28 13:39:57 +00:00
// 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_Parent) return;
GameMessages::SendRacingResetPlayerToLastReset(
m_Parent->GetObjectID(), racingPlayer.playerID,
UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendVehicleStopBoost(vehicle, player->GetSystemAddress(), true);
GameMessages::SendRacingSetPlayerResetInfo(
m_Parent->GetObjectID(), racingPlayer.lap,
racingPlayer.respawnIndex, player->GetObjectID(),
racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1,
UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendResurrect(vehicle);
auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>();
// 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<CharacterComponent>();
if (characterComponent != nullptr) {
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);
2022-07-28 13:39:57 +00:00
}
}
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent::OnRacingPlayerInfoResetFinished(Entity* player) {
// When the player has respawned.
2022-07-28 13:39:57 +00:00
for (auto& racingPlayer : m_RacingPlayers) {
if (racingPlayer.playerID != player->GetObjectID()) {
continue;
}
2022-07-28 13:39:57 +00:00
auto* vehicle =
EntityManager::Instance()->GetEntity(racingPlayer.vehicleID);
2022-07-28 13:39:57 +00:00
if (vehicle == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
racingPlayer.noSmashOnReload = false;
2022-07-28 13:39:57 +00:00
return;
}
}
void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id) {
2022-07-28 13:39:57 +00:00
auto* data = GetPlayerData(player->GetObjectID());
2022-07-28 13:39:57 +00:00
if (data == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
if (id == "rewardButton") {
if (data->collectedRewards) return;
2022-07-28 13:39:57 +00:00
data->collectedRewards = true;
2022-07-28 13:39:57 +00:00
// Calculate the score, different loot depending on player count
const auto score = m_LoadedPlayers * 10 + data->finished;
2022-07-28 13:39:57 +00:00
LootGenerator::Instance().GiveActivityLoot(player, m_Parent, m_ActivityID, score);
2022-07-28 13:39:57 +00:00
// Giving rewards
GameMessages::SendNotifyRacingClient(
m_Parent->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"",
player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
2022-07-28 13:39:57 +00:00
auto* missionComponent = player->GetComponent<MissionComponent>();
2022-07-28 13:39:57 +00:00
if (missionComponent == nullptr) return;
Implementing and Fixing All Racing Achievements (#366) * Grammatical changes in comments * Grammatical fixes in comments Small grammatical fixes found in comments throughout the code. * Added descriptions to functions Added descriptions to functions that didn't have them to keep the code well documented * Created RacingTaskParam.h Created RacingTaskParam so eliminate magic numbers in the original implementation of completing racing missions. * Updated magic numbers in Mission.cpp Updated magic numbers in Mission.cpp to a meaningful name. * Implemented racing smashable task progression Previously, races did not progress tasks for smashing Entities. Now all achievements tracking smashables track them correctly. This has been implemented in the three Entities that can be smashed in a race (imagination boxes, track specific smashables, Forbidden Valley dragon eggs). * Updated race imagination task progression Race imagination now no longer uses a magic number when passed to missionComponent. Instead we use a number defined in an enum located in RacingTaskParam.h * Updated Race task checks Racing tasks for completing races without smashing now no longer auto complete the whole chain of missions. Tasks that track placing on tracks and races overall now properly complete. Tasks that count how many missions in a zone are completed now function. Tasks that track race completions in multiple areas now function. * Updated RacingControlComponent.cpp Fixed any tasks that required 3 players to now require 3 or more players in a race to progress. This restriction is ignored if the world config opted in for solo racing to allow progression in solo worlds. Updated magic numbers sent into missionComponent->Progress to an enum created in this PR. Fixed some indentation. * Fixed a grammatical error in variable name Fixed a grammatical error in the enum for task params
2022-02-05 11:28:17 +00:00
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.
Implementing and Fixing All Racing Achievements (#366) * Grammatical changes in comments * Grammatical fixes in comments Small grammatical fixes found in comments throughout the code. * Added descriptions to functions Added descriptions to functions that didn't have them to keep the code well documented * Created RacingTaskParam.h Created RacingTaskParam so eliminate magic numbers in the original implementation of completing racing missions. * Updated magic numbers in Mission.cpp Updated magic numbers in Mission.cpp to a meaningful name. * Implemented racing smashable task progression Previously, races did not progress tasks for smashing Entities. Now all achievements tracking smashables track them correctly. This has been implemented in the three Entities that can be smashed in a race (imagination boxes, track specific smashables, Forbidden Valley dragon eggs). * Updated race imagination task progression Race imagination now no longer uses a magic number when passed to missionComponent. Instead we use a number defined in an enum located in RacingTaskParam.h * Updated Race task checks Racing tasks for completing races without smashing now no longer auto complete the whole chain of missions. Tasks that track placing on tracks and races overall now properly complete. Tasks that count how many missions in a zone are completed now function. Tasks that track race completions in multiple areas now function. * Updated RacingControlComponent.cpp Fixed any tasks that required 3 players to now require 3 or more players in a race to progress. This restriction is ignored if the world config opted in for solo racing to allow progression in solo worlds. Updated magic numbers sent into missionComponent->Progress to an enum created in this PR. Fixed some indentation. * Fixed a grammatical error in variable name Fixed a grammatical error in the enum for task params
2022-02-05 11:28:17 +00:00
2022-07-28 13:39:57 +00:00
// 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
2022-07-28 13:39:57 +00:00
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.
2022-07-28 13:39:57 +00:00
}
if (data->finished == m_LoadedPlayers) {
missionComponent->Progress(eMissionTaskType::RACING, dZoneManager::Instance()->GetZone()->GetWorldID(), (LWOOBJID)eRacingTaskParam::LAST_PLACE_FINISH); // Finished first place in specific world.
2022-07-28 13:39:57 +00:00
}
}
} else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) {
2022-07-28 13:39:57 +00:00
auto* vehicle = EntityManager::Instance()->GetEntity(data->vehicleID);
2022-07-28 13:39:57 +00:00
if (vehicle == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
// Exiting race
GameMessages::SendNotifyRacingClient(
m_Parent->GetObjectID(), 3, 0, LWOOBJID_EMPTY, u"",
player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
2022-07-28 13:39:57 +00:00
auto* playerInstance = dynamic_cast<Player*>(player);
2022-07-28 13:39:57 +00:00
playerInstance->SendToZone(m_MainWorld);
2022-07-28 13:39:57 +00:00
vehicle->Kill();
}
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent::Serialize(RakNet::BitStream* outBitStream,
bool bIsInitialUpdate,
unsigned int& flags) {
// BEGIN Scripted Activity
2022-07-28 13:39:57 +00:00
outBitStream->Write1();
2022-07-28 13:39:57 +00:00
outBitStream->Write(static_cast<uint32_t>(m_RacingPlayers.size()));
for (const auto& player : m_RacingPlayers) {
outBitStream->Write(player.playerID);
2022-07-28 13:39:57 +00:00
for (int i = 0; i < 10; i++) {
outBitStream->Write(player.data[i]);
}
}
2022-07-28 13:39:57 +00:00
// END Scripted Activity
2022-07-28 13:39:57 +00:00
outBitStream->Write1(); // Dirty?
outBitStream->Write(static_cast<uint16_t>(m_RacingPlayers.size()));
2022-07-28 13:39:57 +00:00
outBitStream->Write(!m_RacingPlayers.empty());
if (!m_RacingPlayers.empty()) {
for (const auto& player : m_RacingPlayers) {
outBitStream->Write1(); // Has more date
2022-07-28 13:39:57 +00:00
outBitStream->Write(player.playerID);
outBitStream->Write(player.vehicleID);
outBitStream->Write(player.playerIndex);
outBitStream->Write(player.playerLoaded);
}
2022-07-28 13:39:57 +00:00
outBitStream->Write0(); // No more data
}
2022-07-28 13:39:57 +00:00
outBitStream->Write(!m_RacingPlayers.empty());
if (!m_RacingPlayers.empty()) {
for (const auto& player : m_RacingPlayers) {
outBitStream->Write1(); // Has more date
2022-07-28 13:39:57 +00:00
outBitStream->Write(player.playerID);
outBitStream->Write<uint32_t>(0);
}
2022-07-28 13:39:57 +00:00
outBitStream->Write0(); // No more data
}
2022-07-28 13:39:57 +00:00
outBitStream->Write1(); // Dirty?
2022-07-28 13:39:57 +00:00
outBitStream->Write(m_RemainingLaps);
2022-07-28 13:39:57 +00:00
outBitStream->Write(static_cast<uint16_t>(m_PathName.size()));
for (const auto character : m_PathName) {
outBitStream->Write(character);
}
2022-07-28 13:39:57 +00:00
outBitStream->Write1(); // ???
outBitStream->Write1(); // ???
2022-07-28 13:39:57 +00:00
outBitStream->Write(m_LeadingPlayer);
outBitStream->Write(m_RaceBestLap);
outBitStream->Write(m_RaceBestTime);
}
2022-07-28 13:39:57 +00:00
RacingPlayerInfo* RacingControlComponent::GetPlayerData(LWOOBJID playerID) {
for (auto& player : m_RacingPlayers) {
if (player.playerID == playerID) {
return &player;
}
}
2022-07-28 13:39:57 +00:00
return nullptr;
}
void RacingControlComponent::Update(float deltaTime) {
2022-07-28 13:39:57 +00:00
// This method is a mess.
2022-07-28 13:39:57 +00:00
// 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]);
2022-07-28 13:39:57 +00:00
if (playerEntity == nullptr) {
--m_LoadedPlayers;
2022-07-28 13:39:57 +00:00
m_LobbyPlayers.erase(m_LobbyPlayers.begin() + i);
2022-07-28 13:39:57 +00:00
return;
}
}
2022-07-28 13:39:57 +00:00
if (m_LoadedPlayers >= 2 || (m_LoadedPlayers == 1 && m_SoloRacing)) {
m_LoadTimer += deltaTime;
} else {
m_EmptyTimer += deltaTime;
}
2022-07-28 13:39:57 +00:00
// 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);
2022-07-28 13:39:57 +00:00
if (playerEntity == nullptr) {
continue;
}
2022-07-28 13:39:57 +00:00
auto* playerInstance = dynamic_cast<Player*>(playerEntity);
2022-07-28 13:39:57 +00:00
playerInstance->SendToZone(m_MainWorld);
}
2022-07-28 13:39:57 +00:00
m_LobbyPlayers.clear();
}
2022-07-28 13:39:57 +00:00
// 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...");
2022-12-22 13:16:18 +00:00
for (size_t positionNumber = 0; positionNumber < m_LobbyPlayers.size(); positionNumber++) {
2022-07-28 13:39:57 +00:00
Game::logger->Log("RacingControlComponent",
"Loading player now!");
2022-07-28 13:39:57 +00:00
auto* player =
2022-12-22 13:16:18 +00:00
EntityManager::Instance()->GetEntity(m_LobbyPlayers[positionNumber]);
2022-07-28 13:39:57 +00:00
if (player == nullptr) {
return;
}
Game::logger->Log("RacingControlComponent",
"Loading player now NOW!");
2022-12-22 13:16:18 +00:00
LoadPlayerVehicle(player, positionNumber + 1, true);
2022-07-28 13:39:57 +00:00
m_Loaded = true;
}
2022-07-28 13:39:57 +00:00
m_Loaded = true;
}
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
// 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);
2022-07-28 13:39:57 +00:00
--m_LoadedPlayers;
2022-07-28 13:39:57 +00:00
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<Player*>(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;
}
}
2022-07-28 13:39:57 +00:00
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);
for (const auto& player : m_RacingPlayers) {
auto* vehicle =
EntityManager::Instance()->GetEntity(player.vehicleID);
auto* playerEntity =
EntityManager::Instance()->GetEntity(player.playerID);
2022-07-28 13:39:57 +00:00
if (vehicle != nullptr && playerEntity != nullptr) {
GameMessages::SendTeleport(
player.playerID, player.respawnPosition,
player.respawnRotation,
playerEntity->GetSystemAddress(), true);
2022-07-28 13:39:57 +00:00
vehicle->SetPosition(player.respawnPosition);
vehicle->SetRotation(player.respawnRotation);
2022-07-28 13:39:57 +00:00
auto* destroyableComponent =
vehicle->GetComponent<DestroyableComponent>();
2022-07-28 13:39:57 +00:00
if (destroyableComponent != nullptr) {
destroyableComponent->SetImagination(0);
}
EntityManager::Instance()->SerializeEntity(vehicle);
EntityManager::Instance()->SerializeEntity(
playerEntity);
}
}
2022-07-28 13:39:57 +00:00
// 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;
}
2022-07-28 13:39:57 +00:00
player.noSmashOnReload = true;
2022-07-28 13:39:57 +00:00
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_Parent->GetObjectID(),
UNASSIGNED_SYSTEM_ADDRESS);
m_Started = true;
Game::logger->Log("RacingControlComponent", "Starting race");
EntityManager::Instance()->SerializeEntity(m_Parent);
m_StartTime = std::time(nullptr);
}
m_StartTimer += deltaTime;
} else {
m_StartTimer = 0;
}
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
// Race routines
auto* path = dZoneManager::Instance()->GetZone()->GetPath(
GeneralUtils::UTF16ToWTF8(m_PathName));
2022-07-28 13:39:57 +00:00
for (auto& player : m_RacingPlayers) {
auto* vehicle = EntityManager::Instance()->GetEntity(player.vehicleID);
auto* playerEntity =
EntityManager::Instance()->GetEntity(player.playerID);
2022-07-28 13:39:57 +00:00
if (vehicle == nullptr || playerEntity == nullptr) {
continue;
}
2022-07-28 13:39:57 +00:00
const auto vehiclePosition = vehicle->GetPosition();
2022-07-28 13:39:57 +00:00
// If the player is this far below the map, safe to assume they should
// be smashed by death plane
if (vehiclePosition.y < -500) {
GameMessages::SendDie(vehicle, m_Parent->GetObjectID(),
LWOOBJID_EMPTY, true, eKillType::VIOLENT, u"", 0, 0, 0,
2022-07-28 13:39:57 +00:00
true, false, 0);
2022-07-28 13:39:57 +00:00
OnRequestDie(playerEntity);
2022-07-28 13:39:57 +00:00
continue;
}
2022-07-28 13:39:57 +00:00
// 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;
}
2022-07-28 13:39:57 +00:00
if (player.respawnIndex == respawnIndex) {
++respawnIndex;
2022-07-28 13:39:57 +00:00
continue;
}
2022-07-28 13:39:57 +00:00
const auto& position = waypoint.position;
2022-07-28 13:39:57 +00:00
if (std::abs((int)respawnIndex - (int)player.respawnIndex) > 10 &&
player.respawnIndex != path->pathWaypoints.size() - 1) {
++respawnIndex;
2022-07-28 13:39:57 +00:00
continue;
}
2022-07-28 13:39:57 +00:00
if (Vector3::DistanceSquared(position, vehiclePosition) > 50 * 50) {
++respawnIndex;
2022-07-28 13:39:57 +00:00
continue;
}
2022-07-28 13:39:57 +00:00
// 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;
2022-07-28 13:39:57 +00:00
continue;
}
2022-07-28 13:39:57 +00:00
// 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;
2022-07-28 13:39:57 +00:00
// Reached the start point, lapped
if (respawnIndex == 0) {
2022-07-28 14:27:06 +00:00
time_t lapTime = std::time(nullptr) - (player.lap == 0 ? m_StartTime : player.lapTime);
2022-07-28 13:39:57 +00:00
// Cheating check
if (lapTime < 40) {
continue;
}
2022-07-28 13:39:57 +00:00
player.lap++;
2022-07-28 13:39:57 +00:00
player.lapTime = std::time(nullptr);
2022-07-28 13:39:57 +00:00
if (player.bestLapTime == 0 || player.bestLapTime > lapTime) {
player.bestLapTime = lapTime;
2022-07-28 13:39:57 +00:00
Game::logger->Log("RacingControlComponent",
"Best lap time (%llu)", lapTime);
}
2022-07-28 13:39:57 +00:00
auto* missionComponent =
playerEntity->GetComponent<MissionComponent>();
2022-07-28 13:39:57 +00:00
if (missionComponent != nullptr) {
Implementing and Fixing All Racing Achievements (#366) * Grammatical changes in comments * Grammatical fixes in comments Small grammatical fixes found in comments throughout the code. * Added descriptions to functions Added descriptions to functions that didn't have them to keep the code well documented * Created RacingTaskParam.h Created RacingTaskParam so eliminate magic numbers in the original implementation of completing racing missions. * Updated magic numbers in Mission.cpp Updated magic numbers in Mission.cpp to a meaningful name. * Implemented racing smashable task progression Previously, races did not progress tasks for smashing Entities. Now all achievements tracking smashables track them correctly. This has been implemented in the three Entities that can be smashed in a race (imagination boxes, track specific smashables, Forbidden Valley dragon eggs). * Updated race imagination task progression Race imagination now no longer uses a magic number when passed to missionComponent. Instead we use a number defined in an enum located in RacingTaskParam.h * Updated Race task checks Racing tasks for completing races without smashing now no longer auto complete the whole chain of missions. Tasks that track placing on tracks and races overall now properly complete. Tasks that count how many missions in a zone are completed now function. Tasks that track race completions in multiple areas now function. * Updated RacingControlComponent.cpp Fixed any tasks that required 3 players to now require 3 or more players in a race to progress. This restriction is ignored if the world config opted in for solo racing to allow progression in solo worlds. Updated magic numbers sent into missionComponent->Progress to an enum created in this PR. Fixed some indentation. * Fixed a grammatical error in variable name Fixed a grammatical error in the enum for task params
2022-02-05 11:28:17 +00:00
2022-07-28 13:39:57 +00:00
// Progress lap time tasks
missionComponent->Progress(eMissionTaskType::RACING, (lapTime) * 1000, (LWOOBJID)eRacingTaskParam::LAP_TIME);
2022-07-28 13:39:57 +00:00
if (player.lap == 3) {
m_Finished++;
player.finished = m_Finished;
2022-07-28 13:39:57 +00:00
const auto raceTime =
(std::time(nullptr) - m_StartTime);
2022-07-28 13:39:57 +00:00
player.raceTime = raceTime;
2022-07-28 13:39:57 +00:00
Game::logger->Log("RacingControlComponent",
"Completed time %llu, %llu",
raceTime, raceTime * 1000);
LeaderboardManager::SaveScore(playerEntity->GetObjectID(), m_ActivityID, static_cast<float>(player.bestLapTime), static_cast<float>(player.raceTime), static_cast<float>(player.finished == 1));
2022-07-28 13:39:57 +00:00
// Entire race time
missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, (LWOOBJID)eRacingTaskParam::TOTAL_TRACK_TIME);
2022-07-28 13:39:57 +00:00
auto* characterComponent = playerEntity->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->TrackRaceCompleted(m_Finished == 1);
}
2022-07-28 13:39:57 +00:00
// TODO: Figure out how to update the GUI leaderboard.
}
}
2022-07-28 13:39:57 +00:00
Game::logger->Log("RacingControlComponent",
"Lapped (%i) in (%llu)", player.lap,
lapTime);
}
2022-07-28 13:39:57 +00:00
Game::logger->Log("RacingControlComponent",
"Reached point (%i)/(%i)", player.respawnIndex,
path->pathWaypoints.size());
2022-07-28 13:39:57 +00:00
break;
}
}
}
std::string RacingControlComponent::FormatTimeString(time_t time) {
2022-07-28 13:39:57 +00:00
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";
}