F4MP/codigos originales/tiltedcode/Code/client/Services/Generic/QuestService.cpp

234 lines
9.1 KiB
C++
Raw Normal View History

#include <TiltedOnlinePCH.h>
#include <Events/ConnectedEvent.h>
#include <Services/QuestService.h>
#include <Services/ImguiService.h>
#include <PlayerCharacter.h>
#include <Forms/TESQuest.h>
#include <Games/TES.h>
#include <Games/Overrides.h>
#include <Events/EventDispatcher.h>
#include <Messages/RequestQuestUpdate.h>
#include <Messages/NotifyQuestUpdate.h>
static TESQuest* FindQuestByNameId(const String& name)
{
auto& questRegistry = ModManager::Get()->quests;
auto it = std::find_if(questRegistry.begin(), questRegistry.end(), [name](auto* it) { return std::strcmp(it->idName.AsAscii(), name.c_str()); });
return it != questRegistry.end() ? *it : nullptr;
}
QuestService::QuestService(World& aWorld, entt::dispatcher& aDispatcher)
: m_world(aWorld)
{
m_joinedConnection = aDispatcher.sink<ConnectedEvent>().connect<&QuestService::OnConnected>(this);
m_questUpdateConnection = aDispatcher.sink<NotifyQuestUpdate>().connect<&QuestService::OnQuestUpdate>(this);
// A note about the Gameevents:
// TESQuestStageItemDoneEvent gets fired to late, we instead use TESQuestStageEvent, because it responds immediately.
// TESQuestInitEvent can be instead managed by start stop quest management.
// bind game event listeners
auto* pEventList = EventDispatcherManager::Get();
pEventList->questStartStopEvent.RegisterSink(this);
pEventList->questStageEvent.RegisterSink(this);
}
void QuestService::OnConnected(const ConnectedEvent&) noexcept
{
// TODO: this should be followed with whatever the quest leader selected
/*
// deselect any active quests
auto* pPlayer = PlayerCharacter::Get();
for (auto& objective : pPlayer->objectives)
{
if (auto* pQuest = objective.instance->quest)
pQuest->SetActive(false);
}
*/
}
BSTEventResult QuestService::OnEvent(const TESQuestStartStopEvent* apEvent, const EventDispatcher<TESQuestStartStopEvent>*)
{
if (ScopedQuestOverride::IsOverriden() || !m_world.Get().GetPartyService().IsInParty())
return BSTEventResult::kOk;
spdlog::info("Quest start/stop event: {:X}", apEvent->formId);
if (TESQuest* pQuest = Cast<TESQuest>(TESForm::GetById(apEvent->formId)))
{
if (IsNonSyncableQuest(pQuest))
return BSTEventResult::kOk;
if (pQuest->type == TESQuest::Type::None || pQuest->type == TESQuest::Type::Miscellaneous)
{
// Perhaps redundant, but necessary. We need the logging and
// the lambda coming up is queued and runs later
GameId Id;
auto& modSys = m_world.GetModSystem();
if (modSys.GetServerModId(pQuest->formID, Id))
{
spdlog::info(__FUNCTION__ ": queuing type none/misc quest gameId {:X} questStage {} questStatus {} questType {} formId {:X} name {}",
Id.LogFormat(), pQuest->currentStage, pQuest->IsStopped() ? RequestQuestUpdate::Stopped : RequestQuestUpdate::Started,
static_cast<std::underlying_type_t<TESQuest::Type>>(pQuest->type),
pQuest->formID, pQuest->fullName.value.AsAscii());
}
}
m_world.GetRunner().Queue(
[&, formId = pQuest->formID, stageId = pQuest->currentStage, stopped = pQuest->IsStopped(), type = pQuest->type]()
{
GameId Id;
auto& modSys = m_world.GetModSystem();
if (modSys.GetServerModId(formId, Id))
{
RequestQuestUpdate update;
update.Id = Id;
update.Stage = stageId;
update.Status = stopped ? RequestQuestUpdate::Stopped : RequestQuestUpdate::Started;
update.ClientQuestType = static_cast<std::underlying_type_t<TESQuest::Type>>(type);
m_world.GetTransport().Send(update);
}
});
}
return BSTEventResult::kOk;
}
BSTEventResult QuestService::OnEvent(const TESQuestStageEvent* apEvent, const EventDispatcher<TESQuestStageEvent>*)
{
if (ScopedQuestOverride::IsOverriden() || !m_world.Get().GetPartyService().IsInParty())
return BSTEventResult::kOk;
spdlog::info("Quest stage event: {:X}, stage: {}", apEvent->formId, apEvent->stageId);
// there is no reason to even fetch the quest object, since the event provides everything already....
if (TESQuest* pQuest = Cast<TESQuest>(TESForm::GetById(apEvent->formId)))
{
if (IsNonSyncableQuest(pQuest))
return BSTEventResult::kOk;
if (pQuest->type == TESQuest::Type::None || pQuest->type == TESQuest::Type::Miscellaneous)
{
// Perhaps redundant, but necessary. We need the logging and
// the lambda coming up is queued and runs later
GameId Id;
auto& modSys = m_world.GetModSystem();
if (modSys.GetServerModId(pQuest->formID, Id))
{
spdlog::info(__FUNCTION__ ": queuing type none/misc quest gameId {:X} questStage {} questStatus {} questType {} formId {:X} name {}",
Id.LogFormat(), pQuest->currentStage,
RequestQuestUpdate::StageUpdate,
static_cast<std::underlying_type_t<TESQuest::Type>>(pQuest->type),
pQuest->formID, pQuest->fullName.value.AsAscii());
}
}
m_world.GetRunner().Queue(
[&, formId = apEvent->formId, stageId = apEvent->stageId, type = pQuest->type]()
{
GameId Id;
auto& modSys = m_world.GetModSystem();
if (modSys.GetServerModId(formId, Id))
{
RequestQuestUpdate update;
update.Id = Id;
update.Stage = stageId;
update.Status = RequestQuestUpdate::StageUpdate;
update.ClientQuestType = static_cast<std::underlying_type_t<TESQuest::Type>>(type);
m_world.GetTransport().Send(update);
}
});
}
return BSTEventResult::kOk;
}
void QuestService::OnQuestUpdate(const NotifyQuestUpdate& aUpdate) noexcept
{
ModSystem& modSystem = World::Get().GetModSystem();
uint32_t formId = modSystem.GetGameId(aUpdate.Id);
TESQuest* pQuest = Cast<TESQuest>(TESForm::GetById(formId));
if (!pQuest)
{
spdlog::error("Failed to find quest, base id: {:X}, mod id: {:X}", aUpdate.Id.BaseId, aUpdate.Id.ModId);
return;
}
if (pQuest->type == TESQuest::Type::None || pQuest->type == TESQuest::Type::Miscellaneous)
{
spdlog::info(__FUNCTION__ ": receiving type none/misc quest update gameId {:X} questStage {} questStatus {} questType {} formId {:X} name {}",
aUpdate.Id.LogFormat(), aUpdate.Stage, aUpdate.Status,
aUpdate.ClientQuestType, formId, pQuest->fullName.value.AsAscii());
}
bool bResult = false;
switch (aUpdate.Status)
{
case NotifyQuestUpdate::Started:
{
pQuest->ScriptSetStage(aUpdate.Stage);
pQuest->SetActive(true);
bResult = true;
spdlog::info("Remote quest started: {:X}, stage: {}", formId, aUpdate.Stage);
break;
}
case NotifyQuestUpdate::StageUpdate:
pQuest->ScriptSetStage(aUpdate.Stage);
bResult = true;
spdlog::info("Remote quest updated: {:X}, stage: {}", formId, aUpdate.Stage);
break;
case NotifyQuestUpdate::Stopped:
bResult = StopQuest(formId);
spdlog::info("Remote quest stopped: {:X}, stage: {}", formId, aUpdate.Stage);
break;
default: break;
}
if (!bResult)
spdlog::error("Failed to update the client quest state, quest: {:X}, stage: {}, status: {}", formId, aUpdate.Stage, aUpdate.Status);
}
bool QuestService::StopQuest(uint32_t aformId)
{
TESQuest* pQuest = Cast<TESQuest>(TESForm::GetById(aformId));
if (pQuest)
{
pQuest->SetActive(false);
pQuest->SetStopped();
return true;
}
return false;
}
static constexpr std::array kNonSyncableQuestIds = std::to_array<uint32_t>({
0x2BA16, // Werewolf transformation quest
0x20071D0, // Vampire transformation quest
0x3AC44, // MS13BleakFallsBarrowLeverScene
// 0xFE014801, // Unknown dynamic ID, kept as note, maybe lookup correct ID this game?
0xF2593 // Skill experience quest
});
bool QuestService::IsNonSyncableQuest(TESQuest* apQuest)
{
// Quests with no quest stages are never synced. Most TESQues::Type:: quests should
// be synced, including Type::None and Type::Miscellaneous, but there are a few
// known exceptions that should be excluded that are in the table.
return apQuest->stages.Empty()
|| std::find(kNonSyncableQuestIds.begin(), kNonSyncableQuestIds.end(), apQuest->formID) != kNonSyncableQuestIds.end();
}
void QuestService::DebugDumpQuests()
{
auto& quests = ModManager::Get()->quests;
for (TESQuest* pQuest : quests)
spdlog::info("{:X}|{}|{}|{}", pQuest->formID, (uint8_t)pQuest->type, pQuest->priority, pQuest->idName.AsAscii());
}