F4MP/codigos originales/tiltedcode/Code/client/Services/Generic/DiscoveryService.cpp
2026-01-06 18:53:59 +01:00

298 lines
8.8 KiB
C++

#include <TiltedOnlinePCH.h>
#include <Services/DiscoveryService.h>
#include <Games/TES.h>
#include <Games/References.h>
#include <Forms/TESObjectCELL.h>
#include <Forms/TESWorldSpace.h>
#include <Forms/TESNPC.h>
#include <Events/ActorAddedEvent.h>
#include <Events/ActorRemovedEvent.h>
#include <Events/PreUpdateEvent.h>
#include <Events/GridCellChangeEvent.h>
#include <Events/CellChangeEvent.h>
#include <Events/LocationChangeEvent.h>
#include <Events/ConnectedEvent.h>
#include <Events/ConnectionErrorEvent.h>
#include <World.h>
DiscoveryService::DiscoveryService(World& aWorld, entt::dispatcher& aDispatcher) noexcept
: m_world(aWorld)
, m_dispatcher(aDispatcher)
{
m_preUpdateConnection = m_dispatcher.sink<PreUpdateEvent>().connect<&DiscoveryService::OnUpdate>(this);
m_connectedConnection = m_dispatcher.sink<ConnectedEvent>().connect<&DiscoveryService::OnConnected>(this);
EventDispatcherManager::Get()->loadGameEvent.RegisterSink(this);
}
void DiscoveryService::VisitCell(bool aForceTrigger) noexcept
{
const PlayerCharacter* pPlayer = PlayerCharacter::Get();
if (!pPlayer)
return;
if (pPlayer->GetWorldSpace())
VisitExteriorCell(aForceTrigger);
else if (pPlayer->GetParentCell())
VisitInteriorCell(aForceTrigger);
// exactly how the game does it too
if (m_pLocation != pPlayer->locationForm)
{
m_dispatcher.trigger(LocationChangeEvent());
m_pLocation = pPlayer->locationForm;
}
}
void DiscoveryService::VisitExteriorCell(bool aForceTrigger) noexcept
{
const PlayerCharacter* pPlayer = PlayerCharacter::Get();
const auto pWorldSpace = pPlayer->GetWorldSpace();
m_interiorCellId = 0;
const TES* pTES = TES::Get();
const uint32_t worldSpaceId = pWorldSpace->formID;
const GridCellCoords gameCurrentGrid(pTES->currentGridX, pTES->currentGridY);
const GridCellCoords gameCenterGrid(pTES->centerGridX, pTES->centerGridY);
if (m_worldSpaceId != worldSpaceId || aForceTrigger)
{
DetectGridCellChange(pWorldSpace, true);
// If the world space changes, then we want to send out a CellChangeEvent out too.
aForceTrigger = true;
}
else if (gameCenterGrid != m_centerGrid)
{
DetectGridCellChange(pWorldSpace, false);
}
if (gameCurrentGrid != m_currentGrid || aForceTrigger)
{
CellChangeEvent cellChangeEvent{};
if (!m_world.GetModSystem().GetServerModId(pWorldSpace->formID, cellChangeEvent.WorldSpaceId))
{
spdlog::error("Failed to find world space id for form id {:X}", pWorldSpace->formID);
return;
}
TESObjectCELL* pCell = pPlayer->GetParentCellEx();
if (!pCell)
pCell = ModManager::Get()->GetCellFromCoordinates(gameCurrentGrid.X, gameCurrentGrid.Y, pWorldSpace, false);
if (!m_world.GetModSystem().GetServerModId(pCell->formID, cellChangeEvent.CellId))
{
spdlog::error("Failed to find cell id for form id {:X}", pCell->formID);
return;
}
cellChangeEvent.CurrentCoords = gameCurrentGrid;
m_dispatcher.trigger(cellChangeEvent);
m_currentGrid = gameCurrentGrid;
}
}
void DiscoveryService::VisitInteriorCell(bool aForceTrigger) noexcept
{
ResetCachedCellData();
const uint32_t cellId = PlayerCharacter::Get()->GetParentCell()->formID;
if (m_interiorCellId != cellId || aForceTrigger)
{
CellChangeEvent cellChangeEvent{};
if (!m_world.GetModSystem().GetServerModId(cellId, cellChangeEvent.CellId))
{
spdlog::error("Failed to find cell id {:X}", cellId);
return;
}
m_dispatcher.trigger(cellChangeEvent);
m_interiorCellId = cellId;
}
}
void DiscoveryService::DetectGridCellChange(TESWorldSpace* aWorldSpace, bool aNewCellGrid) noexcept
{
GridCellChangeEvent changeEvent{};
const uint32_t worldSpaceId = aWorldSpace->formID;
changeEvent.WorldSpaceId = worldSpaceId;
const TES* pTES = TES::Get();
const int32_t startGridX = pTES->centerGridX - GridCellCoords::m_gridsToLoad / 2;
const int32_t startGridY = pTES->centerGridY - GridCellCoords::m_gridsToLoad / 2;
for (int32_t i = 0; i < GridCellCoords::m_gridsToLoad; ++i)
{
for (int32_t j = 0; j < GridCellCoords::m_gridsToLoad; ++j)
{
// If it is a new cell grid, don't check for previously loaded cells.
if (!aNewCellGrid)
{
if (GridCellCoords::IsCellInGridCell(m_centerGrid, {startGridX + i, startGridY + j}, false))
continue;
}
const TESObjectCELL* pCell = ModManager::Get()->GetCellFromCoordinates(startGridX + i, startGridY + j, aWorldSpace, 0);
if (!pCell)
{
spdlog::warn("Cell not found at coordinates ({}, {}) in worldspace {:X}", startGridX + i, startGridY + j, aWorldSpace->formID);
continue;
}
GameId cellId{};
if (!m_world.GetModSystem().GetServerModId(pCell->formID, cellId))
{
spdlog::error("Failed to find cell id for form id {:X}", pCell->formID);
continue;
}
changeEvent.Cells.push_back(cellId);
}
}
TESObjectCELL* pCell = PlayerCharacter::Get()->GetParentCellEx();
if (!pCell)
pCell = ModManager::Get()->GetCellFromCoordinates(pTES->currentGridX, pTES->currentGridY, aWorldSpace, false);
if (!m_world.GetModSystem().GetServerModId(pCell->formID, changeEvent.PlayerCell))
{
spdlog::error("Failed to find cell id for form id {:X}", pCell->formID);
return;
}
changeEvent.CenterCoords = m_centerGrid = {pTES->centerGridX, pTES->centerGridY};
m_dispatcher.trigger(changeEvent);
m_worldSpaceId = worldSpaceId;
}
void DiscoveryService::VisitForms() noexcept
{
static Set<uint32_t> s_previousForms;
s_previousForms = m_forms;
const auto visitor = [this](TESObjectREFR* apReference)
{
const auto formId = apReference->formID;
if (!m_forms.count(formId))
{
m_forms.insert(formId);
m_dispatcher.enqueue(ActorAddedEvent(formId));
}
else
s_previousForms.erase(formId);
};
ProcessLists* const pProcessLists = ProcessLists::Get();
if (!pProcessLists)
return;
for (uint32_t i = 0; i < pProcessLists->highActorHandleArray.length; ++i)
{
TESObjectREFR* const pRefr = TESObjectREFR::GetByHandle(pProcessLists->highActorHandleArray[i]);
if (pRefr)
{
if (pRefr->GetNiNode())
{
visitor(pRefr);
}
}
}
// Not in actor holder
visitor(PlayerCharacter::Get());
// We dispatch removal events first to prevent needless reallocations
for (uint32_t formId : s_previousForms)
{
m_dispatcher.trigger(ActorRemovedEvent(formId));
m_forms.erase(formId);
}
// Dispatch all adds
m_dispatcher.update<ActorAddedEvent>();
}
void DiscoveryService::OnUpdate(const PreUpdateEvent& acUpdateEvent) noexcept
{
TP_UNUSED(acUpdateEvent);
VisitCell();
VisitForms();
}
void DiscoveryService::OnConnected(const ConnectedEvent& acEvent) noexcept
{
// uGridsToLoad should always be 5, as this is what the server enforces
auto* pSetting = INISettingCollection::Get()->GetSetting("uGridsToLoad:General");
if (pSetting && pSetting->data != 5)
{
ConnectionErrorEvent errorEvent{};
errorEvent.ErrorDetail = "{\"error\": \"bad_uGridsToLoad\"}";
m_world.GetRunner().Trigger(errorEvent);
}
VisitCell(true);
}
BSTEventResult DiscoveryService::OnEvent(const TESLoadGameEvent*, const EventDispatcher<TESLoadGameEvent>*)
{
spdlog::info("Finished loading, triggering visit cell");
const TiltedPhoques::String defaultModlist[7] = {"Skyrim.esm", "Update.esm", "Dawnguard.esm",
"HearthFires.esm", "Dragonborn.esm", "_ResourcePack.esl",
"SkyrimTogether.esp"};
auto& currentModlist = ModManager::Get()->mods;
bool isModlistEqual = currentModlist.Size() == 7;
if (isModlistEqual)
{
int i = 0;
for (const auto& currentMod : currentModlist)
{
if (currentMod->filename != defaultModlist[i])
{
isModlistEqual = false;
break;
}
i++;
}
}
if (!isModlistEqual)
{
ConnectionErrorEvent errorEvent{};
errorEvent.ErrorDetail = "{\"error\": \"non_default_install\"}";
m_world.GetRunner().Trigger(errorEvent);
}
VisitCell(true);
return BSTEventResult::kOk;
}
void DiscoveryService::ResetCachedCellData() noexcept
{
m_worldSpaceId = 0;
m_centerGrid.Reset();
m_currentGrid.Reset();
}