mirror of
https://github.com/Jous99/F4MP.git
synced 2026-01-13 00:00:54 +01:00
298 lines
8.8 KiB
C++
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();
|
|
}
|