DarkflameServer/dGame/dComponents/MissionComponent.cpp

450 lines
12 KiB
C++
Raw Normal View History

/*
* Darkflame Universe
2023-07-06 02:32:28 +00:00
* Copyright 2023
*/
#include <string>
#include "MissionComponent.h"
#include "dLogger.h"
#include "CDClientManager.h"
#include "CDMissionTasksTable.h"
#include "InventoryComponent.h"
#include "GameMessages.h"
#include "Game.h"
#include "Amf3.h"
#include "dZoneManager.h"
#include "Mail.h"
#include "MissionPrerequisites.h"
#include "AchievementCacheKey.h"
#include "eMissionState.h"
2023-07-06 02:32:28 +00:00
#include "GeneralUtils.h"
std::unordered_map<AchievementCacheKey, std::vector<uint32_t>> MissionComponent::m_AchievementCache = {};
MissionComponent::MissionComponent(Entity* parent) : Component(parent) {
m_LastUsedMissionOrderUID = dZoneManager::Instance()->GetUniqueMissionIdStartingValue();
}
MissionComponent::~MissionComponent() {
2023-07-06 02:32:28 +00:00
for (const auto& [missionId, mission] : m_Missions) {
delete mission;
2022-07-28 13:39:57 +00:00
}
}
Mission* MissionComponent::GetMission(const uint32_t missionId) const {
2023-07-06 02:32:28 +00:00
if (m_Missions.count(missionId) == 0) return nullptr;
2022-07-28 13:39:57 +00:00
const auto& index = m_Missions.find(missionId);
2023-07-06 02:32:28 +00:00
return index == m_Missions.end() ? nullptr : index->second;
}
eMissionState MissionComponent::GetMissionState(const uint32_t missionId) const {
2022-07-28 13:39:57 +00:00
auto* mission = GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (!mission) return CanAccept(missionId) ? eMissionState::AVAILABLE : eMissionState::UNKNOWN;
2022-07-28 13:39:57 +00:00
return mission->GetMissionState();
}
bool MissionComponent::CanAccept(const uint32_t missionId) const {
2022-07-28 13:39:57 +00:00
return MissionPrerequisites::CanAccept(missionId, m_Missions);
}
void MissionComponent::AcceptMission(const uint32_t missionId, const bool skipChecks) {
2022-07-28 13:39:57 +00:00
if (!skipChecks && !CanAccept(missionId)) {
return;
}
2022-07-28 13:39:57 +00:00
// If this is a daily mission, it may already be "accepted"
auto* mission = this->GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (mission) {
2022-07-28 13:39:57 +00:00
if (mission->GetClientInfo().repeatable) {
mission->Accept();
if (mission->IsMission()) mission->SetUniqueMissionOrderID(++m_LastUsedMissionOrderUID);
2022-07-28 13:39:57 +00:00
}
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
mission = new Mission(this, missionId);
if (mission->IsMission()) mission->SetUniqueMissionOrderID(++m_LastUsedMissionOrderUID);
2022-07-28 13:39:57 +00:00
mission->Accept();
2022-07-28 13:39:57 +00:00
this->m_Missions.insert_or_assign(missionId, mission);
2023-07-06 02:32:28 +00:00
//Needs to send a mail
if (missionId == 1728) Mail::HandleNotificationRequest(m_ParentEntity->GetSystemAddress(), m_ParentEntity->GetObjectID());
}
void MissionComponent::CompleteMission(const uint32_t missionId, const bool skipChecks, const bool yieldRewards) {
2022-07-28 13:39:57 +00:00
// Get the mission first
auto* mission = this->GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (!mission) {
2022-07-28 13:39:57 +00:00
AcceptMission(missionId, skipChecks);
2022-07-28 13:39:57 +00:00
mission = this->GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (!mission) return;
2022-07-28 13:39:57 +00:00
}
2022-07-28 13:39:57 +00:00
//If this mission is not repeatable, and already completed, we stop here.
2023-07-06 02:32:28 +00:00
if (mission->IsComplete() && !mission->IsRepeatable()) return;
2022-07-28 13:39:57 +00:00
mission->Complete(yieldRewards);
}
2023-07-06 02:32:28 +00:00
void MissionComponent::RemoveMission(const uint32_t missionId) {
auto missionItr = m_Missions.find(missionId);
2023-07-06 02:32:28 +00:00
if (missionItr == m_Missions.end()) return;
2023-07-06 02:32:28 +00:00
delete missionItr->second;
2023-07-06 02:32:28 +00:00
m_Missions.erase(missionItr);
}
2023-07-06 02:32:28 +00:00
void MissionComponent::Progress(const eMissionTaskType type, const int32_t value, const LWOOBJID& associate, const std::string& targets, const int32_t count, const bool ignoreAchievements) {
for (const auto& [missionId, mission] : m_Missions) {
if (!mission) continue;
2022-07-28 13:39:57 +00:00
if (mission->IsAchievement() && ignoreAchievements) continue;
2022-07-28 13:39:57 +00:00
if (mission->IsComplete()) continue;
2022-07-28 13:39:57 +00:00
mission->Progress(type, value, associate, targets, count);
}
2022-07-28 13:39:57 +00:00
if (count > 0 && !ignoreAchievements) {
LookForAchievements(type, value, true, associate, targets, count);
}
}
void MissionComponent::ForceProgress(const uint32_t missionId, const uint32_t taskId, const int32_t value, const bool acceptMission) {
2022-07-28 13:39:57 +00:00
auto* mission = GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (!mission) {
if (!acceptMission) return;
2022-07-28 13:39:57 +00:00
AcceptMission(missionId);
2022-07-28 13:39:57 +00:00
mission = GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (!mission) return;
2022-07-28 13:39:57 +00:00
}
2023-07-06 02:32:28 +00:00
std::for_each(mission->GetTasks().begin(), mission->GetTasks().end(), [value, taskId](MissionTask* element) {
if (element->GetClientInfo().uid != taskId) return;
2022-07-28 13:39:57 +00:00
element->AddProgress(value);
2023-07-06 02:32:28 +00:00
});
2023-07-06 02:32:28 +00:00
if (!mission->IsComplete()) mission->CheckCompletion();
}
void MissionComponent::ForceProgressTaskType(const uint32_t missionId, const uint32_t taskType, const int32_t value, const bool acceptMission) {
2022-07-28 13:39:57 +00:00
auto* mission = GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (!mission) {
if (!acceptMission) return;
2022-07-28 13:39:57 +00:00
CDMissions missionInfo;
2023-07-06 02:32:28 +00:00
if (!GetMissionInfo(missionId, missionInfo)) return;
2023-07-06 02:32:28 +00:00
if (missionInfo.isMission) return;
2022-07-28 13:39:57 +00:00
AcceptMission(missionId);
2022-07-28 13:39:57 +00:00
mission = GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (!mission) return;
2022-07-28 13:39:57 +00:00
}
2023-07-06 02:32:28 +00:00
std::for_each(mission->GetTasks().begin(), mission->GetTasks().end(), [value, taskType](MissionTask* element) {
if (element->GetType() != static_cast<eMissionTaskType>(taskType)) return;
2022-07-28 13:39:57 +00:00
element->AddProgress(value);
2023-07-06 02:32:28 +00:00
});
2023-07-06 02:32:28 +00:00
if (!mission->IsComplete()) mission->CheckCompletion();
}
2023-07-06 02:32:28 +00:00
void MissionComponent::ForceProgressValue(const uint32_t missionId, const uint32_t taskType, const int32_t value, const bool acceptMission) {
2022-07-28 13:39:57 +00:00
auto* mission = GetMission(missionId);
2023-07-06 02:32:28 +00:00
if (!mission) {
2022-07-28 13:39:57 +00:00
if (!acceptMission) {
return;
}
2022-07-28 13:39:57 +00:00
CDMissions missionInfo;
2022-07-28 13:39:57 +00:00
if (!GetMissionInfo(missionId, missionInfo)) {
return;
}
2022-07-28 13:39:57 +00:00
if (missionInfo.isMission) {
return;
}
2022-07-28 13:39:57 +00:00
AcceptMission(missionId);
2022-07-28 13:39:57 +00:00
mission = GetMission(missionId);
2022-07-28 13:39:57 +00:00
if (mission == nullptr) {
return;
}
}
2022-07-28 13:39:57 +00:00
for (auto* element : mission->GetTasks()) {
if (element->GetType() != static_cast<eMissionTaskType>(taskType) || !element->InAllTargets(value)) continue;
2022-07-28 13:39:57 +00:00
element->AddProgress(1);
}
2022-07-28 13:39:57 +00:00
if (!mission->IsComplete()) {
mission->CheckCompletion();
}
}
2023-07-06 02:32:28 +00:00
bool MissionComponent::GetMissionInfo(const uint32_t missionId, CDMissions& result) const {
auto* missionsTable = CDClientManager::Instance().GetTable<CDMissionsTable>();
2022-07-28 13:39:57 +00:00
const auto missions = missionsTable->Query([=](const CDMissions& entry) {
2023-07-06 02:32:28 +00:00
return entry.id == static_cast<uint32_t>(missionId);
2022-07-28 13:39:57 +00:00
});
2022-07-28 13:39:57 +00:00
if (missions.empty()) {
return false;
}
2022-07-28 13:39:57 +00:00
result = missions[0];
2022-07-28 13:39:57 +00:00
return true;
}
2023-07-06 02:32:28 +00:00
bool MissionComponent::LookForAchievements(const eMissionTaskType type, const int32_t value, const bool progress, const LWOOBJID& associate, const std::string& targets, const int32_t count) {
2022-07-28 13:39:57 +00:00
// Query for achievments, using the cache
const auto& result = QueryAchievements(type, value, targets);
2022-07-28 13:39:57 +00:00
bool any = false;
2022-07-28 13:39:57 +00:00
for (const uint32_t missionID : result) {
// Check if we already have this achievement
2023-07-06 02:32:28 +00:00
if (GetMission(missionID)) continue;
2022-07-28 13:39:57 +00:00
// Check if we can accept this achievement
if (!MissionPrerequisites::CanAccept(missionID, m_Missions)) {
continue;
}
2022-07-28 13:39:57 +00:00
// Instantiate new mission and accept it
auto* instance = new Mission(this, missionID);
2022-07-28 13:39:57 +00:00
m_Missions.insert_or_assign(missionID, instance);
if (instance->IsMission()) instance->SetUniqueMissionOrderID(++m_LastUsedMissionOrderUID);
2022-07-28 13:39:57 +00:00
instance->Accept();
2022-07-28 13:39:57 +00:00
any = true;
2023-07-06 02:32:28 +00:00
if (!progress) continue;
// Progress mission to bring it up to speed
instance->Progress(type, value, associate, targets, count);
2022-07-28 13:39:57 +00:00
}
2022-07-28 13:39:57 +00:00
return any;
}
2023-07-06 02:32:28 +00:00
const std::vector<uint32_t>& MissionComponent::QueryAchievements(const eMissionTaskType type, const int32_t value, const std::string& targets) {
2022-07-28 13:39:57 +00:00
// Create a hash which represent this query for achievements
AchievementCacheKey toFind;
toFind.SetType(type);
toFind.SetValue(value);
toFind.SetTargets(targets);
const auto& iter = m_AchievementCache.find(toFind);
2022-07-28 13:39:57 +00:00
// Check if this query is cached
if (iter != m_AchievementCache.end()) {
return iter->second;
}
2022-07-28 13:39:57 +00:00
// Find relevent tables
auto* missionTasksTable = CDClientManager::Instance().GetTable<CDMissionTasksTable>();
auto* missionsTable = CDClientManager::Instance().GetTable<CDMissionsTable>();
2022-07-28 13:39:57 +00:00
std::vector<uint32_t> result;
2022-07-28 13:39:57 +00:00
// Loop through all mission tasks, might cache this task check later
for (const auto& task : missionTasksTable->GetEntries()) {
if (task.taskType != static_cast<uint32_t>(type)) {
continue;
}
2022-07-28 13:39:57 +00:00
// Seek the assosicated mission
auto foundMission = false;
2023-07-06 02:32:28 +00:00
const auto& cdMission = missionsTable->GetByMissionID(task.id, foundMission);
2023-07-06 02:32:28 +00:00
if (!foundMission || cdMission.isMission) continue;
2022-07-28 13:39:57 +00:00
// Compare the easy values
if (task.target == value || task.targetGroup == targets) {
2023-07-06 02:32:28 +00:00
result.push_back(cdMission.id);
2022-07-28 13:39:57 +00:00
continue;
}
2022-07-28 13:39:57 +00:00
// Compare the target group, array separated by ','
2023-07-06 02:32:28 +00:00
for (const auto& possibleMissionStr : GeneralUtils::SplitString(task.targetGroup, ',')) {
uint32_t possibleMission;
if (GeneralUtils::TryParse(possibleMissionStr, possibleMission) && possibleMission == value) {
result.push_back(cdMission.id);
break;
2022-07-28 13:39:57 +00:00
}
}
}
2023-07-06 02:32:28 +00:00
// Insert into cache and return the inserted value.
return m_AchievementCache.insert_or_assign(toFind, result).first->second;
}
bool MissionComponent::RequiresItem(const LOT lot) {
2023-07-06 02:32:28 +00:00
auto query = CDClientDatabase::CreatePreppedStmt("SELECT type FROM Objects WHERE id = ?;");
query.bind(1, static_cast<int>(lot));
2022-07-28 13:39:57 +00:00
auto result = query.execQuery();
2023-07-06 02:32:28 +00:00
if (result.eof()) return false;
2022-07-28 13:39:57 +00:00
if (!result.fieldIsNull(0)) {
const auto type = std::string(result.getStringField(0));
2023-07-06 02:32:28 +00:00
if (type == "Powerup") return true;
2022-07-28 13:39:57 +00:00
}
2023-07-06 02:32:28 +00:00
for (const auto& [missionId, mission] : m_Missions) {
if (mission->IsComplete()) continue;
2022-07-28 13:39:57 +00:00
for (auto* task : mission->GetTasks()) {
if (task->IsComplete() || task->GetType() != eMissionTaskType::GATHER) {
2022-07-28 13:39:57 +00:00
continue;
}
2023-07-06 02:32:28 +00:00
if (!task->InAllTargets(lot)) continue;
2022-07-28 13:39:57 +00:00
return true;
}
}
2023-07-06 02:32:28 +00:00
return LookForAchievements(eMissionTaskType::GATHER, lot, false);
}
void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
2023-07-06 02:32:28 +00:00
if (!doc) return;
2022-07-28 13:39:57 +00:00
auto* mis = doc->FirstChildElement("obj")->FirstChildElement("mis");
2023-07-06 02:32:28 +00:00
if (!mis) return;
2022-07-28 13:39:57 +00:00
auto* cur = mis->FirstChildElement("cur");
auto* done = mis->FirstChildElement("done");
2022-07-28 13:39:57 +00:00
auto* doneM = done->FirstChildElement();
2022-07-28 13:39:57 +00:00
while (doneM) {
2023-07-06 02:32:28 +00:00
uint32_t missionId;
2022-07-28 13:39:57 +00:00
doneM->QueryAttribute("id", &missionId);
2022-07-28 13:39:57 +00:00
auto* mission = new Mission(this, missionId);
2022-07-28 13:39:57 +00:00
mission->LoadFromXml(doneM);
2022-07-28 13:39:57 +00:00
doneM = doneM->NextSiblingElement();
2022-07-28 13:39:57 +00:00
m_Missions.insert_or_assign(missionId, mission);
}
2022-07-28 13:39:57 +00:00
auto* currentM = cur->FirstChildElement();
uint32_t missionOrder{};
2022-07-28 13:39:57 +00:00
while (currentM) {
2023-07-06 02:32:28 +00:00
uint32_t missionId;
2022-07-28 13:39:57 +00:00
currentM->QueryAttribute("id", &missionId);
2022-07-28 13:39:57 +00:00
auto* mission = new Mission(this, missionId);
2022-07-28 13:39:57 +00:00
mission->LoadFromXml(currentM);
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
mission->SetUniqueMissionOrderID(missionOrder);
2023-07-06 02:32:28 +00:00
m_LastUsedMissionOrderUID = std::max(missionOrder, m_LastUsedMissionOrderUID);
}
2022-07-28 13:39:57 +00:00
currentM = currentM->NextSiblingElement();
2022-07-28 13:39:57 +00:00
m_Missions.insert_or_assign(missionId, mission);
}
}
void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
2023-07-06 02:32:28 +00:00
if (!doc) return;
2022-07-28 13:39:57 +00:00
auto shouldInsertMis = false;
2022-07-28 13:39:57 +00:00
auto* obj = doc->FirstChildElement("obj");
2022-07-28 13:39:57 +00:00
auto* mis = obj->FirstChildElement("mis");
2023-07-06 02:32:28 +00:00
if (!mis) {
2022-07-28 13:39:57 +00:00
mis = doc->NewElement("mis");
2022-07-28 13:39:57 +00:00
shouldInsertMis = true;
}
2022-07-28 13:39:57 +00:00
mis->DeleteChildren();
2022-07-28 13:39:57 +00:00
auto* done = doc->NewElement("done");
auto* cur = doc->NewElement("cur");
2023-07-06 02:32:28 +00:00
for (const auto& [missionId, mission] : m_Missions) {
if (!mission) continue;
const auto complete = mission->IsComplete();
2023-07-06 02:32:28 +00:00
auto* missionElement = doc->NewElement("m");
2023-07-06 02:32:28 +00:00
if (!complete && mission->IsMission()) missionElement->SetAttribute("o", mission->GetUniqueMissionOrderID());
2023-07-06 02:32:28 +00:00
mission->UpdateXml(missionElement);
2023-07-06 02:32:28 +00:00
cur->LinkEndChild(missionElement);
2022-07-28 13:39:57 +00:00
}
2022-07-28 13:39:57 +00:00
mis->InsertFirstChild(done);
mis->InsertEndChild(cur);
2023-07-06 02:32:28 +00:00
if (shouldInsertMis) obj->LinkEndChild(mis);
}
2023-07-06 02:32:28 +00:00
void MissionComponent::AddCollectible(const int32_t collectibleID) {
if (!HasCollectible(collectibleID)) m_Collectibles.push_back(collectibleID);
}
2023-07-06 02:32:28 +00:00
bool MissionComponent::HasCollectible(const int32_t collectibleID) const {
2022-07-28 13:39:57 +00:00
return std::find(m_Collectibles.begin(), m_Collectibles.end(), collectibleID) != m_Collectibles.end();
}
2023-07-06 02:32:28 +00:00
bool MissionComponent::HasMission(const uint32_t missionId) const {
2022-07-28 13:39:57 +00:00
return GetMission(missionId) != nullptr;
}