Add proxy visual sneak scaffolding
Introduce default-off scaffolding for proxy visual sneak experiments: add kEnableProxyVisualSneakSync and kEnableProxyVisualSneakExperimentalGraph flags (false), include ACTOR_STANCE, and add a non-mutating ApplyProxyVisualSneakIfEnabled helper that reads proxy sneak/stance/forceSneak and emits one-time diagnostics when visual sneak is disabled. Add per-slot tracking fields (hasAppliedVisualSneakingState, hasLoggedVisualSneakDisabled, lastAppliedVisualSneakingState), ResetSlotVisualSneakTracking and calls to reset tracking when holding/reusing/disconnecting slots, and switch UpdateProxyAnimationStateDebug to accept a non-const RE::Actor&. Update docs/dev-log.md with an entry describing the change. Comments/TODOs note a future, separately approved experiment to call RE::Actor::SetSneaking(...) behind a disabled gate; current scaffolding intentionally avoids mutating actors, animation graph writes/events, or AI/package crouch logic. Build tested locally (plugin rebuild).
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include "F4TNetworking.h"
|
||||
#include "F4TRemotePlayerState.h"
|
||||
|
||||
#include "RE/A/ACTOR_STANCE.h"
|
||||
#include "RE/A/ActorValue.h"
|
||||
#include "RE/C/COMMAND_TYPE.h"
|
||||
#include "RE/C/Console.h"
|
||||
@@ -71,6 +72,8 @@ namespace
|
||||
constexpr auto kRuntimeProxyVisibilityDiagnosticLogInterval = 2s;
|
||||
constexpr auto kProxyNeutralizationInterval = 2s;
|
||||
constexpr bool kEnableProxyAIMovementIntentSuppression = false;
|
||||
constexpr bool kEnableProxyVisualSneakSync = false;
|
||||
constexpr bool kEnableProxyVisualSneakExperimentalGraph = false;
|
||||
constexpr auto kNotInTestCellLogInterval = 30s;
|
||||
|
||||
using RemotePlayer = F4T::RemotePlayerState::RemotePlayerState;
|
||||
@@ -228,6 +231,8 @@ namespace
|
||||
bool reusableLogged{ false };
|
||||
bool directRemoteSpawnLogged{ false };
|
||||
bool hasLoggedInitialAnimationState{ false };
|
||||
bool hasAppliedVisualSneakingState{ false };
|
||||
bool hasLoggedVisualSneakDisabled{ false };
|
||||
std::optional<bool> lastObservedMovingState;
|
||||
std::optional<bool> lastObservedSprintingState;
|
||||
std::optional<bool> lastObservedSneakingState;
|
||||
@@ -235,6 +240,7 @@ namespace
|
||||
std::optional<bool> lastObservedWeaponDrawnState;
|
||||
std::optional<std::string> lastObservedMovementType;
|
||||
std::optional<ProxyAnimationSpeedBucket> lastObservedMovementSpeedBucket;
|
||||
std::optional<bool> lastAppliedVisualSneakingState;
|
||||
};
|
||||
|
||||
constexpr auto kProxyMovementMode = ProxyMovementMode::kRemotePlayerStateTest;
|
||||
@@ -380,7 +386,7 @@ namespace
|
||||
void UpdateProxyAnimationStateDebug(
|
||||
ProxyActorSlot& a_slot,
|
||||
const RemotePlayer& a_remotePlayer,
|
||||
const RE::Actor& a_proxy);
|
||||
RE::Actor& a_proxy);
|
||||
|
||||
const char* GetProxyNeutralizationReasonLabel(ProxyNeutralizationReason a_reason)
|
||||
{
|
||||
@@ -4060,6 +4066,13 @@ namespace
|
||||
a_slot.remoteStateMovedLogged = false;
|
||||
}
|
||||
|
||||
void ResetSlotVisualSneakTracking(ProxyActorSlot& a_slot)
|
||||
{
|
||||
a_slot.hasAppliedVisualSneakingState = false;
|
||||
a_slot.hasLoggedVisualSneakDisabled = false;
|
||||
a_slot.lastAppliedVisualSneakingState.reset();
|
||||
}
|
||||
|
||||
void ResetSlotObservationState(ProxyActorSlot& a_slot)
|
||||
{
|
||||
a_slot.hasLoggedInitialAnimationState = false;
|
||||
@@ -4070,6 +4083,7 @@ namespace
|
||||
a_slot.lastObservedWeaponDrawnState.reset();
|
||||
a_slot.lastObservedMovementType.reset();
|
||||
a_slot.lastObservedMovementSpeedBucket.reset();
|
||||
ResetSlotVisualSneakTracking(a_slot);
|
||||
}
|
||||
|
||||
bool HasSlotHeldSinceTime(const ProxyActorSlot& a_slot)
|
||||
@@ -4130,6 +4144,9 @@ namespace
|
||||
}
|
||||
|
||||
a_proxy.SetPosition(GetSlotHoldingPosition(a_slot), true);
|
||||
// TODO: If a later approved visual writer is added, clear any applied
|
||||
// proxy sneak visual here before holding/reusing the actor.
|
||||
ResetSlotVisualSneakTracking(a_slot);
|
||||
if (auto* player = RE::PlayerCharacter::GetSingleton()) {
|
||||
NeutralizeProxyActor(a_slot, a_proxy, *player, ProxyNeutralizationReason::kHolding);
|
||||
}
|
||||
@@ -4198,6 +4215,7 @@ namespace
|
||||
slot.lifecycleState = ProxyLifecycleState::kReusable;
|
||||
slot.holdReason.reset();
|
||||
slot.previousRemotePlayerId = playerId;
|
||||
ResetSlotVisualSneakTracking(slot);
|
||||
if (!slot.reusableLogged) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy slot for disconnected remote player {} became reusable after {:.1f}s held.",
|
||||
@@ -4247,10 +4265,66 @@ namespace
|
||||
ProxyMovementDiagnosticMode::kRestore);
|
||||
}
|
||||
|
||||
void ApplyProxyVisualSneakIfEnabled(
|
||||
ProxyActorSlot& a_slot,
|
||||
RE::Actor& a_proxy,
|
||||
const RemotePlayer& a_remotePlayer)
|
||||
{
|
||||
if constexpr (!kEnableProxyVisualSneakSync && !kEnableProxyVisualSneakExperimentalGraph) {
|
||||
if (a_slot.hasLoggedVisualSneakDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto proxyIsSneaking = a_proxy.IsSneaking();
|
||||
const auto proxyStance = static_cast<std::uint32_t>(a_proxy.stance);
|
||||
const auto proxyForceSneak = a_proxy.forceSneak != 0;
|
||||
const auto proxyStanceIsSneaking =
|
||||
proxyStance == static_cast<std::uint32_t>(RE::ACTOR_STANCE::kSneaking);
|
||||
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy visual sneak sync disabled for remote player {}: actor={:08X}, requestedSneaking={}, proxyIsSneaking={}, proxyStance={}, proxyStanceIsSneaking={}, proxyForceSneak={}. No visual sneak method is enabled.",
|
||||
a_remotePlayer.playerId,
|
||||
a_proxy.GetFormID(),
|
||||
a_remotePlayer.isSneaking,
|
||||
proxyIsSneaking,
|
||||
proxyStance,
|
||||
proxyStanceIsSneaking,
|
||||
proxyForceSneak));
|
||||
a_slot.hasLoggedVisualSneakDisabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (kEnableProxyVisualSneakExperimentalGraph) {
|
||||
if (!a_slot.hasLoggedVisualSneakDisabled) {
|
||||
LogWarningWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy visual sneak experimental graph flag is enabled for remote player {}, but graph writes/events are intentionally not implemented in this scaffolding pass. actor={:08X}.",
|
||||
a_remotePlayer.playerId,
|
||||
a_proxy.GetFormID()));
|
||||
a_slot.hasLoggedVisualSneakDisabled = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (kEnableProxyVisualSneakSync) {
|
||||
if (!a_slot.hasLoggedVisualSneakDisabled) {
|
||||
LogWarningWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy visual sneak sync flag is enabled for remote player {}, but no actor-mutating sneak writer is implemented in this scaffolding pass. actor={:08X}.",
|
||||
a_remotePlayer.playerId,
|
||||
a_proxy.GetFormID()));
|
||||
a_slot.hasLoggedVisualSneakDisabled = true;
|
||||
}
|
||||
// TODO: In a separately approved experiment, consider calling
|
||||
// RE::Actor::SetSneaking(a_remotePlayer.isSneaking) here behind this
|
||||
// flag, with explicit clear handling on false, disconnect, hold,
|
||||
// reassignment, and reuse. Do not add graph writes/events,
|
||||
// PerformAction, ActorState writes, or package/procedure crouch logic.
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateProxyAnimationStateDebug(
|
||||
ProxyActorSlot& a_slot,
|
||||
const RemotePlayer& a_remotePlayer,
|
||||
const RE::Actor& a_proxy)
|
||||
RE::Actor& a_proxy)
|
||||
{
|
||||
const auto speedBucket = GetProxyAnimationSpeedBucket(a_remotePlayer);
|
||||
const auto shouldLogInitial = !a_slot.hasLoggedInitialAnimationState;
|
||||
@@ -4289,6 +4363,8 @@ namespace
|
||||
a_slot.lastObservedMovementType = a_remotePlayer.movementType;
|
||||
a_slot.lastObservedMovementSpeedBucket = speedBucket;
|
||||
|
||||
ApplyProxyVisualSneakIfEnabled(a_slot, a_proxy, a_remotePlayer);
|
||||
|
||||
// TODO: Future visual milestones should investigate safe sneak/crouch
|
||||
// application, weapon drawn visuals, locomotion graph behavior, jump
|
||||
// animation behavior, and read-only research for animation graph
|
||||
|
||||
Reference in New Issue
Block a user