Add runtime proxy actor lookup diagnostics
Introduce Stage 1 runtime lookup diagnostics for the proxy actor base: adds ResolveProxyActorBase and ResolveProxyActorBaseByPluginLocalFormId along with helper checks (IsTestPluginLoaded, IsUsableProxyActorBase, editor/fullname helpers). The code logs plugin load status, editor-ID resolution, and a plugin-local FormID fallback (0x0020A1) while refusing to spawn actors (runtime spawning remains disabled). Adds throttled and one-shot log flags to avoid spam and invokes the diagnostic early on the game-thread before placed-proxy fallback and remote-player selection. Documentation updated (docs/architecture.md and docs/dev-log.md) to record the new diagnostic behavior and next steps.
This commit is contained in:
@@ -21,6 +21,7 @@ namespace
|
||||
constexpr auto kProxyEditorId = "F4TProxyRemotePlayer01REF";
|
||||
constexpr auto kProxyBaseActorEditorId = "F4T_RemotePlayerProxy";
|
||||
constexpr auto kTestPluginName = "Fallout4Together_Test.esp";
|
||||
constexpr RE::TESFormID kProxyBaseActorLocalFormId = 0x0020A1;
|
||||
constexpr RE::TESFormID kProxyFallbackLocalFormId = 0x0020A2;
|
||||
constexpr auto kProxyOffsetX = 150.0F;
|
||||
constexpr float kProxyPositionLerpAlpha = 0.15F;
|
||||
@@ -67,6 +68,9 @@ namespace
|
||||
|
||||
RE::ObjectRefHandle g_proxyHandle;
|
||||
bool g_proxyResolvedLogged = false;
|
||||
bool g_proxyActorBaseDiagnosticStartedLogged = false;
|
||||
bool g_testPluginLoadedLogged = false;
|
||||
bool g_proxyActorBaseResolvedLogged = false;
|
||||
bool g_safetyOffsetMovedLogged = false;
|
||||
bool g_remoteStateMovementStartedLogged = false;
|
||||
bool g_remoteStateMovedLogged = false;
|
||||
@@ -246,6 +250,189 @@ namespace
|
||||
return editorId && a_editorId == editorId;
|
||||
}
|
||||
|
||||
bool IsTestPluginLoaded()
|
||||
{
|
||||
auto* dataHandler = RE::TESDataHandler::GetSingleton();
|
||||
if (!dataHandler) {
|
||||
LogThrottledWarning(
|
||||
"test_plugin_data_handler_missing",
|
||||
std::format(
|
||||
"Runtime proxy actor base lookup failed: TESDataHandler is unavailable; cannot check {} or resolve {}.",
|
||||
kTestPluginName,
|
||||
kProxyBaseActorEditorId));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* loadedMod = dataHandler->LookupLoadedModByName(kTestPluginName);
|
||||
const auto* mod = loadedMod ? loadedMod : dataHandler->LookupModByName(kTestPluginName);
|
||||
if (!mod) {
|
||||
LogThrottledWarning(
|
||||
"test_plugin_missing",
|
||||
std::format(
|
||||
"Runtime proxy actor base lookup failed: {} is not loaded; cannot resolve {}.",
|
||||
kTestPluginName,
|
||||
kProxyBaseActorEditorId));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!g_testPluginLoadedLogged) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy actor base lookup confirmed plugin loaded: {}.",
|
||||
kTestPluginName));
|
||||
g_testPluginLoadedLogged = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsUsableProxyActorBase(const RE::TESNPC& a_baseActor)
|
||||
{
|
||||
return a_baseActor.As<RE::TESBoundObject>() != nullptr;
|
||||
}
|
||||
|
||||
std::string GetEditorIdForLog(const RE::TESForm& a_form)
|
||||
{
|
||||
const auto* editorId = a_form.GetFormEditorID();
|
||||
if (!editorId || std::string_view(editorId).empty()) {
|
||||
return "unavailable";
|
||||
}
|
||||
|
||||
return editorId;
|
||||
}
|
||||
|
||||
std::string GetFullNameForLog(const RE::TESForm& a_form)
|
||||
{
|
||||
const auto fullName = RE::TESFullName::GetFullName(a_form);
|
||||
if (fullName.empty()) {
|
||||
return "unavailable";
|
||||
}
|
||||
|
||||
return std::string(fullName);
|
||||
}
|
||||
|
||||
RE::TESNPC* ResolveProxyActorBaseByPluginLocalFormId()
|
||||
{
|
||||
auto* dataHandler = RE::TESDataHandler::GetSingleton();
|
||||
if (!dataHandler) {
|
||||
LogThrottledWarning(
|
||||
"proxy_actor_base_form_id_data_handler_missing",
|
||||
std::format(
|
||||
"Runtime proxy actor base plugin-local FormID fallback failed: TESDataHandler is unavailable for {} local form {:06X}.",
|
||||
kTestPluginName,
|
||||
kProxyBaseActorLocalFormId));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* baseActor = dataHandler->LookupForm<RE::TESNPC>(kProxyBaseActorLocalFormId, kTestPluginName);
|
||||
if (!baseActor) {
|
||||
LogThrottledWarning(
|
||||
"proxy_actor_base_form_id_missing",
|
||||
std::format(
|
||||
"Runtime proxy actor base plugin-local FormID fallback failed: {} local form {:06X} did not resolve as a TESNPC.",
|
||||
kTestPluginName,
|
||||
kProxyBaseActorLocalFormId));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!IsUsableProxyActorBase(*baseActor)) {
|
||||
LogThrottledWarning(
|
||||
"proxy_actor_base_form_id_not_bound_object",
|
||||
std::format(
|
||||
"Runtime proxy actor base plugin-local FormID fallback failed: {} local form {:06X} is not usable as NEW_REFR_DATA::object.",
|
||||
kTestPluginName,
|
||||
kProxyBaseActorLocalFormId));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto editorId = GetEditorIdForLog(*baseActor);
|
||||
const auto fullName = GetFullNameForLog(*baseActor);
|
||||
const auto* formType = RE::TESForm::GetFormTypeString(baseActor->GetFormType());
|
||||
const auto editorIdMatches = editorId == kProxyBaseActorEditorId;
|
||||
if (!editorIdMatches) {
|
||||
LogThrottledWarning(
|
||||
"proxy_actor_base_form_id_editor_id_mismatch_accepted",
|
||||
std::format(
|
||||
"Runtime proxy actor base plugin-local FormID fallback editor ID check did not match: expected {}, got {}. Accepting confirmed {} local form {:06X} because it resolved as TESNPC form {:08X}.",
|
||||
kProxyBaseActorEditorId,
|
||||
editorId,
|
||||
kTestPluginName,
|
||||
kProxyBaseActorLocalFormId,
|
||||
baseActor->GetFormID()));
|
||||
}
|
||||
|
||||
if (!g_proxyActorBaseResolvedLogged) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy actor base resolved: {} by plugin-local FormID fallback. plugin={}, localFormId={:06X}, runtimeFormId={:08X}, formType={}, isTESNPC=true, editorId={}, displayName={}, accepted=true. Runtime spawning remains disabled for Stage 1.",
|
||||
kProxyBaseActorEditorId,
|
||||
kTestPluginName,
|
||||
kProxyBaseActorLocalFormId,
|
||||
baseActor->GetFormID(),
|
||||
formType ? formType : "unavailable",
|
||||
editorId,
|
||||
fullName));
|
||||
g_proxyActorBaseResolvedLogged = true;
|
||||
}
|
||||
|
||||
return baseActor;
|
||||
}
|
||||
|
||||
RE::TESNPC* ResolveProxyActorBase()
|
||||
{
|
||||
if (!g_proxyActorBaseDiagnosticStartedLogged) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy actor base lookup diagnostic running for {}.",
|
||||
kProxyBaseActorEditorId));
|
||||
g_proxyActorBaseDiagnosticStartedLogged = true;
|
||||
}
|
||||
|
||||
if (!IsTestPluginLoaded()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* baseActor = RE::TESForm::GetFormByEditorID<RE::TESNPC>(RE::BSFixedString(kProxyBaseActorEditorId));
|
||||
if (baseActor && HasEditorId(*baseActor, kProxyBaseActorEditorId) && IsUsableProxyActorBase(*baseActor)) {
|
||||
if (!g_proxyActorBaseResolvedLogged) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy actor base resolved: {} by editor ID as form {:08X}. Runtime spawning remains disabled for Stage 1.",
|
||||
kProxyBaseActorEditorId,
|
||||
baseActor->GetFormID()));
|
||||
g_proxyActorBaseResolvedLogged = true;
|
||||
}
|
||||
|
||||
return baseActor;
|
||||
}
|
||||
|
||||
auto* form = RE::TESForm::GetFormByEditorID(RE::BSFixedString(kProxyBaseActorEditorId));
|
||||
if (form) {
|
||||
LogThrottledWarning(
|
||||
"proxy_actor_base_wrong_type",
|
||||
std::format(
|
||||
"Runtime proxy actor base editor ID lookup failed: {} resolved as form {:08X}, but it is not a usable TESNPC actor base; trying plugin-local FormID fallback.",
|
||||
kProxyBaseActorEditorId,
|
||||
form->GetFormID()));
|
||||
} else {
|
||||
LogThrottledWarning(
|
||||
"proxy_actor_base_editor_id_missing",
|
||||
std::format(
|
||||
"Runtime proxy actor base editor ID lookup failed: {} could not be resolved; trying plugin-local FormID fallback.",
|
||||
kProxyBaseActorEditorId));
|
||||
}
|
||||
|
||||
baseActor = ResolveProxyActorBaseByPluginLocalFormId();
|
||||
if (baseActor) {
|
||||
return baseActor;
|
||||
}
|
||||
|
||||
LogThrottledWarning(
|
||||
"proxy_actor_base_missing",
|
||||
std::format(
|
||||
"Runtime proxy actor base lookup failed: {} was not resolved by editor ID or {} plugin-local FormID {:06X}; runtime spawning remains disabled.",
|
||||
kProxyBaseActorEditorId,
|
||||
kTestPluginName,
|
||||
kProxyBaseActorLocalFormId));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsTestCell(const RE::TESObjectCELL& a_cell)
|
||||
{
|
||||
const auto* testCell = RE::TESForm::GetFormByEditorID<RE::TESObjectCELL>(RE::BSFixedString(kTestCellEditorId));
|
||||
@@ -729,6 +916,10 @@ namespace F4T::ProxyActorController
|
||||
return;
|
||||
}
|
||||
|
||||
// Stage 1 runtime-spawn diagnostic only. Run before placed-proxy lookup and
|
||||
// before remote-player selection so it is not hidden behind visual fallback state.
|
||||
(void)ResolveProxyActorBase();
|
||||
|
||||
if constexpr (kProxyMovementMode == ProxyMovementMode::kSafetyOffsetTest) {
|
||||
auto* proxy = ResolveProxy(*parentCell, *player);
|
||||
if (!proxy || !ShouldMoveSafetyOffsetProxy()) {
|
||||
|
||||
Reference in New Issue
Block a user