mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-26 15:37:20 +00:00
Merge branch 'components-wheeeee' of https://github.com/DarkflameUniverse/DarkflameServer into components-wheeeee
This commit is contained in:
commit
d9a3bea6d5
@ -114,7 +114,7 @@ enum class eReplicaComponentType : uint32_t {
|
|||||||
CRAFTING,
|
CRAFTING,
|
||||||
POSSESSABLE,
|
POSSESSABLE,
|
||||||
LEVEL_PROGRESSION,
|
LEVEL_PROGRESSION,
|
||||||
POSSESSOR,
|
POSSESSION,
|
||||||
MOUNT_CONTROL,
|
MOUNT_CONTROL,
|
||||||
UNKNOWN_112,
|
UNKNOWN_112,
|
||||||
PROPERTY_PLAQUE,
|
PROPERTY_PLAQUE,
|
||||||
|
@ -1,5 +1,621 @@
|
|||||||
#include "ActivityComponent.h"
|
#include "ActivityComponent.h"
|
||||||
|
#include "GameMessages.h"
|
||||||
|
#include "CDClientManager.h"
|
||||||
|
#include "MissionComponent.h"
|
||||||
|
#include "Character.h"
|
||||||
|
#include "dZoneManager.h"
|
||||||
|
#include "ZoneInstanceManager.h"
|
||||||
|
#include "Game.h"
|
||||||
|
#include "dLogger.h"
|
||||||
|
#include <WorldPackets.h>
|
||||||
|
#include "EntityManager.h"
|
||||||
|
#include "ChatPackets.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "PacketUtils.h"
|
||||||
|
#include "dServer.h"
|
||||||
|
#include "GeneralUtils.h"
|
||||||
|
#include "dZoneManager.h"
|
||||||
|
#include "dConfig.h"
|
||||||
|
#include "InventoryComponent.h"
|
||||||
|
#include "DestroyableComponent.h"
|
||||||
|
#include "Loot.h"
|
||||||
|
#include "eMissionTaskType.h"
|
||||||
|
#include "eMatchUpdate.h"
|
||||||
|
#include "eConnectionType.h"
|
||||||
|
#include "eChatInternalMessageType.h"
|
||||||
|
|
||||||
|
#include "CDCurrencyTableTable.h"
|
||||||
|
#include "CDActivityRewardsTable.h"
|
||||||
|
#include "CDActivitiesTable.h"
|
||||||
|
#include "LeaderboardManager.h"
|
||||||
|
|
||||||
ActivityComponent::ActivityComponent(Entity* parent) : Component(parent) {
|
ActivityComponent::ActivityComponent(Entity* parent) : Component(parent) {
|
||||||
|
m_ActivityID = activityID;
|
||||||
|
CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable<CDActivitiesTable>();
|
||||||
|
std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); });
|
||||||
|
|
||||||
|
for (CDActivities activity : activities) {
|
||||||
|
m_ActivityInfo = activity;
|
||||||
|
if (static_cast<LeaderboardType>(activity.leaderboardType) == LeaderboardType::Racing && Game::config->GetValue("solo_racing") == "1") {
|
||||||
|
m_ActivityInfo.minTeamSize = 1;
|
||||||
|
m_ActivityInfo.minTeams = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& transferOverride = parent->GetVar<std::u16string>(u"transferZoneID");
|
||||||
|
if (!transferOverride.empty()) {
|
||||||
|
m_ActivityInfo.instanceMapID = std::stoi(GeneralUtils::UTF16ToWTF8(transferOverride));
|
||||||
|
|
||||||
|
// TODO: LU devs made me do it (for some reason cannon cove instancer is marked to go to GF survival)
|
||||||
|
// NOTE: 1301 is GF survival
|
||||||
|
if (m_ActivityInfo.instanceMapID == 1301) {
|
||||||
|
m_ActivityInfo.instanceMapID = 1302;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* destroyableComponent = m_ParentEntity->GetComponent<DestroyableComponent>();
|
||||||
|
|
||||||
|
if (destroyableComponent) {
|
||||||
|
// check for LMIs and set the loot LMIs
|
||||||
|
CDActivityRewardsTable* activityRewardsTable = CDClientManager::Instance().GetTable<CDActivityRewardsTable>();
|
||||||
|
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) {return (entry.LootMatrixIndex == destroyableComponent->GetLootMatrixID()); });
|
||||||
|
|
||||||
|
uint32_t startingLMI = 0;
|
||||||
|
|
||||||
|
if (activityRewards.size() > 0) {
|
||||||
|
startingLMI = activityRewards[0].LootMatrixIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startingLMI > 0) {
|
||||||
|
// now time for bodge :)
|
||||||
|
|
||||||
|
std::vector<CDActivityRewards> objectTemplateActivities = activityRewardsTable->Query([=](CDActivityRewards entry) {return (activityRewards[0].objectTemplate == entry.objectTemplate); });
|
||||||
|
for (const auto& item : objectTemplateActivities) {
|
||||||
|
if (item.activityRating > 0 && item.activityRating < 5) {
|
||||||
|
m_ActivityLootMatrices.insert({ item.activityRating, item.LootMatrixIndex });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityComponent::~ActivityComponent()
|
||||||
|
= default;
|
||||||
|
|
||||||
|
void ActivityComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) const {
|
||||||
|
outBitStream->Write(true);
|
||||||
|
outBitStream->Write<uint32_t>(m_ActivityPlayers.size());
|
||||||
|
|
||||||
|
if (!m_ActivityPlayers.empty()) {
|
||||||
|
for (const auto& activityPlayer : m_ActivityPlayers) {
|
||||||
|
|
||||||
|
outBitStream->Write<LWOOBJID>(activityPlayer->playerID);
|
||||||
|
for (const auto& activityValue : activityPlayer->values) {
|
||||||
|
outBitStream->Write<float_t>(activityValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::ReloadConfig() {
|
||||||
|
CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable<CDActivitiesTable>();
|
||||||
|
std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); });
|
||||||
|
for (auto activity : activities) {
|
||||||
|
auto mapID = m_ActivityInfo.instanceMapID;
|
||||||
|
if ((mapID == 1203 || mapID == 1261 || mapID == 1303 || mapID == 1403) && Game::config->GetValue("solo_racing") == "1") {
|
||||||
|
m_ActivityInfo.minTeamSize = 1;
|
||||||
|
m_ActivityInfo.minTeams = 1;
|
||||||
|
} else {
|
||||||
|
m_ActivityInfo.minTeamSize = activity.minTeamSize;
|
||||||
|
m_ActivityInfo.minTeams = activity.minTeams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::HandleMessageBoxResponse(Entity* player, const std::string& id) {
|
||||||
|
if (m_ActivityInfo.ActivityID == 103) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == "LobbyExit") {
|
||||||
|
PlayerLeave(player->GetObjectID());
|
||||||
|
} else if (id == "PlayButton") {
|
||||||
|
PlayerJoin(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::PlayerJoin(Entity* player) {
|
||||||
|
if (m_ActivityInfo.ActivityID == 103 || PlayerIsInQueue(player) || !IsValidActivity(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a lobby, queue the player and allow others to join, otherwise spin up an instance on the spot
|
||||||
|
if (HasLobby()) {
|
||||||
|
PlayerJoinLobby(player);
|
||||||
|
} else if (!IsPlayedBy(player)) {
|
||||||
|
auto* instance = NewInstance();
|
||||||
|
instance->AddParticipant(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::PlayerJoinLobby(Entity* player) {
|
||||||
|
if (!m_ParentEntity->HasComponent(eReplicaComponentType::QUICK_BUILD))
|
||||||
|
GameMessages::SendMatchResponse(player, player->GetSystemAddress(), 0); // tell the client they joined a lobby
|
||||||
|
LobbyPlayer* newLobbyPlayer = new LobbyPlayer();
|
||||||
|
newLobbyPlayer->entityID = player->GetObjectID();
|
||||||
|
Lobby* playerLobby = nullptr;
|
||||||
|
|
||||||
|
auto* character = player->GetCharacter();
|
||||||
|
if (character != nullptr)
|
||||||
|
character->SetLastNonInstanceZoneID(dZoneManager::Instance()->GetZone()->GetWorldID());
|
||||||
|
|
||||||
|
for (Lobby* lobby : m_Queue) {
|
||||||
|
if (lobby->players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() < m_ActivityInfo.maxTeams) {
|
||||||
|
// If an empty slot in an existing lobby is found
|
||||||
|
lobby->players.push_back(newLobbyPlayer);
|
||||||
|
playerLobby = lobby;
|
||||||
|
|
||||||
|
// Update the joining player on players already in the lobby, and update players already in the lobby on the joining player
|
||||||
|
std::string matchUpdateJoined = "player=9:" + std::to_string(player->GetObjectID()) + "\nplayerName=0:" + player->GetCharacter()->GetName();
|
||||||
|
for (LobbyPlayer* joinedPlayer : lobby->players) {
|
||||||
|
auto* entity = joinedPlayer->GetEntity();
|
||||||
|
|
||||||
|
if (entity == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string matchUpdate = "player=9:" + std::to_string(entity->GetObjectID()) + "\nplayerName=0:" + entity->GetCharacter()->GetName();
|
||||||
|
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchUpdate, eMatchUpdate::PLAYER_ADDED);
|
||||||
|
PlayerReady(entity, joinedPlayer->ready);
|
||||||
|
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateJoined, eMatchUpdate::PLAYER_ADDED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerLobby) {
|
||||||
|
// If all lobbies are full
|
||||||
|
playerLobby = new Lobby();
|
||||||
|
playerLobby->players.push_back(newLobbyPlayer);
|
||||||
|
playerLobby->timer = m_ActivityInfo.waitTime / 1000;
|
||||||
|
m_Queue.push_back(playerLobby);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_ActivityInfo.maxTeamSize != 1 && playerLobby->players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && playerLobby->players.size() >= m_ActivityInfo.minTeams) {
|
||||||
|
// Update the joining player on the match timer
|
||||||
|
std::string matchTimerUpdate = "time=3:" + std::to_string(playerLobby->timer);
|
||||||
|
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::PlayerLeave(LWOOBJID playerID) {
|
||||||
|
|
||||||
|
// Removes the player from a lobby and notifies the others, not applicable for non-lobby instances
|
||||||
|
for (Lobby* lobby : m_Queue) {
|
||||||
|
for (int i = 0; i < lobby->players.size(); ++i) {
|
||||||
|
if (lobby->players[i]->entityID == playerID) {
|
||||||
|
std::string matchUpdateLeft = "player=9:" + std::to_string(playerID);
|
||||||
|
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
||||||
|
auto* entity = lobbyPlayer->GetEntity();
|
||||||
|
if (entity == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft, eMatchUpdate::PLAYER_REMOVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete lobby->players[i];
|
||||||
|
lobby->players[i] = nullptr;
|
||||||
|
lobby->players.erase(lobby->players.begin() + i);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::Update(float deltaTime) {
|
||||||
|
std::vector<Lobby*> lobbiesToRemove{};
|
||||||
|
// Ticks all the lobbies, not applicable for non-instance activities
|
||||||
|
for (Lobby* lobby : m_Queue) {
|
||||||
|
for (LobbyPlayer* player : lobby->players) {
|
||||||
|
auto* entity = player->GetEntity();
|
||||||
|
if (entity == nullptr) {
|
||||||
|
PlayerLeave(player->entityID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lobby->players.empty()) {
|
||||||
|
lobbiesToRemove.push_back(lobby);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the match time for all players
|
||||||
|
if (m_ActivityInfo.maxTeamSize != 1 && lobby->players.size() >= m_ActivityInfo.minTeamSize
|
||||||
|
|| m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() >= m_ActivityInfo.minTeams) {
|
||||||
|
if (lobby->timer == m_ActivityInfo.waitTime / 1000) {
|
||||||
|
for (LobbyPlayer* joinedPlayer : lobby->players) {
|
||||||
|
auto* entity = joinedPlayer->GetEntity();
|
||||||
|
|
||||||
|
if (entity == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
|
||||||
|
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lobby->timer -= deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lobbyReady = true;
|
||||||
|
for (LobbyPlayer* player : lobby->players) {
|
||||||
|
if (player->ready) continue;
|
||||||
|
lobbyReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If everyone's ready, jump the timer
|
||||||
|
if (lobbyReady && lobby->timer > m_ActivityInfo.startDelay / 1000) {
|
||||||
|
lobby->timer = m_ActivityInfo.startDelay / 1000;
|
||||||
|
|
||||||
|
// Update players in lobby on switch to start delay
|
||||||
|
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
|
||||||
|
for (LobbyPlayer* player : lobby->players) {
|
||||||
|
auto* entity = player->GetEntity();
|
||||||
|
|
||||||
|
if (entity == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_START);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The timer has elapsed, start the instance
|
||||||
|
if (lobby->timer <= 0.0f) {
|
||||||
|
Game::logger->Log("ActivityComponent", "Setting up instance.");
|
||||||
|
ActivityInstance* instance = NewInstance();
|
||||||
|
LoadPlayersIntoInstance(instance, lobby->players);
|
||||||
|
instance->StartZone();
|
||||||
|
lobbiesToRemove.push_back(lobby);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!lobbiesToRemove.empty()) {
|
||||||
|
RemoveLobby(lobbiesToRemove.front());
|
||||||
|
lobbiesToRemove.erase(lobbiesToRemove.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::RemoveLobby(Lobby* lobby) {
|
||||||
|
for (int i = 0; i < m_Queue.size(); ++i) {
|
||||||
|
if (m_Queue[i] == lobby) {
|
||||||
|
m_Queue.erase(m_Queue.begin() + i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivityComponent::HasLobby() const {
|
||||||
|
// If the player is not in the world he has to be, create a lobby for the transfer
|
||||||
|
return m_ActivityInfo.instanceMapID != UINT_MAX && m_ActivityInfo.instanceMapID != Game::server->GetZoneID();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivityComponent::IsValidActivity(Entity* player) {
|
||||||
|
// Makes it so that scripted activities with an unimplemented map cannot be joined
|
||||||
|
/*if (player->GetGMLevel() < eGameMasterLevel::DEVELOPER && (m_ActivityInfo.instanceMapID == 1302 || m_ActivityInfo.instanceMapID == 1301)) {
|
||||||
|
if (m_ParentEntity->GetLOT() == 4860) {
|
||||||
|
auto* missionComponent = player->GetComponent<MissionComponent>();
|
||||||
|
missionComponent->CompleteMission(229);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatPackets::SendSystemMessage(player->GetSystemAddress(), u"Sorry, this activity is not ready.");
|
||||||
|
static_cast<Player*>(player)->SendToZone(dZoneManager::Instance()->GetZone()->GetWorldID()); // Gets them out of this stuck state
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivityComponent::PlayerIsInQueue(Entity* player) {
|
||||||
|
for (Lobby* lobby : m_Queue) {
|
||||||
|
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
||||||
|
if (player->GetObjectID() == lobbyPlayer->entityID) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivityComponent::IsPlayedBy(Entity* player) const {
|
||||||
|
for (const auto* instance : this->m_Instances) {
|
||||||
|
for (const auto* instancePlayer : instance->GetParticipants()) {
|
||||||
|
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == player->GetObjectID())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivityComponent::IsPlayedBy(LWOOBJID playerID) const {
|
||||||
|
for (const auto* instance : this->m_Instances) {
|
||||||
|
for (const auto* instancePlayer : instance->GetParticipants()) {
|
||||||
|
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == playerID)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivityComponent::TakeCost(Entity* player) const {
|
||||||
|
if (m_ActivityInfo.optionalCostLOT <= 0 || m_ActivityInfo.optionalCostCount <= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
|
||||||
|
if (inventoryComponent == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (inventoryComponent->GetLotCount(m_ActivityInfo.optionalCostLOT) < m_ActivityInfo.optionalCostCount)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
|
||||||
|
for (Lobby* lobby : m_Queue) {
|
||||||
|
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
||||||
|
if (lobbyPlayer->entityID == player->GetObjectID()) {
|
||||||
|
|
||||||
|
lobbyPlayer->ready = bReady;
|
||||||
|
|
||||||
|
// Update players in lobby on player being ready
|
||||||
|
std::string matchReadyUpdate = "player=9:" + std::to_string(player->GetObjectID());
|
||||||
|
eMatchUpdate readyStatus = eMatchUpdate::PLAYER_READY;
|
||||||
|
if (!bReady) readyStatus = eMatchUpdate::PLAYER_NOT_READY;
|
||||||
|
for (LobbyPlayer* otherPlayer : lobby->players) {
|
||||||
|
auto* entity = otherPlayer->GetEntity();
|
||||||
|
if (entity == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate, readyStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityInstance* ActivityComponent::NewInstance() {
|
||||||
|
auto* instance = new ActivityInstance(m_ParentEntity, m_ActivityInfo);
|
||||||
|
m_Instances.push_back(instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const {
|
||||||
|
for (LobbyPlayer* player : lobby) {
|
||||||
|
auto* entity = player->GetEntity();
|
||||||
|
if (entity == nullptr || !TakeCost(entity)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->AddParticipant(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<ActivityInstance*>& ActivityComponent::GetInstances() const {
|
||||||
|
return m_Instances;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityInstance* ActivityComponent::GetInstance(const LWOOBJID playerID) {
|
||||||
|
for (const auto* instance : GetInstances()) {
|
||||||
|
for (const auto* participant : instance->GetParticipants()) {
|
||||||
|
if (participant->GetObjectID() == playerID)
|
||||||
|
return const_cast<ActivityInstance*>(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::ClearInstances() {
|
||||||
|
for (ActivityInstance* instance : m_Instances) {
|
||||||
|
delete instance;
|
||||||
|
}
|
||||||
|
m_Instances.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityPlayer* ActivityComponent::GetActivityPlayerData(LWOOBJID playerID) {
|
||||||
|
for (auto* activityData : m_ActivityPlayers) {
|
||||||
|
if (activityData->playerID == playerID) {
|
||||||
|
return activityData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::RemoveActivityPlayerData(LWOOBJID playerID) {
|
||||||
|
for (size_t i = 0; i < m_ActivityPlayers.size(); i++) {
|
||||||
|
if (m_ActivityPlayers[i]->playerID == playerID) {
|
||||||
|
delete m_ActivityPlayers[i];
|
||||||
|
m_ActivityPlayers[i] = nullptr;
|
||||||
|
|
||||||
|
m_ActivityPlayers.erase(m_ActivityPlayers.begin() + i);
|
||||||
|
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityPlayer* ActivityComponent::AddActivityPlayerData(LWOOBJID playerID) {
|
||||||
|
auto* data = GetActivityPlayerData(playerID);
|
||||||
|
if (data != nullptr)
|
||||||
|
return data;
|
||||||
|
|
||||||
|
m_ActivityPlayers.push_back(new ActivityPlayer{ playerID, {} });
|
||||||
|
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
|
||||||
|
|
||||||
|
return GetActivityPlayerData(playerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
float_t ActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) {
|
||||||
|
auto value = -1.0f;
|
||||||
|
|
||||||
|
auto* data = GetActivityPlayerData(playerID);
|
||||||
|
if (data != nullptr) {
|
||||||
|
value = data->values[std::min(index, (uint32_t)9)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value) {
|
||||||
|
auto* data = AddActivityPlayerData(playerID);
|
||||||
|
if (data != nullptr) {
|
||||||
|
data->values[std::min(index, (uint32_t)9)] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::PlayerRemove(LWOOBJID playerID) {
|
||||||
|
for (auto* instance : GetInstances()) {
|
||||||
|
auto participants = instance->GetParticipants();
|
||||||
|
for (const auto* participant : participants) {
|
||||||
|
if (participant != nullptr && participant->GetObjectID() == playerID) {
|
||||||
|
instance->RemoveParticipant(participant);
|
||||||
|
RemoveActivityPlayerData(playerID);
|
||||||
|
|
||||||
|
// If the instance is empty after the delete of the participant, delete the instance too
|
||||||
|
if (instance->GetParticipants().empty()) {
|
||||||
|
m_Instances.erase(std::find(m_Instances.begin(), m_Instances.end(), instance));
|
||||||
|
delete instance;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityInstance::StartZone() {
|
||||||
|
if (m_Participants.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto& participants = GetParticipants();
|
||||||
|
if (participants.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto* leader = participants[0];
|
||||||
|
LWOZONEID zoneId = LWOZONEID(m_ActivityInfo.instanceMapID, 0, leader->GetCharacter()->GetPropertyCloneID());
|
||||||
|
|
||||||
|
// only make a team if we have more than one participant
|
||||||
|
if (participants.size() > 1) {
|
||||||
|
CBITSTREAM;
|
||||||
|
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::CREATE_TEAM);
|
||||||
|
|
||||||
|
bitStream.Write(leader->GetObjectID());
|
||||||
|
bitStream.Write(m_Participants.size());
|
||||||
|
|
||||||
|
for (const auto& participant : m_Participants) {
|
||||||
|
bitStream.Write(participant);
|
||||||
|
}
|
||||||
|
|
||||||
|
bitStream.Write(zoneId);
|
||||||
|
|
||||||
|
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto cloneId = GeneralUtils::GenerateRandomNumber<uint32_t>(1, UINT32_MAX);
|
||||||
|
for (Entity* player : participants) {
|
||||||
|
const auto objid = player->GetObjectID();
|
||||||
|
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, m_ActivityInfo.instanceMapID, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
||||||
|
|
||||||
|
auto* player = EntityManager::Instance()->GetEntity(objid);
|
||||||
|
if (player == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", player->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
||||||
|
if (player->GetCharacter()) {
|
||||||
|
player->GetCharacter()->SetZoneID(zoneID);
|
||||||
|
player->GetCharacter()->SetZoneInstance(zoneInstance);
|
||||||
|
player->GetCharacter()->SetZoneClone(zoneClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldPackets::SendTransferToWorld(player->GetSystemAddress(), serverIP, serverPort, mythranShift);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_NextZoneCloneID++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityInstance::RewardParticipant(Entity* participant) {
|
||||||
|
auto* missionComponent = participant->GetComponent<MissionComponent>();
|
||||||
|
if (missionComponent) {
|
||||||
|
missionComponent->Progress(eMissionTaskType::ACTIVITY, m_ActivityInfo.ActivityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, get the activity data
|
||||||
|
auto* activityRewardsTable = CDClientManager::Instance().GetTable<CDActivityRewardsTable>();
|
||||||
|
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) { return (entry.objectTemplate == m_ActivityInfo.ActivityID); });
|
||||||
|
|
||||||
|
if (!activityRewards.empty()) {
|
||||||
|
uint32_t minCoins = 0;
|
||||||
|
uint32_t maxCoins = 0;
|
||||||
|
|
||||||
|
auto* currencyTableTable = CDClientManager::Instance().GetTable<CDCurrencyTableTable>();
|
||||||
|
std::vector<CDCurrencyTable> currencyTable = currencyTableTable->Query([=](CDCurrencyTable entry) { return (entry.currencyIndex == activityRewards[0].CurrencyIndex && entry.npcminlevel == 1); });
|
||||||
|
|
||||||
|
if (!currencyTable.empty()) {
|
||||||
|
minCoins = currencyTable[0].minvalue;
|
||||||
|
maxCoins = currencyTable[0].maxvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LootGenerator::Instance().DropLoot(participant, m_ParentEntity, activityRewards[0].LootMatrixIndex, minCoins, maxCoins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Entity*> ActivityInstance::GetParticipants() const {
|
||||||
|
std::vector<Entity*> entities;
|
||||||
|
entities.reserve(m_Participants.size());
|
||||||
|
|
||||||
|
for (const auto& id : m_Participants) {
|
||||||
|
auto* entity = EntityManager::Instance()->GetEntity(id);
|
||||||
|
if (entity != nullptr)
|
||||||
|
entities.push_back(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityInstance::AddParticipant(Entity* participant) {
|
||||||
|
const auto id = participant->GetObjectID();
|
||||||
|
if (std::count(m_Participants.begin(), m_Participants.end(), id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_Participants.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityInstance::RemoveParticipant(const Entity* participant) {
|
||||||
|
const auto loadedParticipant = std::find(m_Participants.begin(), m_Participants.end(), participant->GetObjectID());
|
||||||
|
if (loadedParticipant != m_Participants.end()) {
|
||||||
|
m_Participants.erase(loadedParticipant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ActivityInstance::GetScore() const {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityInstance::SetScore(uint32_t score) {
|
||||||
|
this->score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity* LobbyPlayer::GetEntity() const {
|
||||||
|
return EntityManager::Instance()->GetEntity(entityID);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,379 @@
|
|||||||
#ifndef __ACTIVITYCOMPONENT__H__
|
/*
|
||||||
#define __ACTIVITYCOMPONENT__H__
|
* Darkflame Universe
|
||||||
|
* Copyright 2018
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CDClientManager.h"
|
||||||
|
|
||||||
|
#ifndef ACTIVITYCOMPONENT_H
|
||||||
|
#define ACTIVITYCOMPONENT_H
|
||||||
|
|
||||||
|
#include "BitStream.h"
|
||||||
|
#include "Entity.h"
|
||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
|
|
||||||
class Entity;
|
#include "CDActivitiesTable.h"
|
||||||
|
|
||||||
class ActivityComponent : public Component {
|
/**
|
||||||
|
* Represents an instance of an activity, having participants and score
|
||||||
|
*/
|
||||||
|
class ActivityInstance {
|
||||||
public:
|
public:
|
||||||
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::INVALID;
|
ActivityInstance(Entity* parent, CDActivities activityInfo) { m_ParentEntity = parent; m_ActivityInfo = activityInfo; };
|
||||||
ActivityComponent(Entity* parent);
|
//~ActivityInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an entity to this activity
|
||||||
|
* @param participant the entity to add
|
||||||
|
*/
|
||||||
|
void AddParticipant(Entity* participant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all the participants from this activity
|
||||||
|
*/
|
||||||
|
void ClearParticipants() { m_Participants.clear(); };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the instance world for this activity and sends all participants there
|
||||||
|
*/
|
||||||
|
void StartZone();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives the rewards for completing this activity to some participant
|
||||||
|
* @param participant the participant to give rewards to
|
||||||
|
*/
|
||||||
|
void RewardParticipant(Entity* participant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a participant from this activity
|
||||||
|
* @param participant the participant to remove
|
||||||
|
*/
|
||||||
|
void RemoveParticipant(const Entity* participant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the participants of this activity
|
||||||
|
* @return all the participants of this activity
|
||||||
|
*/
|
||||||
|
std::vector<Entity*> GetParticipants() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently unused
|
||||||
|
*/
|
||||||
|
uint32_t GetScore() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently unused
|
||||||
|
*/
|
||||||
|
void SetScore(uint32_t score);
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently unused
|
||||||
|
*/
|
||||||
|
uint32_t score = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The instance ID of this activity
|
||||||
|
*/
|
||||||
|
uint32_t m_NextZoneCloneID = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The database information for this activity
|
||||||
|
*/
|
||||||
|
CDActivities m_ActivityInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity that owns this activity (the entity that has the ActivityComponent)
|
||||||
|
*/
|
||||||
|
Entity* m_ParentEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All the participants of this activity
|
||||||
|
*/
|
||||||
|
std::vector<LWOOBJID> m_Participants;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //!__ACTIVITYCOMPONENT__H__
|
/**
|
||||||
|
* Represents an entity in a lobby
|
||||||
|
*/
|
||||||
|
struct LobbyPlayer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the entity that is in the lobby
|
||||||
|
*/
|
||||||
|
LWOOBJID entityID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the entity is ready
|
||||||
|
*/
|
||||||
|
bool ready = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the entity that is in the lobby
|
||||||
|
* @return the entity that is in the lobby
|
||||||
|
*/
|
||||||
|
Entity* GetEntity() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a lobby of players with a timer until it should start the activity
|
||||||
|
*/
|
||||||
|
struct Lobby {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lobby of players
|
||||||
|
*/
|
||||||
|
std::vector<LobbyPlayer*> players;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timer that determines when the activity should start
|
||||||
|
*/
|
||||||
|
float timer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the score for the player in an activity, one index might represent score, another one time, etc.
|
||||||
|
*/
|
||||||
|
struct ActivityPlayer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity that the score is tracked for
|
||||||
|
*/
|
||||||
|
LWOOBJID playerID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of score for this entity
|
||||||
|
*/
|
||||||
|
float values[10];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Welcome to the absolute behemoth that is the activity component. I have now clue how this was managed in
|
||||||
|
* live but I figure somewhat similarly and it's terrible. In a nutshell, this components handles any activity that
|
||||||
|
* can be done in the game from quick builds to boss fights to races. On top of that, this component handles instancing
|
||||||
|
* and lobbying.
|
||||||
|
*/
|
||||||
|
class ActivityComponent : public Component {
|
||||||
|
public:
|
||||||
|
ActivityComponent(Entity* parent);
|
||||||
|
~ActivityComponent() override;
|
||||||
|
|
||||||
|
void Update(float deltaTime) override;
|
||||||
|
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes some entity join the minigame, if it's a lobbied one, the entity will be placed in the lobby
|
||||||
|
* @param player the entity to join the game
|
||||||
|
*/
|
||||||
|
void PlayerJoin(Entity* player);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes an entity join the lobby for this minigame, if it exists
|
||||||
|
* @param player the entity to join
|
||||||
|
*/
|
||||||
|
void PlayerJoinLobby(Entity* player);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the player leave the lobby
|
||||||
|
* @param playerID the entity to leave the lobby
|
||||||
|
*/
|
||||||
|
void PlayerLeave(LWOOBJID playerID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the entity from the minigame (and its score)
|
||||||
|
* @param playerID the entity to remove from the minigame
|
||||||
|
*/
|
||||||
|
void PlayerRemove(LWOOBJID playerID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds all the players to an instance of some activity
|
||||||
|
* @param instance the instance to load the players into
|
||||||
|
* @param lobby the players to load into the instance
|
||||||
|
*/
|
||||||
|
void LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a lobby from the activity manager
|
||||||
|
* @param lobby the lobby to remove
|
||||||
|
*/
|
||||||
|
void RemoveLobby(Lobby* lobby);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a player as (un)ready in a lobby
|
||||||
|
* @param player the entity to mark
|
||||||
|
* @param bReady true if the entity is ready, false otherwise
|
||||||
|
*/
|
||||||
|
void PlayerReady(Entity* player, bool bReady);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of this activity
|
||||||
|
* @return the ID of this activity
|
||||||
|
*/
|
||||||
|
int GetActivityID() { return m_ActivityInfo.ActivityID; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if this activity has a lobby, e.g. if it needs to instance players to some other map
|
||||||
|
* @return true if this activity has a lobby, false otherwise
|
||||||
|
*/
|
||||||
|
bool HasLobby() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a player is currently waiting in a lobby
|
||||||
|
* @param player the entity to check for
|
||||||
|
* @return true if the entity is waiting in a lobby, false otherwise
|
||||||
|
*/
|
||||||
|
bool PlayerIsInQueue(Entity* player);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an entity is currently playing this activity
|
||||||
|
* @param player the entity to check
|
||||||
|
* @return true if the entity is playing this lobby, false otherwise
|
||||||
|
*/
|
||||||
|
bool IsPlayedBy(Entity* player) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an entity is currently playing this activity
|
||||||
|
* @param playerID the entity to check
|
||||||
|
* @return true if the entity is playing this lobby, false otherwise
|
||||||
|
*/
|
||||||
|
bool IsPlayedBy(LWOOBJID playerID) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy: used to check for unimplemented maps, gladly, this now just returns true :)
|
||||||
|
*/
|
||||||
|
bool IsValidActivity(Entity* player);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the cost of the activity (e.g. green imaginate) for the entity that plays this activity
|
||||||
|
* @param player the entity to take cost for
|
||||||
|
* @return true if the cost was successfully deducted, false otherwise
|
||||||
|
*/
|
||||||
|
bool TakeCost(Entity* player) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles any response from a player clicking on a lobby / instance menu
|
||||||
|
* @param player the entity that clicked
|
||||||
|
* @param id the message that was passed
|
||||||
|
*/
|
||||||
|
void HandleMessageBoxResponse(Entity* player, const std::string& id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance for this activity
|
||||||
|
* @return a new instance for this activity
|
||||||
|
*/
|
||||||
|
ActivityInstance* NewInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the currently active instances of this activity
|
||||||
|
* @return all the currently active instances of this activity
|
||||||
|
*/
|
||||||
|
const std::vector<ActivityInstance*>& GetInstances() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the instance that some entity is currently playing in
|
||||||
|
* @param playerID the entity to check for
|
||||||
|
* @return if any, the instance that the entity is currently in
|
||||||
|
*/
|
||||||
|
ActivityInstance* GetInstance(const LWOOBJID playerID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reloads the config settings for this component
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void ReloadConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all the instances
|
||||||
|
*/
|
||||||
|
void ClearInstances();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the score for the players that are currently playing this activity
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
std::vector<ActivityPlayer*> GetActivityPlayers() { return m_ActivityPlayers; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns activity data for a specific entity (e.g. score and such).
|
||||||
|
* @param playerID the entity to get data for
|
||||||
|
* @return the activity data (score) for the passed player in this activity, if it exists
|
||||||
|
*/
|
||||||
|
ActivityPlayer* GetActivityPlayerData(LWOOBJID playerID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets some score value for an entity
|
||||||
|
* @param playerID the entity to set score for
|
||||||
|
* @param index the score index to set
|
||||||
|
* @param value the value to set in for that index
|
||||||
|
*/
|
||||||
|
void SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns activity score for the passed parameters
|
||||||
|
* @param playerID the entity to get score for
|
||||||
|
* @param index the index to get score for
|
||||||
|
* @return activity score for the passed parameters
|
||||||
|
*/
|
||||||
|
float_t GetActivityValue(LWOOBJID playerID, uint32_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes activity score tracking for some entity
|
||||||
|
* @param playerID the entity to remove score for
|
||||||
|
*/
|
||||||
|
void RemoveActivityPlayerData(LWOOBJID playerID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds activity score tracking for some entity
|
||||||
|
* @param playerID the entity to add the activity score for
|
||||||
|
* @return the created entry
|
||||||
|
*/
|
||||||
|
ActivityPlayer* AddActivityPlayerData(LWOOBJID playerID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the mapID that this activity points to
|
||||||
|
* @param mapID the map ID to set
|
||||||
|
*/
|
||||||
|
void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LMI that this activity points to for a team size
|
||||||
|
* @param teamSize the team size to get the LMI for
|
||||||
|
* @return the LMI that this activity points to for a team size
|
||||||
|
*/
|
||||||
|
uint32_t GetLootMatrixForTeamSize(uint32_t teamSize) { return m_ActivityLootMatrices[teamSize]; }
|
||||||
|
private:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The database information for this activity
|
||||||
|
*/
|
||||||
|
CDActivities m_ActivityInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All the active instances of this activity
|
||||||
|
*/
|
||||||
|
std::vector<ActivityInstance*> m_Instances;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current lobbies for this activity
|
||||||
|
*/
|
||||||
|
std::vector<Lobby*> m_Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All the activity score for the players in this activity
|
||||||
|
*/
|
||||||
|
std::vector<ActivityPlayer*> m_ActivityPlayers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LMIs for team sizes
|
||||||
|
*/
|
||||||
|
std::unordered_map<uint32_t, uint32_t> m_ActivityLootMatrices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The activity id
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int32_t m_ActivityID;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ACTIVITYCOMPONENT_H
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp"
|
set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp"
|
||||||
"ActivityComponent.cpp"
|
"ActivityComponent.cpp"
|
||||||
"BaseCombatAIComponent.cpp"
|
"BaseCombatAIComponent.cpp"
|
||||||
"RacingControlComponent.cpp"
|
|
||||||
"BouncerComponent.cpp"
|
"BouncerComponent.cpp"
|
||||||
"BuffComponent.cpp"
|
"BuffComponent.cpp"
|
||||||
"BuildBorderComponent.cpp"
|
"BuildBorderComponent.cpp"
|
||||||
@ -11,7 +10,7 @@ set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp"
|
|||||||
"ControllablePhysicsComponent.cpp"
|
"ControllablePhysicsComponent.cpp"
|
||||||
"DestroyableComponent.cpp"
|
"DestroyableComponent.cpp"
|
||||||
"DonationVendorComponent.cpp"
|
"DonationVendorComponent.cpp"
|
||||||
"GateRushControlComponent.cpp"
|
"GateRushComponent.cpp"
|
||||||
"InventoryComponent.cpp"
|
"InventoryComponent.cpp"
|
||||||
"ItemComponent.cpp"
|
"ItemComponent.cpp"
|
||||||
"LevelProgressionComponent.cpp"
|
"LevelProgressionComponent.cpp"
|
||||||
@ -28,13 +27,14 @@ set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp"
|
|||||||
"PhantomPhysicsComponent.cpp"
|
"PhantomPhysicsComponent.cpp"
|
||||||
"PlayerForcedMovementComponent.cpp"
|
"PlayerForcedMovementComponent.cpp"
|
||||||
"PossessableComponent.cpp"
|
"PossessableComponent.cpp"
|
||||||
"PossessorComponent.cpp"
|
"PossessionComponent.cpp"
|
||||||
"PropertyComponent.cpp"
|
"PropertyComponent.cpp"
|
||||||
"PropertyEntranceComponent.cpp"
|
"PropertyEntranceComponent.cpp"
|
||||||
"PropertyManagementComponent.cpp"
|
"PropertyManagementComponent.cpp"
|
||||||
"PropertyVendorComponent.cpp"
|
"PropertyVendorComponent.cpp"
|
||||||
"ProximityMonitorComponent.cpp"
|
"ProximityMonitorComponent.cpp"
|
||||||
"VehicleRacingControlComponent.cpp"
|
"RacingComponent.cpp"
|
||||||
|
"RacingControlComponent.cpp"
|
||||||
"RacingSoundTriggerComponent.cpp"
|
"RacingSoundTriggerComponent.cpp"
|
||||||
"RacingStatsComponent.cpp"
|
"RacingStatsComponent.cpp"
|
||||||
"RailActivatorComponent.cpp"
|
"RailActivatorComponent.cpp"
|
||||||
|
@ -18,8 +18,8 @@ LWOActivityComponent
|
|||||||
├── LWOShootingGalleryComponent
|
├── LWOShootingGalleryComponent
|
||||||
├── LWOScriptedActivityComponent
|
├── LWOScriptedActivityComponent
|
||||||
| └── LWOBaseRacingControlComponent -> RacingControlComponent
|
| └── LWOBaseRacingControlComponent -> RacingControlComponent
|
||||||
| ├── LWORacingControlComponent -> VehicleRacingControlComponent
|
| ├── LWORacingControlComponent -> RacingComponent
|
||||||
| └── LWOGateRushControlComponent
|
| └── LWOGateRushControlComponent -> GateRushComponent
|
||||||
LWOBaseCombatAIComponent
|
LWOBaseCombatAIComponent
|
||||||
├~~ LWOPathfindingControlComponent
|
├~~ LWOPathfindingControlComponent
|
||||||
├~~ LWOProximityMonitorComponent
|
├~~ LWOProximityMonitorComponent
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
#include "MissionComponent.h"
|
#include "MissionComponent.h"
|
||||||
#include "CharacterComponent.h"
|
#include "CharacterComponent.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "InventoryComponent.h"
|
#include "InventoryComponent.h"
|
||||||
#include "dZoneManager.h"
|
#include "dZoneManager.h"
|
||||||
#include "WorldConfig.h"
|
#include "WorldConfig.h"
|
||||||
@ -646,7 +646,7 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dismount on the possessor hit
|
// Dismount on the possessor hit
|
||||||
auto* possessor = m_ParentEntity->GetComponent<PossessorComponent>();
|
auto* possessor = m_ParentEntity->GetComponent<PossessionComponent>();
|
||||||
if (possessor) {
|
if (possessor) {
|
||||||
auto possessableId = possessor->GetPossessable();
|
auto possessableId = possessor->GetPossessable();
|
||||||
if (possessableId != LWOOBJID_EMPTY) {
|
if (possessableId != LWOOBJID_EMPTY) {
|
||||||
|
5
dGame/dComponents/GateRushComponent.cpp
Normal file
5
dGame/dComponents/GateRushComponent.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#include "GateRushComponent.h"
|
||||||
|
|
||||||
|
GateRushComponent::GateRushComponent(Entity* parent) : RacingControlComponent(parent) {
|
||||||
|
|
||||||
|
}
|
@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
class Entity;
|
class Entity;
|
||||||
|
|
||||||
class GateRushControlComponent : public RacingControlComponent {
|
class GateRushComponent : public RacingControlComponent {
|
||||||
public:
|
public:
|
||||||
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::GATE_RUSH_CONTROL;
|
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::GATE_RUSH_CONTROL;
|
||||||
GateRushControlComponent(Entity* parent, int32_t componentId);
|
GateRushComponent(Entity* parent);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //!__GATERUSHCONTROLCOMPONENT__H__
|
#endif //!__GATERUSHCONTROLCOMPONENT__H__
|
@ -1,5 +0,0 @@
|
|||||||
#include "GateRushControlComponent.h"
|
|
||||||
|
|
||||||
GateRushControlComponent::GateRushControlComponent(Entity* parent, int32_t componentId) : RacingControlComponent(parent, componentId) {
|
|
||||||
|
|
||||||
}
|
|
@ -16,7 +16,7 @@
|
|||||||
#include "ItemSet.h"
|
#include "ItemSet.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "PetComponent.h"
|
#include "PetComponent.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "ModuleAssemblyComponent.h"
|
#include "ModuleAssemblyComponent.h"
|
||||||
#include "HavokVehiclePhysicsComponent.h"
|
#include "HavokVehiclePhysicsComponent.h"
|
||||||
@ -961,25 +961,25 @@ void InventoryComponent::HandlePossession(Item* item) {
|
|||||||
auto* characterComponent = m_ParentEntity->GetComponent<CharacterComponent>();
|
auto* characterComponent = m_ParentEntity->GetComponent<CharacterComponent>();
|
||||||
if (!characterComponent) return;
|
if (!characterComponent) return;
|
||||||
|
|
||||||
auto* possessorComponent = m_ParentEntity->GetComponent<PossessorComponent>();
|
auto* possessionComponent = m_ParentEntity->GetComponent<PossessionComponent>();
|
||||||
if (!possessorComponent) return;
|
if (!possessionComponent) return;
|
||||||
|
|
||||||
// Don't do anything if we are busy dismounting
|
// Don't do anything if we are busy dismounting
|
||||||
if (possessorComponent->GetIsDismounting()) return;
|
if (possessionComponent->GetIsDismounting()) return;
|
||||||
|
|
||||||
// Check to see if we are already mounting something
|
// Check to see if we are already mounting something
|
||||||
auto* currentlyPossessedEntity = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
auto* currentlyPossessedEntity = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
auto currentlyPossessedItem = possessorComponent->GetMountItemID();
|
auto currentlyPossessedItem = possessionComponent->GetMountItemID();
|
||||||
|
|
||||||
if (currentlyPossessedItem) {
|
if (currentlyPossessedItem) {
|
||||||
if (currentlyPossessedEntity) possessorComponent->Dismount(currentlyPossessedEntity);
|
if (currentlyPossessedEntity) possessionComponent->Dismount(currentlyPossessedEntity);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameMessages::SendSetStunned(m_ParentEntity->GetObjectID(), eStateChangeType::PUSH, m_ParentEntity->GetSystemAddress(), LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
|
GameMessages::SendSetStunned(m_ParentEntity->GetObjectID(), eStateChangeType::PUSH, m_ParentEntity->GetSystemAddress(), LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
|
||||||
|
|
||||||
// Set the mount Item ID so that we know what were handling
|
// Set the mount Item ID so that we know what were handling
|
||||||
possessorComponent->SetMountItemID(item->GetId());
|
possessionComponent->SetMountItemID(item->GetId());
|
||||||
GameMessages::SendSetMountInventoryID(m_ParentEntity, item->GetId(), UNASSIGNED_SYSTEM_ADDRESS);
|
GameMessages::SendSetMountInventoryID(m_ParentEntity, item->GetId(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
|
||||||
// Create entity to mount
|
// Create entity to mount
|
||||||
@ -1019,8 +1019,8 @@ void InventoryComponent::HandlePossession(Item* item) {
|
|||||||
possessableComponent->SetIsItemSpawned(true);
|
possessableComponent->SetIsItemSpawned(true);
|
||||||
possessableComponent->SetPossessor(m_ParentEntity->GetObjectID());
|
possessableComponent->SetPossessor(m_ParentEntity->GetObjectID());
|
||||||
// Possess it
|
// Possess it
|
||||||
possessorComponent->SetPossessable(mount->GetObjectID());
|
possessionComponent->SetPossessable(mount->GetObjectID());
|
||||||
possessorComponent->SetPossessableType(possessableComponent->GetPossessionType());
|
possessionComponent->SetPossessableType(possessableComponent->GetPossessionType());
|
||||||
}
|
}
|
||||||
|
|
||||||
GameMessages::SendSetJetPackMode(m_ParentEntity, false);
|
GameMessages::SendSetJetPackMode(m_ParentEntity, false);
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
#include "ItemSetPassiveAbility.h"
|
#include "ItemSetPassiveAbility.h"
|
||||||
#include "eItemSetPassiveAbilityID.h"
|
#include "eItemSetPassiveAbilityID.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "eInventoryType.h"
|
#include "eInventoryType.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
#include "eLootSourceType.h"
|
#include "eLootSourceType.h"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include "MinigameControlComponent.h"
|
|
||||||
|
|
||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
|
#include "MinigameControlComponent.h"
|
||||||
|
|
||||||
MinigameControlComponent::MinigameControlComponent(Entity* parent) : ActivityComponent(parent) {
|
MinigameControlComponent::MinigameControlComponent(Entity* parent) : ActivityComponent(parent) {
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "EntityManager.h"
|
#include "EntityManager.h"
|
||||||
#include "Inventory.h"
|
#include "Inventory.h"
|
||||||
#include "Item.h"
|
#include "Item.h"
|
||||||
@ -48,7 +48,7 @@ void PossessableComponent::Dismount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PossessableComponent::OnUse(Entity* originator) {
|
void PossessableComponent::OnUse(Entity* originator) {
|
||||||
auto* possessor = originator->GetComponent<PossessorComponent>();
|
auto* possessor = originator->GetComponent<PossessionComponent>();
|
||||||
if (possessor) {
|
if (possessor) {
|
||||||
possessor->Mount(m_ParentEntity);
|
possessor->Mount(m_ParentEntity);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Component.h"
|
#include "Component.h"
|
||||||
#include "Item.h"
|
#include "Item.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "eAninmationFlags.h"
|
#include "eAninmationFlags.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "CharacterComponent.h"
|
#include "CharacterComponent.h"
|
||||||
#include "EntityManager.h"
|
#include "EntityManager.h"
|
||||||
@ -7,11 +7,11 @@
|
|||||||
#include "eControlScheme.h"
|
#include "eControlScheme.h"
|
||||||
#include "eStateChangeType.h"
|
#include "eStateChangeType.h"
|
||||||
|
|
||||||
PossessorComponent::PossessorComponent(Entity* parent) : Component(parent) {
|
PossessionComponent::PossessionComponent(Entity* parent) : Component(parent) {
|
||||||
m_Possessable = LWOOBJID_EMPTY;
|
m_Possessable = LWOOBJID_EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
PossessorComponent::~PossessorComponent() {
|
PossessionComponent::~PossessionComponent() {
|
||||||
if (m_Possessable != LWOOBJID_EMPTY) {
|
if (m_Possessable != LWOOBJID_EMPTY) {
|
||||||
auto* mount = EntityManager::Instance()->GetEntity(m_Possessable);
|
auto* mount = EntityManager::Instance()->GetEntity(m_Possessable);
|
||||||
if (mount) {
|
if (mount) {
|
||||||
@ -26,7 +26,7 @@ PossessorComponent::~PossessorComponent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PossessorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
|
void PossessionComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
|
||||||
outBitStream->Write(m_DirtyPossesor || bIsInitialUpdate);
|
outBitStream->Write(m_DirtyPossesor || bIsInitialUpdate);
|
||||||
if (m_DirtyPossesor || bIsInitialUpdate) {
|
if (m_DirtyPossesor || bIsInitialUpdate) {
|
||||||
m_DirtyPossesor = false;
|
m_DirtyPossesor = false;
|
||||||
@ -38,7 +38,7 @@ void PossessorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PossessorComponent::Mount(Entity* mount) {
|
void PossessionComponent::Mount(Entity* mount) {
|
||||||
// Don't do anything if we are busy dismounting
|
// Don't do anything if we are busy dismounting
|
||||||
if (GetIsDismounting() || !mount) return;
|
if (GetIsDismounting() || !mount) return;
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ void PossessorComponent::Mount(Entity* mount) {
|
|||||||
EntityManager::Instance()->SerializeEntity(mount);
|
EntityManager::Instance()->SerializeEntity(mount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PossessorComponent::Dismount(Entity* mount, bool forceDismount) {
|
void PossessionComponent::Dismount(Entity* mount, bool forceDismount) {
|
||||||
// Don't do anything if we are busy dismounting
|
// Don't do anything if we are busy dismounting
|
||||||
if (GetIsDismounting() || !mount) return;
|
if (GetIsDismounting() || !mount) return;
|
||||||
SetIsDismounting(true);
|
SetIsDismounting(true);
|
@ -16,12 +16,12 @@ enum class ePossessionType : uint8_t {
|
|||||||
/**
|
/**
|
||||||
* Represents an entity that can posess other entities. Generally used by players to drive a car.
|
* Represents an entity that can posess other entities. Generally used by players to drive a car.
|
||||||
*/
|
*/
|
||||||
class PossessorComponent : public Component {
|
class PossessionComponent : public Component {
|
||||||
public:
|
public:
|
||||||
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::POSSESSOR;
|
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::POSSESSION;
|
||||||
|
|
||||||
PossessorComponent(Entity* parent);
|
PossessionComponent(Entity* parent);
|
||||||
~PossessorComponent() override;
|
~PossessionComponent() override;
|
||||||
|
|
||||||
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
|
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
|
||||||
|
|
||||||
@ -97,13 +97,13 @@ private:
|
|||||||
ePossessionType m_PossessableType = ePossessionType::NO_POSSESSION;
|
ePossessionType m_PossessableType = ePossessionType::NO_POSSESSION;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief If the possessor is dirty
|
* @brief If the possession is dirty
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
bool m_DirtyPossesor = false;
|
bool m_DirtyPossesor = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief If the possessor is busy dismounting
|
* @brief If the possession is busy dismounting
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
bool m_IsDismounting = false;
|
bool m_IsDismounting = false;
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
#include "CppScripts.h"
|
#include "CppScripts.h"
|
||||||
|
|
||||||
QuickBuildComponent::QuickBuildComponent(Entity* entity, uint32_t componentId) : ActivityComponent(entity) {
|
QuickBuildComponent::QuickBuildComponent(Entity* entity) : ActivityComponent(entity) {
|
||||||
m_ComponentId = componentId;
|
m_ComponentId = componentId;
|
||||||
std::u16string checkPreconditions = entity->GetVar<std::u16string>(u"CheckPrecondition");
|
std::u16string checkPreconditions = entity->GetVar<std::u16string>(u"CheckPrecondition");
|
||||||
|
|
||||||
|
5
dGame/dComponents/RacingComponent.cpp
Normal file
5
dGame/dComponents/RacingComponent.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#include "RacingComponent.h"
|
||||||
|
|
||||||
|
RacingComponent::RacingComponent(Entity* parent) : RacingControlComponent(parent) {
|
||||||
|
|
||||||
|
}
|
16
dGame/dComponents/RacingComponent.h
Normal file
16
dGame/dComponents/RacingComponent.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#ifndef __RACINGCOMPONENT__H__
|
||||||
|
#define __RACINGCOMPONENT__H__
|
||||||
|
|
||||||
|
#include "RacingControlComponent.h"
|
||||||
|
#include "eReplicaComponentType.h"
|
||||||
|
|
||||||
|
class Entity;
|
||||||
|
|
||||||
|
class RacingComponent : public RacingControlComponent {
|
||||||
|
public:
|
||||||
|
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL;
|
||||||
|
RacingComponent(Entity* parent);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //!__RACINGCOMPONENT__H__
|
@ -1,5 +1,880 @@
|
|||||||
|
/**
|
||||||
|
* Thanks to Simon for his early research on the racing system.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "RacingControlComponent.h"
|
#include "RacingControlComponent.h"
|
||||||
|
|
||||||
RacingControlComponent::RacingControlComponent(Entity* parent, int32_t componentId) : ScriptedActivityComponent(parent, componentId) {
|
#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 "PossessionComponent.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
|
||||||
|
|
||||||
|
RacingControlComponent::RacingControlComponent(Entity* parent) : ScriptedActivityComponent(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;
|
||||||
|
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<CDActivitiesTable>();
|
||||||
|
std::vector<CDActivities> 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*>(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<InventoryComponent>();
|
||||||
|
|
||||||
|
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<DestroyableComponent>();
|
||||||
|
|
||||||
|
// 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<PossessableComponent>();
|
||||||
|
|
||||||
|
if (possessableComponent != nullptr) {
|
||||||
|
possessableComponent->SetPossessor(player->GetObjectID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the vehicle's assemblyPartLOTs for display.
|
||||||
|
auto* moduleAssemblyComponent = carEntity->GetComponent<ModuleAssemblyComponent>();
|
||||||
|
|
||||||
|
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* possessionComponent = player->GetComponent<PossessionComponent>();
|
||||||
|
|
||||||
|
if (possessionComponent != nullptr) {
|
||||||
|
possessionComponent->SetPossessable(carEntity->GetObjectID());
|
||||||
|
possessionComponent->SetPossessableType(ePossessionType::ATTACHED_VISIBLE); // for racing it's always Attached_Visible
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the player's current activity as racing.
|
||||||
|
auto* characterComponent = player->GetComponent<CharacterComponent>();
|
||||||
|
|
||||||
|
if (characterComponent != nullptr) {
|
||||||
|
characterComponent->SetIsRacing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_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<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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<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_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<MissionComponent>();
|
||||||
|
|
||||||
|
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*>(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<uint32_t>(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<uint16_t>(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<uint32_t>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
outBitStream->Write0(); // No more data
|
||||||
|
}
|
||||||
|
|
||||||
|
outBitStream->Write1(); // Dirty?
|
||||||
|
|
||||||
|
outBitStream->Write(m_RemainingLaps);
|
||||||
|
|
||||||
|
outBitStream->Write(static_cast<uint16_t>(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<Player*>(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<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<DestroyableComponent>();
|
||||||
|
|
||||||
|
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<MissionComponent>();
|
||||||
|
|
||||||
|
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<CharacterComponent>();
|
||||||
|
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";
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,254 @@
|
|||||||
#ifndef __RACINGCONTROLCOMPONENT__H__
|
/**
|
||||||
#define __RACINGCONTROLCOMPONENT__H__
|
* Thanks to Simon for his early research on the racing system.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BitStream.h"
|
||||||
|
#include "Entity.h"
|
||||||
#include "ScriptedActivityComponent.h"
|
#include "ScriptedActivityComponent.h"
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
|
|
||||||
class Entity;
|
/**
|
||||||
|
* 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 RacingControlComponent : public ScriptedActivityComponent {
|
class RacingControlComponent : public ScriptedActivityComponent {
|
||||||
public:
|
public:
|
||||||
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL;
|
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL;
|
||||||
RacingControlComponent(Entity* parent, int32_t componentId);
|
|
||||||
|
RacingControlComponent(Entity* parentEntity);
|
||||||
|
~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<RacingPlayerInfo> 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<LWOOBJID> 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif //!__RACINGCONTROLCOMPONENT__H__
|
|
||||||
|
@ -1,621 +1,5 @@
|
|||||||
#include "ScriptedActivityComponent.h"
|
#include "ScriptedActivityComponent.h"
|
||||||
#include "GameMessages.h"
|
|
||||||
#include "CDClientManager.h"
|
|
||||||
#include "MissionComponent.h"
|
|
||||||
#include "Character.h"
|
|
||||||
#include "dZoneManager.h"
|
|
||||||
#include "ZoneInstanceManager.h"
|
|
||||||
#include "Game.h"
|
|
||||||
#include "dLogger.h"
|
|
||||||
#include <WorldPackets.h>
|
|
||||||
#include "EntityManager.h"
|
|
||||||
#include "ChatPackets.h"
|
|
||||||
#include "Player.h"
|
|
||||||
#include "PacketUtils.h"
|
|
||||||
#include "dServer.h"
|
|
||||||
#include "GeneralUtils.h"
|
|
||||||
#include "dZoneManager.h"
|
|
||||||
#include "dConfig.h"
|
|
||||||
#include "InventoryComponent.h"
|
|
||||||
#include "DestroyableComponent.h"
|
|
||||||
#include "Loot.h"
|
|
||||||
#include "eMissionTaskType.h"
|
|
||||||
#include "eMatchUpdate.h"
|
|
||||||
#include "eConnectionType.h"
|
|
||||||
#include "eChatInternalMessageType.h"
|
|
||||||
|
|
||||||
#include "CDCurrencyTableTable.h"
|
ScriptedActivityComponent::ScriptedActivityComponent(Entity* parent) : ActivityComponent(parent) {
|
||||||
#include "CDActivityRewardsTable.h"
|
|
||||||
#include "CDActivitiesTable.h"
|
|
||||||
#include "LeaderboardManager.h"
|
|
||||||
|
|
||||||
ScriptedActivityComponent::ScriptedActivityComponent(Entity* parent, int activityID) : Component(parent) {
|
|
||||||
m_ActivityID = activityID;
|
|
||||||
CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable<CDActivitiesTable>();
|
|
||||||
std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); });
|
|
||||||
|
|
||||||
for (CDActivities activity : activities) {
|
|
||||||
m_ActivityInfo = activity;
|
|
||||||
if (static_cast<LeaderboardType>(activity.leaderboardType) == LeaderboardType::Racing && Game::config->GetValue("solo_racing") == "1") {
|
|
||||||
m_ActivityInfo.minTeamSize = 1;
|
|
||||||
m_ActivityInfo.minTeams = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& transferOverride = parent->GetVar<std::u16string>(u"transferZoneID");
|
|
||||||
if (!transferOverride.empty()) {
|
|
||||||
m_ActivityInfo.instanceMapID = std::stoi(GeneralUtils::UTF16ToWTF8(transferOverride));
|
|
||||||
|
|
||||||
// TODO: LU devs made me do it (for some reason cannon cove instancer is marked to go to GF survival)
|
|
||||||
// NOTE: 1301 is GF survival
|
|
||||||
if (m_ActivityInfo.instanceMapID == 1301) {
|
|
||||||
m_ActivityInfo.instanceMapID = 1302;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* destroyableComponent = m_ParentEntity->GetComponent<DestroyableComponent>();
|
|
||||||
|
|
||||||
if (destroyableComponent) {
|
|
||||||
// check for LMIs and set the loot LMIs
|
|
||||||
CDActivityRewardsTable* activityRewardsTable = CDClientManager::Instance().GetTable<CDActivityRewardsTable>();
|
|
||||||
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) {return (entry.LootMatrixIndex == destroyableComponent->GetLootMatrixID()); });
|
|
||||||
|
|
||||||
uint32_t startingLMI = 0;
|
|
||||||
|
|
||||||
if (activityRewards.size() > 0) {
|
|
||||||
startingLMI = activityRewards[0].LootMatrixIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startingLMI > 0) {
|
|
||||||
// now time for bodge :)
|
|
||||||
|
|
||||||
std::vector<CDActivityRewards> objectTemplateActivities = activityRewardsTable->Query([=](CDActivityRewards entry) {return (activityRewards[0].objectTemplate == entry.objectTemplate); });
|
|
||||||
for (const auto& item : objectTemplateActivities) {
|
|
||||||
if (item.activityRating > 0 && item.activityRating < 5) {
|
|
||||||
m_ActivityLootMatrices.insert({ item.activityRating, item.LootMatrixIndex });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptedActivityComponent::~ScriptedActivityComponent()
|
|
||||||
= default;
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) const {
|
|
||||||
outBitStream->Write(true);
|
|
||||||
outBitStream->Write<uint32_t>(m_ActivityPlayers.size());
|
|
||||||
|
|
||||||
if (!m_ActivityPlayers.empty()) {
|
|
||||||
for (const auto& activityPlayer : m_ActivityPlayers) {
|
|
||||||
|
|
||||||
outBitStream->Write<LWOOBJID>(activityPlayer->playerID);
|
|
||||||
for (const auto& activityValue : activityPlayer->values) {
|
|
||||||
outBitStream->Write<float_t>(activityValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::ReloadConfig() {
|
|
||||||
CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable<CDActivitiesTable>();
|
|
||||||
std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); });
|
|
||||||
for (auto activity : activities) {
|
|
||||||
auto mapID = m_ActivityInfo.instanceMapID;
|
|
||||||
if ((mapID == 1203 || mapID == 1261 || mapID == 1303 || mapID == 1403) && Game::config->GetValue("solo_racing") == "1") {
|
|
||||||
m_ActivityInfo.minTeamSize = 1;
|
|
||||||
m_ActivityInfo.minTeams = 1;
|
|
||||||
} else {
|
|
||||||
m_ActivityInfo.minTeamSize = activity.minTeamSize;
|
|
||||||
m_ActivityInfo.minTeams = activity.minTeams;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::HandleMessageBoxResponse(Entity* player, const std::string& id) {
|
|
||||||
if (m_ActivityInfo.ActivityID == 103) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id == "LobbyExit") {
|
|
||||||
PlayerLeave(player->GetObjectID());
|
|
||||||
} else if (id == "PlayButton") {
|
|
||||||
PlayerJoin(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::PlayerJoin(Entity* player) {
|
|
||||||
if (m_ActivityInfo.ActivityID == 103 || PlayerIsInQueue(player) || !IsValidActivity(player)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a lobby, queue the player and allow others to join, otherwise spin up an instance on the spot
|
|
||||||
if (HasLobby()) {
|
|
||||||
PlayerJoinLobby(player);
|
|
||||||
} else if (!IsPlayedBy(player)) {
|
|
||||||
auto* instance = NewInstance();
|
|
||||||
instance->AddParticipant(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::PlayerJoinLobby(Entity* player) {
|
|
||||||
if (!m_ParentEntity->HasComponent(eReplicaComponentType::QUICK_BUILD))
|
|
||||||
GameMessages::SendMatchResponse(player, player->GetSystemAddress(), 0); // tell the client they joined a lobby
|
|
||||||
LobbyPlayer* newLobbyPlayer = new LobbyPlayer();
|
|
||||||
newLobbyPlayer->entityID = player->GetObjectID();
|
|
||||||
Lobby* playerLobby = nullptr;
|
|
||||||
|
|
||||||
auto* character = player->GetCharacter();
|
|
||||||
if (character != nullptr)
|
|
||||||
character->SetLastNonInstanceZoneID(dZoneManager::Instance()->GetZone()->GetWorldID());
|
|
||||||
|
|
||||||
for (Lobby* lobby : m_Queue) {
|
|
||||||
if (lobby->players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() < m_ActivityInfo.maxTeams) {
|
|
||||||
// If an empty slot in an existing lobby is found
|
|
||||||
lobby->players.push_back(newLobbyPlayer);
|
|
||||||
playerLobby = lobby;
|
|
||||||
|
|
||||||
// Update the joining player on players already in the lobby, and update players already in the lobby on the joining player
|
|
||||||
std::string matchUpdateJoined = "player=9:" + std::to_string(player->GetObjectID()) + "\nplayerName=0:" + player->GetCharacter()->GetName();
|
|
||||||
for (LobbyPlayer* joinedPlayer : lobby->players) {
|
|
||||||
auto* entity = joinedPlayer->GetEntity();
|
|
||||||
|
|
||||||
if (entity == nullptr) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string matchUpdate = "player=9:" + std::to_string(entity->GetObjectID()) + "\nplayerName=0:" + entity->GetCharacter()->GetName();
|
|
||||||
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchUpdate, eMatchUpdate::PLAYER_ADDED);
|
|
||||||
PlayerReady(entity, joinedPlayer->ready);
|
|
||||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateJoined, eMatchUpdate::PLAYER_ADDED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!playerLobby) {
|
|
||||||
// If all lobbies are full
|
|
||||||
playerLobby = new Lobby();
|
|
||||||
playerLobby->players.push_back(newLobbyPlayer);
|
|
||||||
playerLobby->timer = m_ActivityInfo.waitTime / 1000;
|
|
||||||
m_Queue.push_back(playerLobby);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_ActivityInfo.maxTeamSize != 1 && playerLobby->players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && playerLobby->players.size() >= m_ActivityInfo.minTeams) {
|
|
||||||
// Update the joining player on the match timer
|
|
||||||
std::string matchTimerUpdate = "time=3:" + std::to_string(playerLobby->timer);
|
|
||||||
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::PlayerLeave(LWOOBJID playerID) {
|
|
||||||
|
|
||||||
// Removes the player from a lobby and notifies the others, not applicable for non-lobby instances
|
|
||||||
for (Lobby* lobby : m_Queue) {
|
|
||||||
for (int i = 0; i < lobby->players.size(); ++i) {
|
|
||||||
if (lobby->players[i]->entityID == playerID) {
|
|
||||||
std::string matchUpdateLeft = "player=9:" + std::to_string(playerID);
|
|
||||||
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
|
||||||
auto* entity = lobbyPlayer->GetEntity();
|
|
||||||
if (entity == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft, eMatchUpdate::PLAYER_REMOVED);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete lobby->players[i];
|
|
||||||
lobby->players[i] = nullptr;
|
|
||||||
lobby->players.erase(lobby->players.begin() + i);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::Update(float deltaTime) {
|
|
||||||
std::vector<Lobby*> lobbiesToRemove{};
|
|
||||||
// Ticks all the lobbies, not applicable for non-instance activities
|
|
||||||
for (Lobby* lobby : m_Queue) {
|
|
||||||
for (LobbyPlayer* player : lobby->players) {
|
|
||||||
auto* entity = player->GetEntity();
|
|
||||||
if (entity == nullptr) {
|
|
||||||
PlayerLeave(player->entityID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lobby->players.empty()) {
|
|
||||||
lobbiesToRemove.push_back(lobby);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the match time for all players
|
|
||||||
if (m_ActivityInfo.maxTeamSize != 1 && lobby->players.size() >= m_ActivityInfo.minTeamSize
|
|
||||||
|| m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() >= m_ActivityInfo.minTeams) {
|
|
||||||
if (lobby->timer == m_ActivityInfo.waitTime / 1000) {
|
|
||||||
for (LobbyPlayer* joinedPlayer : lobby->players) {
|
|
||||||
auto* entity = joinedPlayer->GetEntity();
|
|
||||||
|
|
||||||
if (entity == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
|
|
||||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lobby->timer -= deltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool lobbyReady = true;
|
|
||||||
for (LobbyPlayer* player : lobby->players) {
|
|
||||||
if (player->ready) continue;
|
|
||||||
lobbyReady = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If everyone's ready, jump the timer
|
|
||||||
if (lobbyReady && lobby->timer > m_ActivityInfo.startDelay / 1000) {
|
|
||||||
lobby->timer = m_ActivityInfo.startDelay / 1000;
|
|
||||||
|
|
||||||
// Update players in lobby on switch to start delay
|
|
||||||
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
|
|
||||||
for (LobbyPlayer* player : lobby->players) {
|
|
||||||
auto* entity = player->GetEntity();
|
|
||||||
|
|
||||||
if (entity == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_START);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The timer has elapsed, start the instance
|
|
||||||
if (lobby->timer <= 0.0f) {
|
|
||||||
Game::logger->Log("ScriptedActivityComponent", "Setting up instance.");
|
|
||||||
ActivityInstance* instance = NewInstance();
|
|
||||||
LoadPlayersIntoInstance(instance, lobby->players);
|
|
||||||
instance->StartZone();
|
|
||||||
lobbiesToRemove.push_back(lobby);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!lobbiesToRemove.empty()) {
|
|
||||||
RemoveLobby(lobbiesToRemove.front());
|
|
||||||
lobbiesToRemove.erase(lobbiesToRemove.begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::RemoveLobby(Lobby* lobby) {
|
|
||||||
for (int i = 0; i < m_Queue.size(); ++i) {
|
|
||||||
if (m_Queue[i] == lobby) {
|
|
||||||
m_Queue.erase(m_Queue.begin() + i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScriptedActivityComponent::HasLobby() const {
|
|
||||||
// If the player is not in the world he has to be, create a lobby for the transfer
|
|
||||||
return m_ActivityInfo.instanceMapID != UINT_MAX && m_ActivityInfo.instanceMapID != Game::server->GetZoneID();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScriptedActivityComponent::IsValidActivity(Entity* player) {
|
|
||||||
// Makes it so that scripted activities with an unimplemented map cannot be joined
|
|
||||||
/*if (player->GetGMLevel() < eGameMasterLevel::DEVELOPER && (m_ActivityInfo.instanceMapID == 1302 || m_ActivityInfo.instanceMapID == 1301)) {
|
|
||||||
if (m_ParentEntity->GetLOT() == 4860) {
|
|
||||||
auto* missionComponent = player->GetComponent<MissionComponent>();
|
|
||||||
missionComponent->CompleteMission(229);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatPackets::SendSystemMessage(player->GetSystemAddress(), u"Sorry, this activity is not ready.");
|
|
||||||
static_cast<Player*>(player)->SendToZone(dZoneManager::Instance()->GetZone()->GetWorldID()); // Gets them out of this stuck state
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScriptedActivityComponent::PlayerIsInQueue(Entity* player) {
|
|
||||||
for (Lobby* lobby : m_Queue) {
|
|
||||||
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
|
||||||
if (player->GetObjectID() == lobbyPlayer->entityID) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScriptedActivityComponent::IsPlayedBy(Entity* player) const {
|
|
||||||
for (const auto* instance : this->m_Instances) {
|
|
||||||
for (const auto* instancePlayer : instance->GetParticipants()) {
|
|
||||||
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == player->GetObjectID())
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScriptedActivityComponent::IsPlayedBy(LWOOBJID playerID) const {
|
|
||||||
for (const auto* instance : this->m_Instances) {
|
|
||||||
for (const auto* instancePlayer : instance->GetParticipants()) {
|
|
||||||
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == playerID)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScriptedActivityComponent::TakeCost(Entity* player) const {
|
|
||||||
if (m_ActivityInfo.optionalCostLOT <= 0 || m_ActivityInfo.optionalCostCount <= 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
|
|
||||||
if (inventoryComponent == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (inventoryComponent->GetLotCount(m_ActivityInfo.optionalCostLOT) < m_ActivityInfo.optionalCostCount)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::PlayerReady(Entity* player, bool bReady) {
|
|
||||||
for (Lobby* lobby : m_Queue) {
|
|
||||||
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
|
||||||
if (lobbyPlayer->entityID == player->GetObjectID()) {
|
|
||||||
|
|
||||||
lobbyPlayer->ready = bReady;
|
|
||||||
|
|
||||||
// Update players in lobby on player being ready
|
|
||||||
std::string matchReadyUpdate = "player=9:" + std::to_string(player->GetObjectID());
|
|
||||||
eMatchUpdate readyStatus = eMatchUpdate::PLAYER_READY;
|
|
||||||
if (!bReady) readyStatus = eMatchUpdate::PLAYER_NOT_READY;
|
|
||||||
for (LobbyPlayer* otherPlayer : lobby->players) {
|
|
||||||
auto* entity = otherPlayer->GetEntity();
|
|
||||||
if (entity == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate, readyStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityInstance* ScriptedActivityComponent::NewInstance() {
|
|
||||||
auto* instance = new ActivityInstance(m_ParentEntity, m_ActivityInfo);
|
|
||||||
m_Instances.push_back(instance);
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const {
|
|
||||||
for (LobbyPlayer* player : lobby) {
|
|
||||||
auto* entity = player->GetEntity();
|
|
||||||
if (entity == nullptr || !TakeCost(entity)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance->AddParticipant(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<ActivityInstance*>& ScriptedActivityComponent::GetInstances() const {
|
|
||||||
return m_Instances;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityInstance* ScriptedActivityComponent::GetInstance(const LWOOBJID playerID) {
|
|
||||||
for (const auto* instance : GetInstances()) {
|
|
||||||
for (const auto* participant : instance->GetParticipants()) {
|
|
||||||
if (participant->GetObjectID() == playerID)
|
|
||||||
return const_cast<ActivityInstance*>(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::ClearInstances() {
|
|
||||||
for (ActivityInstance* instance : m_Instances) {
|
|
||||||
delete instance;
|
|
||||||
}
|
|
||||||
m_Instances.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityPlayer* ScriptedActivityComponent::GetActivityPlayerData(LWOOBJID playerID) {
|
|
||||||
for (auto* activityData : m_ActivityPlayers) {
|
|
||||||
if (activityData->playerID == playerID) {
|
|
||||||
return activityData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::RemoveActivityPlayerData(LWOOBJID playerID) {
|
|
||||||
for (size_t i = 0; i < m_ActivityPlayers.size(); i++) {
|
|
||||||
if (m_ActivityPlayers[i]->playerID == playerID) {
|
|
||||||
delete m_ActivityPlayers[i];
|
|
||||||
m_ActivityPlayers[i] = nullptr;
|
|
||||||
|
|
||||||
m_ActivityPlayers.erase(m_ActivityPlayers.begin() + i);
|
|
||||||
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityPlayer* ScriptedActivityComponent::AddActivityPlayerData(LWOOBJID playerID) {
|
|
||||||
auto* data = GetActivityPlayerData(playerID);
|
|
||||||
if (data != nullptr)
|
|
||||||
return data;
|
|
||||||
|
|
||||||
m_ActivityPlayers.push_back(new ActivityPlayer{ playerID, {} });
|
|
||||||
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
|
|
||||||
|
|
||||||
return GetActivityPlayerData(playerID);
|
|
||||||
}
|
|
||||||
|
|
||||||
float_t ScriptedActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) {
|
|
||||||
auto value = -1.0f;
|
|
||||||
|
|
||||||
auto* data = GetActivityPlayerData(playerID);
|
|
||||||
if (data != nullptr) {
|
|
||||||
value = data->values[std::min(index, (uint32_t)9)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value) {
|
|
||||||
auto* data = AddActivityPlayerData(playerID);
|
|
||||||
if (data != nullptr) {
|
|
||||||
data->values[std::min(index, (uint32_t)9)] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptedActivityComponent::PlayerRemove(LWOOBJID playerID) {
|
|
||||||
for (auto* instance : GetInstances()) {
|
|
||||||
auto participants = instance->GetParticipants();
|
|
||||||
for (const auto* participant : participants) {
|
|
||||||
if (participant != nullptr && participant->GetObjectID() == playerID) {
|
|
||||||
instance->RemoveParticipant(participant);
|
|
||||||
RemoveActivityPlayerData(playerID);
|
|
||||||
|
|
||||||
// If the instance is empty after the delete of the participant, delete the instance too
|
|
||||||
if (instance->GetParticipants().empty()) {
|
|
||||||
m_Instances.erase(std::find(m_Instances.begin(), m_Instances.end(), instance));
|
|
||||||
delete instance;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityInstance::StartZone() {
|
|
||||||
if (m_Participants.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto& participants = GetParticipants();
|
|
||||||
if (participants.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto* leader = participants[0];
|
|
||||||
LWOZONEID zoneId = LWOZONEID(m_ActivityInfo.instanceMapID, 0, leader->GetCharacter()->GetPropertyCloneID());
|
|
||||||
|
|
||||||
// only make a team if we have more than one participant
|
|
||||||
if (participants.size() > 1) {
|
|
||||||
CBITSTREAM;
|
|
||||||
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::CREATE_TEAM);
|
|
||||||
|
|
||||||
bitStream.Write(leader->GetObjectID());
|
|
||||||
bitStream.Write(m_Participants.size());
|
|
||||||
|
|
||||||
for (const auto& participant : m_Participants) {
|
|
||||||
bitStream.Write(participant);
|
|
||||||
}
|
|
||||||
|
|
||||||
bitStream.Write(zoneId);
|
|
||||||
|
|
||||||
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto cloneId = GeneralUtils::GenerateRandomNumber<uint32_t>(1, UINT32_MAX);
|
|
||||||
for (Entity* player : participants) {
|
|
||||||
const auto objid = player->GetObjectID();
|
|
||||||
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, m_ActivityInfo.instanceMapID, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
|
||||||
|
|
||||||
auto* player = EntityManager::Instance()->GetEntity(objid);
|
|
||||||
if (player == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", player->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
|
||||||
if (player->GetCharacter()) {
|
|
||||||
player->GetCharacter()->SetZoneID(zoneID);
|
|
||||||
player->GetCharacter()->SetZoneInstance(zoneInstance);
|
|
||||||
player->GetCharacter()->SetZoneClone(zoneClone);
|
|
||||||
}
|
|
||||||
|
|
||||||
WorldPackets::SendTransferToWorld(player->GetSystemAddress(), serverIP, serverPort, mythranShift);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
m_NextZoneCloneID++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityInstance::RewardParticipant(Entity* participant) {
|
|
||||||
auto* missionComponent = participant->GetComponent<MissionComponent>();
|
|
||||||
if (missionComponent) {
|
|
||||||
missionComponent->Progress(eMissionTaskType::ACTIVITY, m_ActivityInfo.ActivityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, get the activity data
|
|
||||||
auto* activityRewardsTable = CDClientManager::Instance().GetTable<CDActivityRewardsTable>();
|
|
||||||
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) { return (entry.objectTemplate == m_ActivityInfo.ActivityID); });
|
|
||||||
|
|
||||||
if (!activityRewards.empty()) {
|
|
||||||
uint32_t minCoins = 0;
|
|
||||||
uint32_t maxCoins = 0;
|
|
||||||
|
|
||||||
auto* currencyTableTable = CDClientManager::Instance().GetTable<CDCurrencyTableTable>();
|
|
||||||
std::vector<CDCurrencyTable> currencyTable = currencyTableTable->Query([=](CDCurrencyTable entry) { return (entry.currencyIndex == activityRewards[0].CurrencyIndex && entry.npcminlevel == 1); });
|
|
||||||
|
|
||||||
if (!currencyTable.empty()) {
|
|
||||||
minCoins = currencyTable[0].minvalue;
|
|
||||||
maxCoins = currencyTable[0].maxvalue;
|
|
||||||
}
|
|
||||||
|
|
||||||
LootGenerator::Instance().DropLoot(participant, m_ParentEntity, activityRewards[0].LootMatrixIndex, minCoins, maxCoins);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Entity*> ActivityInstance::GetParticipants() const {
|
|
||||||
std::vector<Entity*> entities;
|
|
||||||
entities.reserve(m_Participants.size());
|
|
||||||
|
|
||||||
for (const auto& id : m_Participants) {
|
|
||||||
auto* entity = EntityManager::Instance()->GetEntity(id);
|
|
||||||
if (entity != nullptr)
|
|
||||||
entities.push_back(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityInstance::AddParticipant(Entity* participant) {
|
|
||||||
const auto id = participant->GetObjectID();
|
|
||||||
if (std::count(m_Participants.begin(), m_Participants.end(), id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_Participants.push_back(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityInstance::RemoveParticipant(const Entity* participant) {
|
|
||||||
const auto loadedParticipant = std::find(m_Participants.begin(), m_Participants.end(), participant->GetObjectID());
|
|
||||||
if (loadedParticipant != m_Participants.end()) {
|
|
||||||
m_Participants.erase(loadedParticipant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t ActivityInstance::GetScore() const {
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityInstance::SetScore(uint32_t score) {
|
|
||||||
this->score = score;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity* LobbyPlayer::GetEntity() const {
|
|
||||||
return EntityManager::Instance()->GetEntity(entityID);
|
|
||||||
}
|
}
|
||||||
|
@ -1,381 +1,18 @@
|
|||||||
/*
|
|
||||||
* Darkflame Universe
|
|
||||||
* Copyright 2018
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "CDClientManager.h"
|
#ifndef __SCRIPTEDACTIVITYCOMPONENT__H__
|
||||||
|
#define __SCRIPTEDACTIVITYCOMPONENT__H__
|
||||||
|
|
||||||
#ifndef SCRIPTEDACTIVITYCOMPONENT_H
|
#include "ActivityComponent.h"
|
||||||
#define SCRIPTEDACTIVITYCOMPONENT_H
|
|
||||||
|
|
||||||
#include "BitStream.h"
|
|
||||||
#include "Entity.h"
|
|
||||||
#include "Component.h"
|
|
||||||
#include "eReplicaComponentType.h"
|
#include "eReplicaComponentType.h"
|
||||||
|
|
||||||
#include "CDActivitiesTable.h"
|
class Entity;
|
||||||
|
|
||||||
/**
|
class ScriptedActivityComponent : public ActivityComponent {
|
||||||
* Represents an instance of an activity, having participants and score
|
|
||||||
*/
|
|
||||||
class ActivityInstance {
|
|
||||||
public:
|
|
||||||
ActivityInstance(Entity* parent, CDActivities activityInfo) { m_ParentEntity = parent; m_ActivityInfo = activityInfo; };
|
|
||||||
//~ActivityInstance();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an entity to this activity
|
|
||||||
* @param participant the entity to add
|
|
||||||
*/
|
|
||||||
void AddParticipant(Entity* participant);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all the participants from this activity
|
|
||||||
*/
|
|
||||||
void ClearParticipants() { m_Participants.clear(); };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the instance world for this activity and sends all participants there
|
|
||||||
*/
|
|
||||||
void StartZone();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gives the rewards for completing this activity to some participant
|
|
||||||
* @param participant the participant to give rewards to
|
|
||||||
*/
|
|
||||||
void RewardParticipant(Entity* participant);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a participant from this activity
|
|
||||||
* @param participant the participant to remove
|
|
||||||
*/
|
|
||||||
void RemoveParticipant(const Entity* participant);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the participants of this activity
|
|
||||||
* @return all the participants of this activity
|
|
||||||
*/
|
|
||||||
std::vector<Entity*> GetParticipants() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently unused
|
|
||||||
*/
|
|
||||||
uint32_t GetScore() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently unused
|
|
||||||
*/
|
|
||||||
void SetScore(uint32_t score);
|
|
||||||
private:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently unused
|
|
||||||
*/
|
|
||||||
uint32_t score = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The instance ID of this activity
|
|
||||||
*/
|
|
||||||
uint32_t m_NextZoneCloneID = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database information for this activity
|
|
||||||
*/
|
|
||||||
CDActivities m_ActivityInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The entity that owns this activity (the entity that has the ScriptedActivityComponent)
|
|
||||||
*/
|
|
||||||
Entity* m_ParentEntity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All the participants of this activity
|
|
||||||
*/
|
|
||||||
std::vector<LWOOBJID> m_Participants;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an entity in a lobby
|
|
||||||
*/
|
|
||||||
struct LobbyPlayer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the entity that is in the lobby
|
|
||||||
*/
|
|
||||||
LWOOBJID entityID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the entity is ready
|
|
||||||
*/
|
|
||||||
bool ready = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the entity that is in the lobby
|
|
||||||
* @return the entity that is in the lobby
|
|
||||||
*/
|
|
||||||
Entity* GetEntity() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a lobby of players with a timer until it should start the activity
|
|
||||||
*/
|
|
||||||
struct Lobby {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The lobby of players
|
|
||||||
*/
|
|
||||||
std::vector<LobbyPlayer*> players;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The timer that determines when the activity should start
|
|
||||||
*/
|
|
||||||
float timer;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the score for the player in an activity, one index might represent score, another one time, etc.
|
|
||||||
*/
|
|
||||||
struct ActivityPlayer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The entity that the score is tracked for
|
|
||||||
*/
|
|
||||||
LWOOBJID playerID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of score for this entity
|
|
||||||
*/
|
|
||||||
float values[10];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Welcome to the absolute behemoth that is the scripted activity component. I have now clue how this was managed in
|
|
||||||
* live but I figure somewhat similarly and it's terrible. In a nutshell, this components handles any activity that
|
|
||||||
* can be done in the game from quick builds to boss fights to races. On top of that, this component handles instancing
|
|
||||||
* and lobbying.
|
|
||||||
*/
|
|
||||||
class ScriptedActivityComponent : public Component {
|
|
||||||
public:
|
public:
|
||||||
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::SCRIPTED_ACTIVITY;
|
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::SCRIPTED_ACTIVITY;
|
||||||
|
ScriptedActivityComponent(Entity* parent);
|
||||||
ScriptedActivityComponent(Entity* parent, int activityID);
|
|
||||||
~ScriptedActivityComponent() override;
|
|
||||||
|
|
||||||
void Update(float deltaTime) override;
|
|
||||||
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes some entity join the minigame, if it's a lobbied one, the entity will be placed in the lobby
|
|
||||||
* @param player the entity to join the game
|
|
||||||
*/
|
|
||||||
void PlayerJoin(Entity* player);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes an entity join the lobby for this minigame, if it exists
|
|
||||||
* @param player the entity to join
|
|
||||||
*/
|
|
||||||
void PlayerJoinLobby(Entity* player);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the player leave the lobby
|
|
||||||
* @param playerID the entity to leave the lobby
|
|
||||||
*/
|
|
||||||
void PlayerLeave(LWOOBJID playerID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the entity from the minigame (and its score)
|
|
||||||
* @param playerID the entity to remove from the minigame
|
|
||||||
*/
|
|
||||||
void PlayerRemove(LWOOBJID playerID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds all the players to an instance of some activity
|
|
||||||
* @param instance the instance to load the players into
|
|
||||||
* @param lobby the players to load into the instance
|
|
||||||
*/
|
|
||||||
void LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a lobby from the activity manager
|
|
||||||
* @param lobby the lobby to remove
|
|
||||||
*/
|
|
||||||
void RemoveLobby(Lobby* lobby);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks a player as (un)ready in a lobby
|
|
||||||
* @param player the entity to mark
|
|
||||||
* @param bReady true if the entity is ready, false otherwise
|
|
||||||
*/
|
|
||||||
void PlayerReady(Entity* player, bool bReady);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ID of this activity
|
|
||||||
* @return the ID of this activity
|
|
||||||
*/
|
|
||||||
int GetActivityID() { return m_ActivityInfo.ActivityID; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if this activity has a lobby, e.g. if it needs to instance players to some other map
|
|
||||||
* @return true if this activity has a lobby, false otherwise
|
|
||||||
*/
|
|
||||||
bool HasLobby() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a player is currently waiting in a lobby
|
|
||||||
* @param player the entity to check for
|
|
||||||
* @return true if the entity is waiting in a lobby, false otherwise
|
|
||||||
*/
|
|
||||||
bool PlayerIsInQueue(Entity* player);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an entity is currently playing this activity
|
|
||||||
* @param player the entity to check
|
|
||||||
* @return true if the entity is playing this lobby, false otherwise
|
|
||||||
*/
|
|
||||||
bool IsPlayedBy(Entity* player) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if an entity is currently playing this activity
|
|
||||||
* @param playerID the entity to check
|
|
||||||
* @return true if the entity is playing this lobby, false otherwise
|
|
||||||
*/
|
|
||||||
bool IsPlayedBy(LWOOBJID playerID) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy: used to check for unimplemented maps, gladly, this now just returns true :)
|
|
||||||
*/
|
|
||||||
bool IsValidActivity(Entity* player);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the cost of the activity (e.g. green imaginate) for the entity that plays this activity
|
|
||||||
* @param player the entity to take cost for
|
|
||||||
* @return true if the cost was successfully deducted, false otherwise
|
|
||||||
*/
|
|
||||||
bool TakeCost(Entity* player) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles any response from a player clicking on a lobby / instance menu
|
|
||||||
* @param player the entity that clicked
|
|
||||||
* @param id the message that was passed
|
|
||||||
*/
|
|
||||||
void HandleMessageBoxResponse(Entity* player, const std::string& id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance for this activity
|
|
||||||
* @return a new instance for this activity
|
|
||||||
*/
|
|
||||||
ActivityInstance* NewInstance();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the currently active instances of this activity
|
|
||||||
* @return all the currently active instances of this activity
|
|
||||||
*/
|
|
||||||
const std::vector<ActivityInstance*>& GetInstances() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the instance that some entity is currently playing in
|
|
||||||
* @param playerID the entity to check for
|
|
||||||
* @return if any, the instance that the entity is currently in
|
|
||||||
*/
|
|
||||||
ActivityInstance* GetInstance(const LWOOBJID playerID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Reloads the config settings for this component
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void ReloadConfig();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all the instances
|
|
||||||
*/
|
|
||||||
void ClearInstances();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the score for the players that are currently playing this activity
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
std::vector<ActivityPlayer*> GetActivityPlayers() { return m_ActivityPlayers; };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns activity data for a specific entity (e.g. score and such).
|
|
||||||
* @param playerID the entity to get data for
|
|
||||||
* @return the activity data (score) for the passed player in this activity, if it exists
|
|
||||||
*/
|
|
||||||
ActivityPlayer* GetActivityPlayerData(LWOOBJID playerID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets some score value for an entity
|
|
||||||
* @param playerID the entity to set score for
|
|
||||||
* @param index the score index to set
|
|
||||||
* @param value the value to set in for that index
|
|
||||||
*/
|
|
||||||
void SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns activity score for the passed parameters
|
|
||||||
* @param playerID the entity to get score for
|
|
||||||
* @param index the index to get score for
|
|
||||||
* @return activity score for the passed parameters
|
|
||||||
*/
|
|
||||||
float_t GetActivityValue(LWOOBJID playerID, uint32_t index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes activity score tracking for some entity
|
|
||||||
* @param playerID the entity to remove score for
|
|
||||||
*/
|
|
||||||
void RemoveActivityPlayerData(LWOOBJID playerID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds activity score tracking for some entity
|
|
||||||
* @param playerID the entity to add the activity score for
|
|
||||||
* @return the created entry
|
|
||||||
*/
|
|
||||||
ActivityPlayer* AddActivityPlayerData(LWOOBJID playerID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the mapID that this activity points to
|
|
||||||
* @param mapID the map ID to set
|
|
||||||
*/
|
|
||||||
void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the LMI that this activity points to for a team size
|
|
||||||
* @param teamSize the team size to get the LMI for
|
|
||||||
* @return the LMI that this activity points to for a team size
|
|
||||||
*/
|
|
||||||
uint32_t GetLootMatrixForTeamSize(uint32_t teamSize) { return m_ActivityLootMatrices[teamSize]; }
|
|
||||||
private:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database information for this activity
|
|
||||||
*/
|
|
||||||
CDActivities m_ActivityInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All the active instances of this activity
|
|
||||||
*/
|
|
||||||
std::vector<ActivityInstance*> m_Instances;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current lobbies for this activity
|
|
||||||
*/
|
|
||||||
std::vector<Lobby*> m_Queue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All the activity score for the players in this activity
|
|
||||||
*/
|
|
||||||
std::vector<ActivityPlayer*> m_ActivityPlayers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LMIs for team sizes
|
|
||||||
*/
|
|
||||||
std::unordered_map<uint32_t, uint32_t> m_ActivityLootMatrices;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The activity id
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
int32_t m_ActivityID;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SCRIPTEDACTIVITYCOMPONENT_H
|
#endif //!__SCRIPTEDACTIVITYCOMPONENT__H__
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,880 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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<CDActivitiesTable>();
|
|
||||||
std::vector<CDActivities> 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*>(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<InventoryComponent>();
|
|
||||||
|
|
||||||
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<DestroyableComponent>();
|
|
||||||
|
|
||||||
// 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<PossessableComponent>();
|
|
||||||
|
|
||||||
if (possessableComponent != nullptr) {
|
|
||||||
possessableComponent->SetPossessor(player->GetObjectID());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the vehicle's assemblyPartLOTs for display.
|
|
||||||
auto* moduleAssemblyComponent = carEntity->GetComponent<ModuleAssemblyComponent>();
|
|
||||||
|
|
||||||
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<PossessorComponent>();
|
|
||||||
|
|
||||||
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<CharacterComponent>();
|
|
||||||
|
|
||||||
if (characterComponent != nullptr) {
|
|
||||||
characterComponent->SetIsRacing(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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_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<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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<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_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<MissionComponent>();
|
|
||||||
|
|
||||||
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*>(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<uint32_t>(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<uint16_t>(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<uint32_t>(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
outBitStream->Write0(); // No more data
|
|
||||||
}
|
|
||||||
|
|
||||||
outBitStream->Write1(); // Dirty?
|
|
||||||
|
|
||||||
outBitStream->Write(m_RemainingLaps);
|
|
||||||
|
|
||||||
outBitStream->Write(static_cast<uint16_t>(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<Player*>(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<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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<DestroyableComponent>();
|
|
||||||
|
|
||||||
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<MissionComponent>();
|
|
||||||
|
|
||||||
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<CharacterComponent>();
|
|
||||||
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";
|
|
||||||
}
|
|
@ -1,254 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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<RacingPlayerInfo> 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<LWOOBJID> 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;
|
|
||||||
};
|
|
@ -56,9 +56,8 @@
|
|||||||
#include "PetComponent.h"
|
#include "PetComponent.h"
|
||||||
#include "HavokVehiclePhysicsComponent.h"
|
#include "HavokVehiclePhysicsComponent.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "ModuleAssemblyComponent.h"
|
#include "ModuleAssemblyComponent.h"
|
||||||
#include "RacingControlComponent.h"
|
|
||||||
#include "SoundTriggerComponent.h"
|
#include "SoundTriggerComponent.h"
|
||||||
#include "ShootingGalleryComponent.h"
|
#include "ShootingGalleryComponent.h"
|
||||||
#include "RailActivatorComponent.h"
|
#include "RailActivatorComponent.h"
|
||||||
@ -71,10 +70,11 @@
|
|||||||
#include "MinigameControlComponent.h"
|
#include "MinigameControlComponent.h"
|
||||||
#include "ItemComponent.h"
|
#include "ItemComponent.h"
|
||||||
#include "DonationVendorComponent.h"
|
#include "DonationVendorComponent.h"
|
||||||
#include "GateRushControlComponent.h"
|
#include "GateRushComponent.h"
|
||||||
#include "RacingSoundTriggerComponent.h"
|
#include "RacingSoundTriggerComponent.h"
|
||||||
#include "AchievementVendorComponent.h"
|
#include "AchievementVendorComponent.h"
|
||||||
#include "MutableModelBehaviorComponent.h"
|
#include "MutableModelBehaviorComponent.h"
|
||||||
|
#include "RacingComponent.h"
|
||||||
|
|
||||||
// Table includes
|
// Table includes
|
||||||
#include "CDComponentsRegistryTable.h"
|
#include "CDComponentsRegistryTable.h"
|
||||||
@ -266,7 +266,7 @@ void Entity::Initialize() {
|
|||||||
case eReplicaComponentType::CHARACTER:
|
case eReplicaComponentType::CHARACTER:
|
||||||
AddComponent<CharacterComponent>(m_Character);
|
AddComponent<CharacterComponent>(m_Character);
|
||||||
AddComponent<MissionComponent>();
|
AddComponent<MissionComponent>();
|
||||||
AddComponent<PossessorComponent>();
|
AddComponent<PossessionComponent>();
|
||||||
AddComponent<LevelProgressionComponent>();
|
AddComponent<LevelProgressionComponent>();
|
||||||
AddComponent<PlayerForcedMovementComponent>();
|
AddComponent<PlayerForcedMovementComponent>();
|
||||||
break;
|
break;
|
||||||
@ -430,7 +430,7 @@ void Entity::Initialize() {
|
|||||||
AddComponent<RocketLaunchpadControlComponent>(componentId);
|
AddComponent<RocketLaunchpadControlComponent>(componentId);
|
||||||
break;
|
break;
|
||||||
case eReplicaComponentType::RACING_CONTROL:
|
case eReplicaComponentType::RACING_CONTROL:
|
||||||
AddComponent<RacingControlComponent>(componentId);
|
AddComponent<RacingComponent>(componentId);
|
||||||
m_IsGhostingCandidate = false;
|
m_IsGhostingCandidate = false;
|
||||||
break;
|
break;
|
||||||
case eReplicaComponentType::MISSION_OFFER:
|
case eReplicaComponentType::MISSION_OFFER:
|
||||||
@ -470,7 +470,7 @@ void Entity::Initialize() {
|
|||||||
hasProximityMonitorComponent = true;
|
hasProximityMonitorComponent = true;
|
||||||
break;
|
break;
|
||||||
case eReplicaComponentType::GATE_RUSH_CONTROL:
|
case eReplicaComponentType::GATE_RUSH_CONTROL:
|
||||||
AddComponent<GateRushControlComponent>(componentId);
|
AddComponent<GateRushComponent>(componentId);
|
||||||
break;
|
break;
|
||||||
case eReplicaComponentType::RACING_SOUND_TRIGGER:
|
case eReplicaComponentType::RACING_SOUND_TRIGGER:
|
||||||
AddComponent<RacingSoundTriggerComponent>();
|
AddComponent<RacingSoundTriggerComponent>();
|
||||||
@ -1038,11 +1038,11 @@ void Entity::Smash(const LWOOBJID source, const eKillType killType, const std::u
|
|||||||
Kill(EntityManager::Instance()->GetEntity(source));
|
Kill(EntityManager::Instance()->GetEntity(source));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto* possessorComponent = GetComponent<PossessorComponent>();
|
auto* possessionComponent = GetComponent<PossessionComponent>();
|
||||||
if (possessorComponent) {
|
if (possessionComponent) {
|
||||||
if (possessorComponent->GetPossessable() != LWOOBJID_EMPTY) {
|
if (possessionComponent->GetPossessable() != LWOOBJID_EMPTY) {
|
||||||
auto* mount = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
auto* mount = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
if (mount) possessorComponent->Dismount(mount, true);
|
if (mount) possessionComponent->Dismount(mount, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#include "CDClientManager.h"
|
#include "CDClientManager.h"
|
||||||
#include "CDSkillBehaviorTable.h"
|
#include "CDSkillBehaviorTable.h"
|
||||||
#include "SkillComponent.h"
|
#include "SkillComponent.h"
|
||||||
#include "VehicleRacingControlComponent.h"
|
#include "RacingComponent.h"
|
||||||
#include "RequestServerProjectileImpact.h"
|
#include "RequestServerProjectileImpact.h"
|
||||||
#include "SyncSkill.h"
|
#include "SyncSkill.h"
|
||||||
#include "StartSkill.h"
|
#include "StartSkill.h"
|
||||||
@ -126,8 +126,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System
|
|||||||
|
|
||||||
std::vector<Entity*> racingControllers = EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
|
std::vector<Entity*> racingControllers = EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
|
||||||
for (Entity* racingController : racingControllers) {
|
for (Entity* racingController : racingControllers) {
|
||||||
auto* vehicleRacingControlComponent = racingController->GetComponent<VehicleRacingControlComponent>();
|
auto* racingComponent = racingController->GetComponent<RacingComponent>();
|
||||||
if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnPlayerLoaded(entity);
|
if (racingComponent) racingComponent->OnPlayerLoaded(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity();
|
Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity();
|
||||||
|
@ -72,8 +72,8 @@
|
|||||||
#include "ModuleAssemblyComponent.h"
|
#include "ModuleAssemblyComponent.h"
|
||||||
#include "RenderComponent.h"
|
#include "RenderComponent.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "VehicleRacingControlComponent.h"
|
#include "RacingComponent.h"
|
||||||
#include "RailActivatorComponent.h"
|
#include "RailActivatorComponent.h"
|
||||||
#include "LevelProgressionComponent.h"
|
#include "LevelProgressionComponent.h"
|
||||||
|
|
||||||
@ -3889,9 +3889,9 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream* inStream, Entity*
|
|||||||
scriptedActivityComponent->HandleMessageBoxResponse(userEntity, GeneralUtils::UTF16ToWTF8(identifier));
|
scriptedActivityComponent->HandleMessageBoxResponse(userEntity, GeneralUtils::UTF16ToWTF8(identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* vehicleRacingControlComponent = entity->GetComponent<VehicleRacingControlComponent>();
|
auto* racingComponent = entity->GetComponent<RacingComponent>();
|
||||||
|
|
||||||
if (vehicleRacingControlComponent) vehicleRacingControlComponent->HandleMessageBoxResponse(userEntity, iButton, GeneralUtils::UTF16ToWTF8(identifier));
|
if (racingComponent) racingComponent->HandleMessageBoxResponse(userEntity, iButton, GeneralUtils::UTF16ToWTF8(identifier));
|
||||||
|
|
||||||
for (auto* shootingGallery : EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::SHOOTING_GALLERY)) {
|
for (auto* shootingGallery : EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::SHOOTING_GALLERY)) {
|
||||||
shootingGallery->OnMessageBoxResponse(userEntity, iButton, identifier, userData);
|
shootingGallery->OnMessageBoxResponse(userEntity, iButton, identifier, userData);
|
||||||
@ -4053,21 +4053,21 @@ void GameMessages::HandleDismountComplete(RakNet::BitStream* inStream, Entity* e
|
|||||||
|
|
||||||
// If we aren't possessing somethings, the don't do anything
|
// If we aren't possessing somethings, the don't do anything
|
||||||
if (objectId != LWOOBJID_EMPTY) {
|
if (objectId != LWOOBJID_EMPTY) {
|
||||||
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
auto* possessionComponent = entity->GetComponent<PossessionComponent>();
|
||||||
auto* mount = EntityManager::Instance()->GetEntity(objectId);
|
auto* mount = EntityManager::Instance()->GetEntity(objectId);
|
||||||
// make sure we have the things we need and they aren't null
|
// make sure we have the things we need and they aren't null
|
||||||
if (possessorComponent && mount) {
|
if (possessionComponent && mount) {
|
||||||
if (!possessorComponent->GetIsDismounting()) return;
|
if (!possessionComponent->GetIsDismounting()) return;
|
||||||
possessorComponent->SetIsDismounting(false);
|
possessionComponent->SetIsDismounting(false);
|
||||||
possessorComponent->SetPossessable(LWOOBJID_EMPTY);
|
possessionComponent->SetPossessable(LWOOBJID_EMPTY);
|
||||||
possessorComponent->SetPossessableType(ePossessionType::NO_POSSESSION);
|
possessionComponent->SetPossessableType(ePossessionType::NO_POSSESSION);
|
||||||
|
|
||||||
// character related things
|
// character related things
|
||||||
auto* character = entity->GetComponent<CharacterComponent>();
|
auto* character = entity->GetComponent<CharacterComponent>();
|
||||||
if (character) {
|
if (character) {
|
||||||
// If we had an active item turn it off
|
// If we had an active item turn it off
|
||||||
if (possessorComponent->GetMountItemID() != LWOOBJID_EMPTY) GameMessages::SendMarkInventoryItemAsActive(entity->GetObjectID(), false, eUnequippableActiveType::MOUNT, possessorComponent->GetMountItemID(), entity->GetSystemAddress());
|
if (possessionComponent->GetMountItemID() != LWOOBJID_EMPTY) GameMessages::SendMarkInventoryItemAsActive(entity->GetObjectID(), false, eUnequippableActiveType::MOUNT, possessionComponent->GetMountItemID(), entity->GetSystemAddress());
|
||||||
possessorComponent->SetMountItemID(LWOOBJID_EMPTY);
|
possessionComponent->SetMountItemID(LWOOBJID_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set that the controllabel phsyics comp is teleporting
|
// Set that the controllabel phsyics comp is teleporting
|
||||||
@ -4135,11 +4135,11 @@ void GameMessages::HandleRacingClientReady(RakNet::BitStream* inStream, Entity*
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* vehicleRacingControlComponent = dZoneManager::Instance()->GetZoneControlObject()->GetComponent<VehicleRacingControlComponent>();
|
auto* racingComponent = dZoneManager::Instance()->GetZoneControlObject()->GetComponent<RacingComponent>();
|
||||||
|
|
||||||
if (!vehicleRacingControlComponent) return;
|
if (!racingComponent) return;
|
||||||
|
|
||||||
vehicleRacingControlComponent->OnRacingClientReady(player);
|
racingComponent->OnRacingClientReady(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -4183,11 +4183,11 @@ void GameMessages::HandleRequestDie(RakNet::BitStream* inStream, Entity* entity,
|
|||||||
|
|
||||||
auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();
|
auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();
|
||||||
|
|
||||||
auto* vehicleRacingControlComponent = zoneController->GetComponent<VehicleRacingControlComponent>();
|
auto* racingComponent = zoneController->GetComponent<RacingComponent>();
|
||||||
|
|
||||||
Game::logger->Log("HandleRequestDie", "Got die request: %i", entity->GetLOT());
|
Game::logger->Log("HandleRequestDie", "Got die request: %i", entity->GetLOT());
|
||||||
|
|
||||||
if (!vehicleRacingControlComponent) return;
|
if (!racingComponent) return;
|
||||||
auto* possessableComponent = entity->GetComponent<PossessableComponent>();
|
auto* possessableComponent = entity->GetComponent<PossessableComponent>();
|
||||||
|
|
||||||
if (possessableComponent) {
|
if (possessableComponent) {
|
||||||
@ -4196,7 +4196,7 @@ void GameMessages::HandleRequestDie(RakNet::BitStream* inStream, Entity* entity,
|
|||||||
if (!entity) return;
|
if (!entity) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vehicleRacingControlComponent->OnRequestDie(entity);
|
racingComponent->OnRequestDie(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -4223,11 +4223,11 @@ void GameMessages::HandleRacingPlayerInfoResetFinished(RakNet::BitStream* inStre
|
|||||||
|
|
||||||
auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();
|
auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();
|
||||||
|
|
||||||
auto* vehicleRacingControlComponent = zoneController->GetComponent<VehicleRacingControlComponent>();
|
auto* racingComponent = zoneController->GetComponent<RacingComponent>();
|
||||||
|
|
||||||
Game::logger->Log("HandleRacingPlayerInfoResetFinished", "Got finished: %i", entity->GetLOT());
|
Game::logger->Log("HandleRacingPlayerInfoResetFinished", "Got finished: %i", entity->GetLOT());
|
||||||
|
|
||||||
if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRacingPlayerInfoResetFinished(player);
|
if (racingComponent) racingComponent->OnRacingPlayerInfoResetFinished(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr) {
|
void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr) {
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
#include "ProximityMonitorComponent.h"
|
#include "ProximityMonitorComponent.h"
|
||||||
#include "dpShapeSphere.h"
|
#include "dpShapeSphere.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "HavokVehiclePhysicsComponent.h"
|
#include "HavokVehiclePhysicsComponent.h"
|
||||||
#include "BuffComponent.h"
|
#include "BuffComponent.h"
|
||||||
#include "SkillComponent.h"
|
#include "SkillComponent.h"
|
||||||
@ -413,9 +413,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
|||||||
if ((chatCommand == "playanimation" || chatCommand == "playanim") && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
if ((chatCommand == "playanimation" || chatCommand == "playanim") && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||||
std::u16string anim = GeneralUtils::ASCIIToUTF16(args[0], args[0].size());
|
std::u16string anim = GeneralUtils::ASCIIToUTF16(args[0], args[0].size());
|
||||||
RenderComponent::PlayAnimation(entity, anim);
|
RenderComponent::PlayAnimation(entity, anim);
|
||||||
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
auto* possessionComponent = entity->GetComponent<PossessionComponent>();
|
||||||
if (possessorComponent) {
|
if (possessionComponent) {
|
||||||
auto* possessedComponent = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
auto* possessedComponent = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
if (possessedComponent) RenderComponent::PlayAnimation(possessedComponent, anim);
|
if (possessedComponent) RenderComponent::PlayAnimation(possessedComponent, anim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -474,7 +474,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
|||||||
controllablePhysicsComponent->SetSpeedMultiplier(boost);
|
controllablePhysicsComponent->SetSpeedMultiplier(boost);
|
||||||
|
|
||||||
// speedboost possesables
|
// speedboost possesables
|
||||||
auto* possessor = entity->GetComponent<PossessorComponent>();
|
auto* possessor = entity->GetComponent<PossessionComponent>();
|
||||||
if (possessor) {
|
if (possessor) {
|
||||||
auto possessedID = possessor->GetPossessable();
|
auto possessedID = possessor->GetPossessable();
|
||||||
if (possessedID != LWOOBJID_EMPTY) {
|
if (possessedID != LWOOBJID_EMPTY) {
|
||||||
@ -935,9 +935,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
auto* possessionComponent = entity->GetComponent<PossessionComponent>();
|
||||||
if (possessorComponent) {
|
if (possessionComponent) {
|
||||||
auto* possassableEntity = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
auto* possassableEntity = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
|
|
||||||
if (possassableEntity != nullptr) {
|
if (possassableEntity != nullptr) {
|
||||||
auto* havokVehiclePhysicsComponent = possassableEntity->GetComponent<HavokVehiclePhysicsComponent>();
|
auto* havokVehiclePhysicsComponent = possassableEntity->GetComponent<HavokVehiclePhysicsComponent>();
|
||||||
@ -962,12 +962,12 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chatCommand == "dismount" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
if (chatCommand == "dismount" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||||
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
auto* possessionComponent = entity->GetComponent<PossessionComponent>();
|
||||||
if (possessorComponent) {
|
if (possessionComponent) {
|
||||||
auto possessableId = possessorComponent->GetPossessable();
|
auto possessableId = possessionComponent->GetPossessable();
|
||||||
if (possessableId != LWOOBJID_EMPTY) {
|
if (possessableId != LWOOBJID_EMPTY) {
|
||||||
auto* possessableEntity = EntityManager::Instance()->GetEntity(possessableId);
|
auto* possessableEntity = EntityManager::Instance()->GetEntity(possessableId);
|
||||||
if (possessableEntity) possessorComponent->Dismount(possessableEntity, true);
|
if (possessableEntity) possessionComponent->Dismount(possessableEntity, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1615,13 +1615,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((chatCommand == "boost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
if ((chatCommand == "boost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||||
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
auto* possessionComponent = entity->GetComponent<PossessionComponent>();
|
||||||
|
|
||||||
if (possessorComponent == nullptr) {
|
if (possessionComponent == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* vehicle = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
auto* vehicle = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
|
|
||||||
if (vehicle == nullptr) {
|
if (vehicle == nullptr) {
|
||||||
return;
|
return;
|
||||||
@ -1647,10 +1647,10 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((chatCommand == "unboost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
if ((chatCommand == "unboost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
|
||||||
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
auto* possessionComponent = entity->GetComponent<PossessionComponent>();
|
||||||
|
|
||||||
if (possessorComponent == nullptr) return;
|
if (possessionComponent == nullptr) return;
|
||||||
auto* vehicle = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
auto* vehicle = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
|
|
||||||
if (vehicle == nullptr) return;
|
if (vehicle == nullptr) return;
|
||||||
GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#include "dZoneManager.h"
|
#include "dZoneManager.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "Zone.h"
|
#include "Zone.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "HavokVehiclePhysicsComponent.h"
|
#include "HavokVehiclePhysicsComponent.h"
|
||||||
#include "dConfig.h"
|
#include "dConfig.h"
|
||||||
@ -100,7 +100,7 @@ void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Pac
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
auto* possessorComponent = entity->GetComponent<PossessorComponent>();
|
auto* possessionComponent = entity->GetComponent<PossessionComponent>();
|
||||||
|
|
||||||
NiPoint3 position;
|
NiPoint3 position;
|
||||||
inStream.Read(position.x);
|
inStream.Read(position.x);
|
||||||
@ -165,8 +165,8 @@ void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Pac
|
|||||||
|
|
||||||
bool updateChar = true;
|
bool updateChar = true;
|
||||||
|
|
||||||
if (possessorComponent != nullptr) {
|
if (possessionComponent != nullptr) {
|
||||||
auto* possassableEntity = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
auto* possassableEntity = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
|
|
||||||
if (possassableEntity != nullptr) {
|
if (possassableEntity != nullptr) {
|
||||||
auto* possessableComponent = possassableEntity->GetComponent<PossessableComponent>();
|
auto* possessableComponent = possassableEntity->GetComponent<PossessableComponent>();
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#include "RaceMaelstromGeiser.h"
|
#include "RaceMaelstromGeiser.h"
|
||||||
#include "GameMessages.h"
|
#include "GameMessages.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "EntityManager.h"
|
#include "EntityManager.h"
|
||||||
#include "VehicleRacingControlComponent.h"
|
#include "RacingComponent.h"
|
||||||
#include "dZoneManager.h"
|
#include "dZoneManager.h"
|
||||||
|
|
||||||
void RaceMaelstromGeiser::OnStartup(Entity* self) {
|
void RaceMaelstromGeiser::OnStartup(Entity* self) {
|
||||||
@ -37,13 +37,13 @@ void RaceMaelstromGeiser::OnProximityUpdate(Entity* self, Entity* entering, std:
|
|||||||
|
|
||||||
vehicle = entering;
|
vehicle = entering;
|
||||||
} else if (entering->IsPlayer()) {
|
} else if (entering->IsPlayer()) {
|
||||||
auto* possessorComponent = entering->GetComponent<PossessorComponent>();
|
auto* possessionComponent = entering->GetComponent<PossessionComponent>();
|
||||||
|
|
||||||
if (possessorComponent == nullptr) {
|
if (possessionComponent == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vehicle = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
vehicle = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
|
|
||||||
if (vehicle == nullptr) {
|
if (vehicle == nullptr) {
|
||||||
return;
|
return;
|
||||||
@ -59,9 +59,9 @@ void RaceMaelstromGeiser::OnProximityUpdate(Entity* self, Entity* entering, std:
|
|||||||
|
|
||||||
auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();
|
auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();
|
||||||
|
|
||||||
auto* vehicleRacingControlComponent = zoneController->GetComponent<VehicleRacingControlComponent>();
|
auto* racingComponent = zoneController->GetComponent<RacingComponent>();
|
||||||
|
|
||||||
if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRequestDie(player);
|
if (racingComponent) racingComponent->OnRequestDie(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RaceMaelstromGeiser::OnTimerDone(Entity* self, std::string timerName) {
|
void RaceMaelstromGeiser::OnTimerDone(Entity* self, std::string timerName) {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
#include "ActVehicleDeathTrigger.h"
|
#include "ActVehicleDeathTrigger.h"
|
||||||
#include "PossessableComponent.h"
|
#include "PossessableComponent.h"
|
||||||
#include "GameMessages.h"
|
#include "GameMessages.h"
|
||||||
#include "VehicleRacingControlComponent.h"
|
#include "RacingComponent.h"
|
||||||
#include "dZoneManager.h"
|
#include "dZoneManager.h"
|
||||||
#include "EntityManager.h"
|
#include "EntityManager.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
|
|
||||||
|
|
||||||
void ActVehicleDeathTrigger::OnCollisionPhantom(Entity* self, Entity* target) {
|
void ActVehicleDeathTrigger::OnCollisionPhantom(Entity* self, Entity* target) {
|
||||||
@ -22,13 +22,13 @@ void ActVehicleDeathTrigger::OnCollisionPhantom(Entity* self, Entity* target) {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
} else if (target->IsPlayer()) {
|
} else if (target->IsPlayer()) {
|
||||||
auto* possessorComponent = target->GetComponent<PossessorComponent>();
|
auto* possessionComponent = target->GetComponent<PossessionComponent>();
|
||||||
|
|
||||||
if (possessorComponent == nullptr) {
|
if (possessionComponent == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vehicle = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
vehicle = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
|
|
||||||
if (vehicle == nullptr) {
|
if (vehicle == nullptr) {
|
||||||
return;
|
return;
|
||||||
@ -44,7 +44,7 @@ void ActVehicleDeathTrigger::OnCollisionPhantom(Entity* self, Entity* target) {
|
|||||||
|
|
||||||
auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();
|
auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();
|
||||||
|
|
||||||
auto* vehicleRacingControlComponent = zoneController->GetComponent<VehicleRacingControlComponent>();
|
auto* racingComponent = zoneController->GetComponent<RacingComponent>();
|
||||||
|
|
||||||
if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRequestDie(player);
|
if (racingComponent) racingComponent->OnRequestDie(player);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
#include "ShootingGalleryComponent.h"
|
#include "ShootingGalleryComponent.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "CharacterComponent.h"
|
#include "CharacterComponent.h"
|
||||||
#include "SimplePhysicsComponent.h"
|
#include "SimplePhysicsComponent.h"
|
||||||
#include "MovementAIComponent.h"
|
#include "MovementAIComponent.h"
|
||||||
@ -105,7 +105,7 @@ void SGCannon::OnActivityStateChangeRequest(Entity* self, LWOOBJID senderID, int
|
|||||||
if (characterComponent != nullptr) {
|
if (characterComponent != nullptr) {
|
||||||
characterComponent->SetIsRacing(true);
|
characterComponent->SetIsRacing(true);
|
||||||
characterComponent->SetCurrentActivity(eGameActivity::SHOOTING_GALLERY);
|
characterComponent->SetCurrentActivity(eGameActivity::SHOOTING_GALLERY);
|
||||||
auto* possessor = player->GetComponent<PossessorComponent>();
|
auto* possessor = player->GetComponent<PossessionComponent>();
|
||||||
if (possessor) {
|
if (possessor) {
|
||||||
possessor->SetPossessable(self->GetObjectID());
|
possessor->SetPossessable(self->GetObjectID());
|
||||||
possessor->SetPossessableType(ePossessionType::NO_POSSESSION);
|
possessor->SetPossessableType(ePossessionType::NO_POSSESSION);
|
||||||
@ -288,7 +288,7 @@ void SGCannon::OnActivityTimerDone(Entity* self, const std::string& name) {
|
|||||||
EntityManager::Instance()->ConstructEntity(enemy);
|
EntityManager::Instance()->ConstructEntity(enemy);
|
||||||
|
|
||||||
auto* movementAiComponent = enemy->AddComponent<MovementAIComponent>(0U);
|
auto* movementAiComponent = enemy->AddComponent<MovementAIComponent>(0U);
|
||||||
|
|
||||||
movementAiComponent->SetSpeed(toSpawn.initialSpeed);
|
movementAiComponent->SetSpeed(toSpawn.initialSpeed);
|
||||||
movementAiComponent->SetCurrentSpeed(toSpawn.initialSpeed);
|
movementAiComponent->SetCurrentSpeed(toSpawn.initialSpeed);
|
||||||
movementAiComponent->SetHaltDistance(0.0f);
|
movementAiComponent->SetHaltDistance(0.0f);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include "DestroyableComponent.h"
|
#include "DestroyableComponent.h"
|
||||||
#include "EntityManager.h"
|
#include "EntityManager.h"
|
||||||
#include "PossessorComponent.h"
|
#include "PossessionComponent.h"
|
||||||
#include "RaceImaginePowerup.h"
|
#include "RaceImaginePowerup.h"
|
||||||
#include "eRacingTaskParam.h"
|
#include "eRacingTaskParam.h"
|
||||||
#include "MissionComponent.h"
|
#include "MissionComponent.h"
|
||||||
@ -9,13 +9,13 @@
|
|||||||
void RaceImaginePowerup::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1,
|
void RaceImaginePowerup::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1,
|
||||||
int32_t param2, int32_t param3) {
|
int32_t param2, int32_t param3) {
|
||||||
if (sender->IsPlayer() && args == "powerup") {
|
if (sender->IsPlayer() && args == "powerup") {
|
||||||
auto* possessorComponent = sender->GetComponent<PossessorComponent>();
|
auto* possessionComponent = sender->GetComponent<PossessionComponent>();
|
||||||
|
|
||||||
if (possessorComponent == nullptr) {
|
if (possessionComponent == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* vehicle = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
|
auto* vehicle = EntityManager::Instance()->GetEntity(possessionComponent->GetPossessable());
|
||||||
|
|
||||||
if (vehicle == nullptr) {
|
if (vehicle == nullptr) {
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user