Add Pip-Boy state to transform sync
Adds optional `isPipboyOpen` to transform packets, remote player state, proxy animation logging, and fake client/player samples. Updates protocol and architecture docs to describe the new Pip-Boy open/raising/closing state and related movement-state handling.
This commit is contained in:
@@ -30,6 +30,7 @@ namespace F4T::Networking
|
||||
bool a_isJumping = false,
|
||||
bool a_isCrouching = false,
|
||||
bool a_weaponDrawn = false,
|
||||
bool a_isPipboyOpen = false,
|
||||
float a_movementSpeed = 0.0F,
|
||||
float a_animationGraphSpeed = -1.0F,
|
||||
std::span<const F4T::RemotePlayerState::RemoteEquippedItem> a_equippedItems = {},
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace F4T::ProxyAnimationSync
|
||||
bool lastIsJumping{ false };
|
||||
bool lastIsCrouching{ false };
|
||||
bool lastWeaponDrawn{ false };
|
||||
bool lastIsPipboyOpen{ false };
|
||||
float lastGraphSpeed{ 0.0F };
|
||||
float lastDirection{ 0.0F };
|
||||
std::optional<LocomotionTier> lastLocomotionTier{};
|
||||
|
||||
@@ -80,6 +80,7 @@ namespace F4T::RemotePlayerState
|
||||
bool isJumping{ false };
|
||||
bool isCrouching{ false };
|
||||
bool weaponDrawn{ false };
|
||||
bool isPipboyOpen{ false };
|
||||
float movementSpeed{ 0.0F };
|
||||
float animationGraphSpeed{ -1.0F };
|
||||
bool hasEquipmentUpdate{ false };
|
||||
|
||||
@@ -723,6 +723,7 @@ namespace
|
||||
ReadBool(a_packet, "isJumping").value_or(false),
|
||||
ReadBool(a_packet, "isCrouching").value_or(false),
|
||||
ReadBool(a_packet, "weaponDrawn").value_or(false),
|
||||
ReadBool(a_packet, "isPipboyOpen").value_or(false),
|
||||
static_cast<float>(movementSpeed),
|
||||
static_cast<float>(animationGraphSpeed),
|
||||
hasEquipmentUpdate,
|
||||
@@ -755,7 +756,7 @@ namespace
|
||||
|
||||
if (ShouldLogRemoteMovementState(*playerId)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Remote player {} movement state: moving={}, sprinting={}, sneaking={}, jumping={}, crouching={}, weaponDrawn={}, speed={:.1f}, flags1={:08X}, flags2={:08X}",
|
||||
"Remote player {} movement state: moving={}, sprinting={}, sneaking={}, jumping={}, crouching={}, weaponDrawn={}, pipboyOpen={}, speed={:.1f}, flags1={:08X}, flags2={:08X}",
|
||||
remoteState.playerId,
|
||||
remoteState.isMoving,
|
||||
remoteState.isSprinting,
|
||||
@@ -763,6 +764,7 @@ namespace
|
||||
remoteState.isJumping,
|
||||
remoteState.isCrouching,
|
||||
remoteState.weaponDrawn,
|
||||
remoteState.isPipboyOpen,
|
||||
remoteState.movementSpeed,
|
||||
remoteState.actorStateFlags1,
|
||||
remoteState.actorStateFlags2));
|
||||
@@ -1083,6 +1085,7 @@ namespace F4T::Networking
|
||||
bool a_isJumping,
|
||||
bool a_isCrouching,
|
||||
bool a_weaponDrawn,
|
||||
bool a_isPipboyOpen,
|
||||
float a_movementSpeed,
|
||||
float a_animationGraphSpeed,
|
||||
std::span<const F4T::RemotePlayerState::RemoteEquippedItem> a_equippedItems,
|
||||
@@ -1109,6 +1112,7 @@ namespace F4T::Networking
|
||||
{ "isJumping", a_isJumping },
|
||||
{ "isCrouching", a_isCrouching },
|
||||
{ "weaponDrawn", a_weaponDrawn },
|
||||
{ "isPipboyOpen", a_isPipboyOpen },
|
||||
{ "movementSpeed", a_movementSpeed }
|
||||
};
|
||||
|
||||
|
||||
@@ -360,6 +360,7 @@ namespace
|
||||
std::optional<bool> lastObservedSneakingState;
|
||||
std::optional<bool> lastObservedJumpingState;
|
||||
std::optional<bool> lastObservedWeaponDrawnState;
|
||||
std::optional<bool> lastObservedPipboyOpenState;
|
||||
std::optional<std::string> lastObservedMovementType;
|
||||
std::optional<std::string> lastObservedRemoteCellId;
|
||||
std::optional<ProxyAnimationSpeedBucket> lastObservedMovementSpeedBucket;
|
||||
@@ -6052,6 +6053,7 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
||||
a_slot.lastObservedSneakingState.reset();
|
||||
a_slot.lastObservedJumpingState.reset();
|
||||
a_slot.lastObservedWeaponDrawnState.reset();
|
||||
a_slot.lastObservedPipboyOpenState.reset();
|
||||
a_slot.lastObservedMovementType.reset();
|
||||
a_slot.lastObservedMovementSpeedBucket.reset();
|
||||
ResetSlotVisualSneakTracking(a_slot);
|
||||
@@ -6462,12 +6464,13 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
||||
!a_slot.lastObservedSneakingState || *a_slot.lastObservedSneakingState != a_remotePlayer.isSneaking ||
|
||||
!a_slot.lastObservedJumpingState || *a_slot.lastObservedJumpingState != a_remotePlayer.isJumping ||
|
||||
!a_slot.lastObservedWeaponDrawnState || *a_slot.lastObservedWeaponDrawnState != a_remotePlayer.weaponDrawn ||
|
||||
!a_slot.lastObservedPipboyOpenState || *a_slot.lastObservedPipboyOpenState != a_remotePlayer.isPipboyOpen ||
|
||||
!a_slot.lastObservedMovementType || *a_slot.lastObservedMovementType != a_remotePlayer.movementType ||
|
||||
!a_slot.lastObservedMovementSpeedBucket || *a_slot.lastObservedMovementSpeedBucket != speedBucket;
|
||||
|
||||
if (shouldLogInitial || changed) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy animation state {} for remote player {}: actor={:08X}, holdingIndex={}, moving={}, speed={:.1f}, speedBucket={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, movementType={}.",
|
||||
"Runtime proxy animation state {} for remote player {}: actor={:08X}, holdingIndex={}, moving={}, speed={:.1f}, speedBucket={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, pipboyOpen={}, movementType={}.",
|
||||
shouldLogInitial ? "initial" : "changed",
|
||||
a_remotePlayer.playerId,
|
||||
a_proxy.GetFormID(),
|
||||
@@ -6479,6 +6482,7 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
||||
a_remotePlayer.isSneaking,
|
||||
a_remotePlayer.isJumping,
|
||||
a_remotePlayer.weaponDrawn,
|
||||
a_remotePlayer.isPipboyOpen,
|
||||
a_remotePlayer.movementType));
|
||||
}
|
||||
|
||||
@@ -6488,6 +6492,7 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
||||
a_slot.lastObservedSneakingState = a_remotePlayer.isSneaking;
|
||||
a_slot.lastObservedJumpingState = a_remotePlayer.isJumping;
|
||||
a_slot.lastObservedWeaponDrawnState = a_remotePlayer.weaponDrawn;
|
||||
a_slot.lastObservedPipboyOpenState = a_remotePlayer.isPipboyOpen;
|
||||
a_slot.lastObservedMovementType = a_remotePlayer.movementType;
|
||||
a_slot.lastObservedMovementSpeedBucket = speedBucket;
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "RE/T/TESObjectREFR.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
@@ -257,6 +256,7 @@ namespace
|
||||
bool isJumping{ false };
|
||||
bool isCrouching{ false };
|
||||
bool weaponDrawn{ false };
|
||||
bool isPipboyOpen{ false };
|
||||
float graphSpeed{ 0.0F };
|
||||
float direction{ 0.0F };
|
||||
};
|
||||
@@ -273,6 +273,7 @@ namespace
|
||||
desired.isJumping = a_remotePlayer.isJumping;
|
||||
desired.isCrouching = a_remotePlayer.isCrouching;
|
||||
desired.weaponDrawn = a_remotePlayer.weaponDrawn;
|
||||
desired.isPipboyOpen = a_remotePlayer.isPipboyOpen;
|
||||
desired.graphSpeed = ComputeGraphSpeed(a_remotePlayer);
|
||||
desired.direction = a_remotePlayer.angleZ;
|
||||
return desired;
|
||||
@@ -333,13 +334,18 @@ namespace
|
||||
const auto currentTier =
|
||||
F4T::ProxyAnimationSync::ComputeLocomotionTier(a_desired.graphSpeed, a_desired.isSprinting);
|
||||
|
||||
const auto locomotionTierChanged =
|
||||
a_desired.isMoving &&
|
||||
(!a_state.lastLocomotionTier || *a_state.lastLocomotionTier != currentTier);
|
||||
|
||||
return a_state.lastIsMoving != a_desired.isMoving ||
|
||||
a_state.lastIsSprinting != a_desired.isSprinting ||
|
||||
a_state.lastIsSneaking != a_desired.isSneaking ||
|
||||
a_state.lastIsJumping != a_desired.isJumping ||
|
||||
a_state.lastIsCrouching != a_desired.isCrouching ||
|
||||
a_state.lastWeaponDrawn != a_desired.weaponDrawn ||
|
||||
!a_state.lastLocomotionTier || *a_state.lastLocomotionTier != currentTier ||
|
||||
a_state.lastIsPipboyOpen != a_desired.isPipboyOpen ||
|
||||
locomotionTierChanged ||
|
||||
AreFloatsDifferent(a_state.lastGraphSpeed, a_desired.graphSpeed) ||
|
||||
AreFloatsDifferent(a_state.lastDirection, a_desired.direction);
|
||||
}
|
||||
@@ -630,7 +636,7 @@ namespace
|
||||
}
|
||||
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy animation sync {} for remote player {}: actor={:08X}, moving={}, tier={}, sprinting={}, sneaking={}, jumping={}, crouching={}, weaponDrawn={}, graphSpeed={:.1f}, direction={:.3f}.",
|
||||
"Runtime proxy animation sync {} for remote player {}: actor={:08X}, moving={}, tier={}, sprinting={}, sneaking={}, jumping={}, crouching={}, weaponDrawn={}, pipboyOpen={}, graphSpeed={:.1f}, direction={:.3f}.",
|
||||
a_initial ? "initial" : "changed",
|
||||
a_remotePlayerId,
|
||||
a_actorId,
|
||||
@@ -641,6 +647,7 @@ namespace
|
||||
a_desired.isJumping,
|
||||
a_desired.isCrouching,
|
||||
a_desired.weaponDrawn,
|
||||
a_desired.isPipboyOpen,
|
||||
a_desired.graphSpeed,
|
||||
a_desired.direction));
|
||||
}
|
||||
@@ -735,11 +742,12 @@ namespace F4T::ProxyAnimationSync
|
||||
const auto haveSpeedSmoothed =
|
||||
diagHolder->GetGraphVariableImplFloat(RE::BSFixedString{ F4T::ProxyAnimationSync::GraphVar::kSpeedSmoothed }, readSpeedSmoothed);
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Proxy anim diag p{} actor={:08X}: recvMoving={}, recvSprint={}, recvMovementSpeed={:.1f}, recvAnimGraphSpeed={:.1f}, computedGraphSpeed={:.1f}, graphSpeedReadBack={}({:.1f}), speedSmoothedReadBack={}({:.1f}).",
|
||||
"Proxy anim diag p{} actor={:08X}: recvMoving={}, recvSprint={}, recvPipboyOpen={}, recvMovementSpeed={:.1f}, recvAnimGraphSpeed={:.1f}, computedGraphSpeed={:.1f}, graphSpeedReadBack={}({:.1f}), speedSmoothedReadBack={}({:.1f}).",
|
||||
a_remotePlayer.playerId,
|
||||
actorId,
|
||||
a_remotePlayer.isMoving,
|
||||
a_remotePlayer.isSprinting,
|
||||
a_remotePlayer.isPipboyOpen,
|
||||
a_remotePlayer.movementSpeed,
|
||||
a_remotePlayer.animationGraphSpeed,
|
||||
desired.graphSpeed,
|
||||
@@ -780,6 +788,7 @@ namespace F4T::ProxyAnimationSync
|
||||
a_state.lastIsJumping = desired.isJumping;
|
||||
a_state.lastIsCrouching = desired.isCrouching;
|
||||
a_state.lastWeaponDrawn = desired.weaponDrawn;
|
||||
a_state.lastIsPipboyOpen = desired.isPipboyOpen;
|
||||
a_state.lastGraphSpeed = desired.graphSpeed;
|
||||
a_state.lastDirection = desired.direction;
|
||||
if (desired.isJumping) {
|
||||
|
||||
+30
-1
@@ -14,6 +14,9 @@
|
||||
#include "RE/B/BipedAnim.h"
|
||||
#include "RE/B/BSFixedString.h"
|
||||
#include "RE/I/IAnimationGraphManagerHolder.h"
|
||||
#include "RE/P/PipboyManager.h"
|
||||
#include "RE/P/PipboyMenu.h"
|
||||
#include "RE/U/UI.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
@@ -51,6 +54,7 @@ namespace
|
||||
bool isJumping;
|
||||
bool isCrouching;
|
||||
bool weaponDrawn;
|
||||
bool isPipboyOpen;
|
||||
float movementSpeed;
|
||||
};
|
||||
|
||||
@@ -422,6 +426,22 @@ namespace
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLocalPipboyOpen()
|
||||
{
|
||||
if (const auto* ui = RE::UI::GetSingleton(); ui && ui->GetMenuOpen<RE::PipboyMenu>()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto* pipboyManager = RE::PipboyManager::GetSingleton()) {
|
||||
return pipboyManager->QPipboyActive() ||
|
||||
pipboyManager->pipboyOpening ||
|
||||
pipboyManager->pipboyClosing ||
|
||||
pipboyManager->pipboyRaising;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShouldLogSneakDetection(
|
||||
const SneakDetectionState& a_sneakDetection,
|
||||
std::chrono::steady_clock::time_point a_currentTime)
|
||||
@@ -512,6 +532,7 @@ namespace
|
||||
heldJumping,
|
||||
GetCrouchState(a_player),
|
||||
a_player.GetWeaponMagicDrawn(),
|
||||
IsLocalPipboyOpen(),
|
||||
a_movementSpeed
|
||||
};
|
||||
}
|
||||
@@ -545,7 +566,8 @@ namespace
|
||||
a_previous.isSneaking != a_current.isSneaking ||
|
||||
a_previous.isJumping != a_current.isJumping ||
|
||||
a_previous.isCrouching != a_current.isCrouching ||
|
||||
a_previous.weaponDrawn != a_current.weaponDrawn;
|
||||
a_previous.weaponDrawn != a_current.weaponDrawn ||
|
||||
a_previous.isPipboyOpen != a_current.isPipboyOpen;
|
||||
}
|
||||
|
||||
bool HasMeaningfulTransformDelta(const PlayerTransform& a_previous, const PlayerTransform& a_current)
|
||||
@@ -726,6 +748,7 @@ namespace
|
||||
debugMovementState.isJumping,
|
||||
debugMovementState.isCrouching,
|
||||
debugMovementState.weaponDrawn,
|
||||
debugMovementState.isPipboyOpen,
|
||||
debugMovementState.movementSpeed,
|
||||
-1.0F,
|
||||
currentEquipmentState,
|
||||
@@ -818,6 +841,7 @@ namespace
|
||||
networkMovementState.isJumping,
|
||||
networkMovementState.isCrouching,
|
||||
networkMovementState.weaponDrawn,
|
||||
networkMovementState.isPipboyOpen,
|
||||
networkMovementState.movementSpeed,
|
||||
animationGraphSpeed,
|
||||
currentEquipmentState,
|
||||
@@ -839,6 +863,11 @@ namespace
|
||||
} else if (hasAppearanceChanged) {
|
||||
LogInfoWithLocalPlayerPrefix("Detected local appearance change. Sending immediate transform update.");
|
||||
lastLogTime = currentTime;
|
||||
} else if (hasMovementStateChanged && lastSentMovementState.isPipboyOpen != networkMovementState.isPipboyOpen) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Detected local Pip-Boy state change. Sending immediate transform update: isPipboyOpen={}.",
|
||||
networkMovementState.isPipboyOpen));
|
||||
lastLogTime = currentTime;
|
||||
} else if (currentTime - lastLogTime >= kTransformLogInterval) {
|
||||
LogPlayerTransform(currentTransform);
|
||||
lastLogTime = currentTime;
|
||||
|
||||
Reference in New Issue
Block a user