Add crouch state and fix jump sync
Add isCrouching to network, state and animation systems; enable proxy jump syncing with a jump-hold debounce and update animation descriptor and server tooling. Key changes: - Networking: F4TNetworking.h/cpp: added a_isCrouching, include "isCrouching" in transform packets and parsing, and logging. - Remote state: F4TRemotePlayerState.h: added isCrouching field. - Proxy animation: F4TProxyAnimationSync.*: added crouch fields, enabled jump sync, implemented ApplyJumpHoldState and jump hold timing, wrote crouch/jump bools into descriptors when available, updated event firing and logs. - Animation descriptor: F4AnimationDescriptor.cpp: added isJumping and isCrouching to bool variable list. - Local detection: main.cpp: added isCrouching in movement state, stubbed GetCrouchState(), adjusted jump hold timing and included crouch in outgoing transform calls. - Server/dev tooling: server/dev_server_app.py, fake_client.py, fake_player.py, README.md: added crouch reporting, toggle UI/control and fake client support for crouch, and updated docs/logging. - Docs: docs/dev-log.md updated with notes on jump/crouch fixes and testing. Rationale: ensure remote players can report crouch state and make jump animations transition reliably by debouncing jump state on proxies and writing available graph bools; also provide dev-server controls and fake-client support for testing.
This commit is contained in:
@@ -23,6 +23,7 @@ namespace F4T::Networking
|
||||
bool a_isSprinting = false,
|
||||
bool a_isSneaking = false,
|
||||
bool a_isJumping = false,
|
||||
bool a_isCrouching = false,
|
||||
bool a_weaponDrawn = false,
|
||||
float a_movementSpeed = 0.0F,
|
||||
float a_animationGraphSpeed = -1.0F);
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace F4T::ProxyAnimationSync
|
||||
inline constexpr const char* kSpeedSampled = "speedSampled";
|
||||
inline constexpr const char* kSpeedDamped = "speedDamped";
|
||||
inline constexpr const char* kIsSprinting = "IsSprinting";
|
||||
inline constexpr const char* kInJumpState = "bInJumpState";
|
||||
}
|
||||
|
||||
// Maps network movementSpeed (units/sec) to graph Speed (~0-105). Used when the sender
|
||||
@@ -38,10 +39,12 @@ namespace F4T::ProxyAnimationSync
|
||||
bool lastIsSprinting{ false };
|
||||
bool lastIsSneaking{ false };
|
||||
bool lastIsJumping{ false };
|
||||
bool lastIsCrouching{ false };
|
||||
bool lastWeaponDrawn{ false };
|
||||
float lastGraphSpeed{ 0.0F };
|
||||
float lastDirection{ 0.0F };
|
||||
std::chrono::steady_clock::time_point movingHoldUntil{};
|
||||
std::chrono::steady_clock::time_point jumpHoldUntil{};
|
||||
};
|
||||
|
||||
void ResetAppliedAnimationState(ProxyAppliedAnimationState& a_state);
|
||||
|
||||
@@ -38,16 +38,16 @@ namespace F4T::RemotePlayerState
|
||||
bool isSprinting{ false };
|
||||
bool isSneaking{ false };
|
||||
bool isJumping{ false };
|
||||
bool isCrouching{ false };
|
||||
bool weaponDrawn{ false };
|
||||
float movementSpeed{ 0.0F };
|
||||
// Authoritative Havok graph Speed from sender when available (>= 0). -1 means unset.
|
||||
float animationGraphSpeed{ -1.0F };
|
||||
std::chrono::steady_clock::time_point lastReceivedLocalTime{};
|
||||
|
||||
// Phase 2: Actor state and action events (TiltedEvolution alignment)
|
||||
std::uint32_t actorStateFlags1{}; // ActorState.flags1 (combat, sneaking, etc.)
|
||||
std::uint32_t actorStateFlags2{}; // ActorState.flags2 (additional state flags)
|
||||
std::vector<RemoteActionEvent> actionEvents; // Queue of pending action events
|
||||
std::uint32_t actorStateFlags1{};
|
||||
std::uint32_t actorStateFlags2{};
|
||||
std::vector<RemoteActionEvent> actionEvents;
|
||||
};
|
||||
|
||||
void SetAssignedPlayerId(std::uint32_t a_playerId);
|
||||
|
||||
@@ -79,18 +79,20 @@ namespace F4T::AnimationDescriptor
|
||||
m_floatVariableIndexMap[m_floatVariables[i]] = i;
|
||||
}
|
||||
|
||||
// ===== BOOL VARIABLES (State Flags) =====
|
||||
m_boolVariables = {
|
||||
// Movement state
|
||||
"isSprinting", // 0 - Sprint mode active
|
||||
"isSneaking", // 1 - Sneak/crouch mode
|
||||
"isMoving", // 2 - Any movement active
|
||||
// ===== BOOL VARIABLES (State Flags) =====
|
||||
m_boolVariables = {
|
||||
// Movement state
|
||||
"isSprinting", // 0 - Sprint mode active
|
||||
"isSneaking", // 1 - Sneak/crouch mode
|
||||
"isMoving", // 2 - Any movement active
|
||||
"isJumping", // 3 - Jump in progress
|
||||
"isCrouching", // 4 - Crouch mode active
|
||||
|
||||
// Engine state
|
||||
"bMotionDriven", // 3 - Motion-driven locomotion
|
||||
"bInMoveState", // 4 - In locomotion state machine
|
||||
"bSprintOK", // 5 - Sprint available (game state dependent)
|
||||
};
|
||||
// Engine state
|
||||
"bMotionDriven", // 5 - Motion-driven locomotion
|
||||
"bInMoveState", // 6 - In locomotion state machine
|
||||
"bSprintOK", // 7 - Sprint available (game state dependent)
|
||||
};
|
||||
|
||||
// Build reverse lookup map for bool variables
|
||||
for (std::size_t i = 0; i < m_boolVariables.size(); ++i) {
|
||||
|
||||
@@ -393,6 +393,7 @@ namespace
|
||||
ReadBool(a_packet, "isSprinting").value_or(false),
|
||||
ReadBool(a_packet, "isSneaking").value_or(false),
|
||||
ReadBool(a_packet, "isJumping").value_or(false),
|
||||
ReadBool(a_packet, "isCrouching").value_or(false),
|
||||
ReadBool(a_packet, "weaponDrawn").value_or(false),
|
||||
static_cast<float>(movementSpeed),
|
||||
static_cast<float>(animationGraphSpeed),
|
||||
@@ -422,12 +423,13 @@ namespace
|
||||
|
||||
if (ShouldLogRemoteMovementState(*playerId)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Remote player {} movement state: moving={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, speed={:.1f}, flags1={:08X}, flags2={:08X}",
|
||||
"Remote player {} movement state: moving={}, sprinting={}, sneaking={}, jumping={}, crouching={}, weaponDrawn={}, speed={:.1f}, flags1={:08X}, flags2={:08X}",
|
||||
remoteState.playerId,
|
||||
remoteState.isMoving,
|
||||
remoteState.isSprinting,
|
||||
remoteState.isSneaking,
|
||||
remoteState.isJumping,
|
||||
remoteState.isCrouching,
|
||||
remoteState.weaponDrawn,
|
||||
remoteState.movementSpeed,
|
||||
remoteState.actorStateFlags1,
|
||||
@@ -697,6 +699,7 @@ namespace F4T::Networking
|
||||
bool a_isSprinting,
|
||||
bool a_isSneaking,
|
||||
bool a_isJumping,
|
||||
bool a_isCrouching,
|
||||
bool a_weaponDrawn,
|
||||
float a_movementSpeed,
|
||||
float a_animationGraphSpeed)
|
||||
@@ -713,7 +716,7 @@ namespace F4T::Networking
|
||||
packet.data(),
|
||||
packet.size(),
|
||||
"{\"type\":\"transform\",\"x\":%.2f,\"y\":%.2f,\"z\":%.2f,\"angleZ\":%.2f,\"movementType\":\"%s\",\"clientTime\":%.3f,"
|
||||
"\"isMoving\":%s,\"isSprinting\":%s,\"isSneaking\":%s,\"isJumping\":%s,\"weaponDrawn\":%s,\"movementSpeed\":%.1f",
|
||||
"\"isMoving\":%s,\"isSprinting\":%s,\"isSneaking\":%s,\"isJumping\":%s,\"isCrouching\":%s,\"weaponDrawn\":%s,\"movementSpeed\":%.1f",
|
||||
a_x,
|
||||
a_y,
|
||||
a_z,
|
||||
@@ -724,6 +727,7 @@ namespace F4T::Networking
|
||||
a_isSprinting ? "true" : "false",
|
||||
a_isSneaking ? "true" : "false",
|
||||
a_isJumping ? "true" : "false",
|
||||
a_isCrouching ? "true" : "false",
|
||||
a_weaponDrawn ? "true" : "false",
|
||||
a_movementSpeed);
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ namespace
|
||||
constexpr bool kEnableProxyLocomotionSync = true;
|
||||
// Disabled until read-by-name debug finds working bool variable names on this graph.
|
||||
constexpr bool kEnableProxySneakSync = false;
|
||||
constexpr bool kEnableProxyJumpSync = false;
|
||||
constexpr bool kEnableProxyJumpSync = true;
|
||||
constexpr bool kEnableProxyCrouchSync = false;
|
||||
constexpr bool kEnableProxyWeaponDrawnSync = false;
|
||||
|
||||
constexpr float kIdleSpeedThreshold = 1.0F;
|
||||
@@ -40,6 +41,9 @@ namespace
|
||||
// Hold duration for movement stop debounce. Must be short so animation stop events
|
||||
// fire promptly; long holds cause characters to glide while playing stop animations.
|
||||
constexpr auto kProxyMovingStopHoldDuration = std::chrono::milliseconds{ 50 };
|
||||
// Minimum time to keep a remote jump "active" on the proxy so takeoff animation can play
|
||||
// before we fire landing/locomotion exit events (network updates are ~100ms apart).
|
||||
constexpr auto kProxyJumpMinDuration = std::chrono::milliseconds{ 400 };
|
||||
|
||||
// Graph Speed range confirmed from local-player read-by-name debug in
|
||||
// Fallout4Together.log: walk starts ~47, full run ~373. FO4's graph "Speed" is in the
|
||||
@@ -123,6 +127,12 @@ namespace
|
||||
inline constexpr const char* kSprintStop = "SprintStop";
|
||||
inline constexpr const char* kSneakStart = "SneakStart";
|
||||
inline constexpr const char* kSneakStop = "SneakStop";
|
||||
// JumpStart is accepted by the FO4 proxy graph (takeoff). JumpStop is not reliable
|
||||
// for landing; use moveStart/moveStop to exit the jump state instead.
|
||||
inline constexpr const char* kJumpStart = "JumpStart";
|
||||
inline constexpr const char* kJumpStop = "JumpStop";
|
||||
inline constexpr const char* kCrouchStart = "CrouchStart";
|
||||
inline constexpr const char* kCrouchStop = "CrouchStop";
|
||||
}
|
||||
|
||||
bool TrySendGraphEvent(RE::Actor& a_proxy, const char* a_eventName)
|
||||
@@ -204,6 +214,7 @@ namespace
|
||||
bool isSprinting{ false };
|
||||
bool isSneaking{ false };
|
||||
bool isJumping{ false };
|
||||
bool isCrouching{ false };
|
||||
bool weaponDrawn{ false };
|
||||
float graphSpeed{ 0.0F };
|
||||
float direction{ 0.0F };
|
||||
@@ -216,6 +227,7 @@ namespace
|
||||
desired.isSprinting = desired.isMoving && a_remotePlayer.isSprinting;
|
||||
desired.isSneaking = a_remotePlayer.isSneaking;
|
||||
desired.isJumping = a_remotePlayer.isJumping;
|
||||
desired.isCrouching = a_remotePlayer.isCrouching;
|
||||
desired.weaponDrawn = a_remotePlayer.weaponDrawn;
|
||||
desired.graphSpeed = ComputeGraphSpeed(a_remotePlayer);
|
||||
desired.direction = a_remotePlayer.angleZ;
|
||||
@@ -235,6 +247,21 @@ namespace
|
||||
return a_state.movingHoldUntil.time_since_epoch().count() != 0 && now < a_state.movingHoldUntil;
|
||||
}
|
||||
|
||||
bool ApplyJumpHoldState(
|
||||
F4T::ProxyAnimationSync::ProxyAppliedAnimationState& a_state,
|
||||
bool a_rawIsJumping)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (a_rawIsJumping) {
|
||||
if (a_state.jumpHoldUntil.time_since_epoch().count() == 0 || now >= a_state.jumpHoldUntil) {
|
||||
a_state.jumpHoldUntil = now + kProxyJumpMinDuration;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return a_state.jumpHoldUntil.time_since_epoch().count() != 0 && now < a_state.jumpHoldUntil;
|
||||
}
|
||||
|
||||
DesiredProxyAnimationState BuildDebouncedDesiredState(
|
||||
F4T::ProxyAnimationSync::ProxyAppliedAnimationState& a_state,
|
||||
const F4T::RemotePlayerState::RemotePlayerState& a_remotePlayer)
|
||||
@@ -243,6 +270,9 @@ namespace
|
||||
auto desired = rawDesired;
|
||||
// Apply minimal hold (50ms) for debounce, but don't coast; snap to idle immediately.
|
||||
desired.isMoving = ApplyMovingHoldState(a_state, rawDesired.isMoving);
|
||||
if constexpr (kEnableProxyJumpSync) {
|
||||
desired.isJumping = ApplyJumpHoldState(a_state, rawDesired.isJumping);
|
||||
}
|
||||
|
||||
if (!desired.isMoving) {
|
||||
desired.isSprinting = false;
|
||||
@@ -268,6 +298,7 @@ namespace
|
||||
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 ||
|
||||
AreFloatsDifferent(a_state.lastGraphSpeed, a_desired.graphSpeed) ||
|
||||
AreFloatsDifferent(a_state.lastDirection, a_desired.direction);
|
||||
@@ -316,10 +347,8 @@ namespace
|
||||
}
|
||||
|
||||
(void)kEnableProxySneakSync;
|
||||
(void)kEnableProxyJumpSync;
|
||||
(void)kEnableProxyWeaponDrawnSync;
|
||||
(void)a_desired.isSneaking;
|
||||
(void)a_desired.isJumping;
|
||||
(void)a_desired.weaponDrawn;
|
||||
|
||||
if (!success) {
|
||||
@@ -358,25 +387,43 @@ namespace
|
||||
snapshot.floats[directionIdx] = a_desired.direction;
|
||||
}
|
||||
|
||||
// Populate bool variables
|
||||
const auto isSprintingIdx = descriptor.GetBoolVariableIndex("isSprinting");
|
||||
if (isSprintingIdx != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[isSprintingIdx] = a_desired.isSprinting;
|
||||
}
|
||||
// Populate bool variables
|
||||
const auto isSprintingIdx = descriptor.GetBoolVariableIndex("isSprinting");
|
||||
if (isSprintingIdx != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[isSprintingIdx] = a_desired.isSprinting;
|
||||
}
|
||||
|
||||
const auto isSneakingIdx = descriptor.GetBoolVariableIndex("isSneaking");
|
||||
if (isSneakingIdx != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[isSneakingIdx] = a_desired.isSneaking;
|
||||
}
|
||||
const auto isSneakingIdx = descriptor.GetBoolVariableIndex("isSneaking");
|
||||
if (isSneakingIdx != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[isSneakingIdx] = a_desired.isSneaking;
|
||||
}
|
||||
|
||||
const auto isMovingIdx = descriptor.GetBoolVariableIndex("isMoving");
|
||||
if (isMovingIdx != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[isMovingIdx] = a_desired.isMoving;
|
||||
const auto isMovingIdx = descriptor.GetBoolVariableIndex("isMoving");
|
||||
if (isMovingIdx != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[isMovingIdx] = a_desired.isMoving;
|
||||
}
|
||||
|
||||
if constexpr (kEnableProxyJumpSync) {
|
||||
const auto isJumpingIdx = descriptor.GetBoolVariableIndex("isJumping");
|
||||
if (isJumpingIdx != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[isJumpingIdx] = a_desired.isJumping;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (kEnableProxyCrouchSync) {
|
||||
const auto isCrouchingIdx = descriptor.GetBoolVariableIndex("isCrouching");
|
||||
if (isCrouchingIdx != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[isCrouchingIdx] = a_desired.isCrouching;
|
||||
}
|
||||
}
|
||||
|
||||
// Single bulk write operation (major performance win)
|
||||
const auto success = descriptor.LoadAnimationVariablesToCache(*graphHolder, snapshot);
|
||||
|
||||
if constexpr (kEnableProxyJumpSync) {
|
||||
TrySetGraphBool(*graphHolder, F4T::ProxyAnimationSync::GraphVar::kInJumpState, a_desired.isJumping);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
static std::unordered_set<std::uint32_t> loggedWriteFailures;
|
||||
if (loggedWriteFailures.insert(a_remotePlayerId).second) {
|
||||
@@ -391,7 +438,7 @@ namespace
|
||||
}
|
||||
|
||||
// Fire locomotion animation events on state transitions to actually drive the
|
||||
// proxy's animation graph FSM (idle <-> move, sprint, sneak). Edge-triggered, mirroring
|
||||
// proxy's animation graph FSM (idle <-> move, sprint, sneak, jump, crouch). Edge-triggered, mirroring
|
||||
// how the game itself emits these events when the local player starts/stops moving.
|
||||
void SendLocomotionTransitionEvents(
|
||||
RE::Actor& a_proxy,
|
||||
@@ -431,6 +478,24 @@ namespace
|
||||
fireEvent(a_desired.isSneaking ? GraphEvent::kSneakStart : GraphEvent::kSneakStop);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (kEnableProxyJumpSync) {
|
||||
if (!wasInitialized || a_previous.lastIsJumping != a_desired.isJumping) {
|
||||
if (a_desired.isJumping) {
|
||||
fireEvent(GraphEvent::kJumpStart);
|
||||
} else if (wasInitialized) {
|
||||
// JumpStop is not consumed by the graph; re-assert ground locomotion to
|
||||
// exit the jump loop (same mechanism as "jump off a table" resetting it).
|
||||
fireEvent(a_desired.isMoving ? GraphEvent::kMoveStart : GraphEvent::kMoveStop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (kEnableProxyCrouchSync) {
|
||||
if (!wasInitialized || a_previous.lastIsCrouching != a_desired.isCrouching) {
|
||||
fireEvent(a_desired.isCrouching ? GraphEvent::kCrouchStart : GraphEvent::kCrouchStop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LogAppliedTransition(
|
||||
@@ -440,7 +505,7 @@ namespace
|
||||
bool a_initial)
|
||||
{
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Runtime proxy animation sync {} for remote player {}: actor={:08X}, moving={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, graphSpeed={:.1f}, direction={:.3f}.",
|
||||
"Runtime proxy animation sync {} for remote player {}: actor={:08X}, moving={}, sprinting={}, sneaking={}, jumping={}, crouching={}, weaponDrawn={}, graphSpeed={:.1f}, direction={:.3f}.",
|
||||
a_initial ? "initial" : "changed",
|
||||
a_remotePlayerId,
|
||||
a_actorId,
|
||||
@@ -448,6 +513,7 @@ namespace
|
||||
a_desired.isSprinting,
|
||||
a_desired.isSneaking,
|
||||
a_desired.isJumping,
|
||||
a_desired.isCrouching,
|
||||
a_desired.weaponDrawn,
|
||||
a_desired.graphSpeed,
|
||||
a_desired.direction));
|
||||
@@ -555,14 +621,15 @@ namespace F4T::ProxyAnimationSync
|
||||
LogAppliedTransition(a_remotePlayer.playerId, actorId, desired, initial);
|
||||
}
|
||||
|
||||
a_state.hasAppliedGraphState = true;
|
||||
a_state.lastIsMoving = desired.isMoving;
|
||||
a_state.lastIsSprinting = desired.isSprinting;
|
||||
a_state.lastIsSneaking = desired.isSneaking;
|
||||
a_state.lastIsJumping = desired.isJumping;
|
||||
a_state.lastWeaponDrawn = desired.weaponDrawn;
|
||||
a_state.lastGraphSpeed = desired.graphSpeed;
|
||||
a_state.lastDirection = desired.direction;
|
||||
a_state.hasAppliedGraphState = true;
|
||||
a_state.lastIsMoving = desired.isMoving;
|
||||
a_state.lastIsSprinting = desired.isSprinting;
|
||||
a_state.lastIsSneaking = desired.isSneaking;
|
||||
a_state.lastIsJumping = desired.isJumping;
|
||||
a_state.lastIsCrouching = desired.isCrouching;
|
||||
a_state.lastWeaponDrawn = desired.weaponDrawn;
|
||||
a_state.lastGraphSpeed = desired.graphSpeed;
|
||||
a_state.lastDirection = desired.direction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,20 +694,28 @@ namespace F4T::ProxyAnimationSync
|
||||
snapshot.floats[descriptor.GetFloatVariableIndex("direction")] = desired.direction;
|
||||
}
|
||||
|
||||
// Prepare bool variables (isSprinting, isSneaking, isMoving)
|
||||
snapshot.bools.resize(descriptor.GetBoolVariableCount(), false);
|
||||
// Prepare bool variables (isSprinting, isSneaking, isMoving, isJumping, isCrouching)
|
||||
snapshot.bools.resize(descriptor.GetBoolVariableCount(), false);
|
||||
|
||||
if (descriptor.GetBoolVariableIndex("isSprinting") != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isSprinting")] = desired.isSprinting;
|
||||
}
|
||||
if (descriptor.GetBoolVariableIndex("isSprinting") != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isSprinting")] = desired.isSprinting;
|
||||
}
|
||||
|
||||
if (descriptor.GetBoolVariableIndex("isSneaking") != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isSneaking")] = desired.isSneaking;
|
||||
}
|
||||
if (descriptor.GetBoolVariableIndex("isSneaking") != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isSneaking")] = desired.isSneaking;
|
||||
}
|
||||
|
||||
if (descriptor.GetBoolVariableIndex("isMoving") != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isMoving")] = desired.isMoving;
|
||||
}
|
||||
if (descriptor.GetBoolVariableIndex("isMoving") != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isMoving")] = desired.isMoving;
|
||||
}
|
||||
|
||||
if (descriptor.GetBoolVariableIndex("isJumping") != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isJumping")] = desired.isJumping;
|
||||
}
|
||||
|
||||
if (descriptor.GetBoolVariableIndex("isCrouching") != static_cast<std::size_t>(-1)) {
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isCrouching")] = desired.isCrouching;
|
||||
}
|
||||
|
||||
// Prepare int variables (equipment, etc.)
|
||||
snapshot.ints.resize(descriptor.GetIntVariableCount(), 0);
|
||||
|
||||
+24
-11
@@ -38,6 +38,7 @@ namespace
|
||||
bool isSprinting;
|
||||
bool isSneaking;
|
||||
bool isJumping;
|
||||
bool isCrouching;
|
||||
bool weaponDrawn;
|
||||
float movementSpeed;
|
||||
};
|
||||
@@ -59,7 +60,8 @@ namespace
|
||||
constexpr auto kMovingSpeedThreshold = 1.0F;
|
||||
constexpr auto kMovementStopHoldDuration = 300ms;
|
||||
constexpr auto kJumpVerticalSpeedThreshold = 80.0F;
|
||||
constexpr auto kJumpStateHoldDuration = 400ms;
|
||||
// Tail hold after IsJumping() clears so transform packets still carry isJumping=true.
|
||||
constexpr auto kJumpStateHoldDuration = 200ms;
|
||||
constexpr auto kJumpDiagnosticLogInterval = 1s;
|
||||
constexpr auto kSneakDiagnosticLogInterval = 2s;
|
||||
|
||||
@@ -232,6 +234,14 @@ namespace
|
||||
};
|
||||
}
|
||||
|
||||
bool GetCrouchState(RE::PlayerCharacter&)
|
||||
{
|
||||
// TODO: FO4 doesn't expose crouch as a separate animation graph variable.
|
||||
// The cover stances (combat) are not reliable for movement sync.
|
||||
// Crouch sync is disabled until we find a better detection method.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShouldLogSneakDetection(
|
||||
const SneakDetectionState& a_sneakDetection,
|
||||
std::chrono::steady_clock::time_point a_currentTime)
|
||||
@@ -316,6 +326,7 @@ namespace
|
||||
a_player.DoGetSprinting(),
|
||||
sneakDetection.finalIsSneaking,
|
||||
heldJumping,
|
||||
GetCrouchState(a_player),
|
||||
a_player.GetWeaponMagicDrawn(),
|
||||
a_movementSpeed
|
||||
};
|
||||
@@ -349,6 +360,7 @@ namespace
|
||||
a_previous.isSprinting != a_current.isSprinting ||
|
||||
a_previous.isSneaking != a_current.isSneaking ||
|
||||
a_previous.isJumping != a_current.isJumping ||
|
||||
a_previous.isCrouching != a_current.isCrouching ||
|
||||
a_previous.weaponDrawn != a_current.weaponDrawn;
|
||||
}
|
||||
|
||||
@@ -538,16 +550,17 @@ namespace
|
||||
currentTransform.angleZ,
|
||||
movementType,
|
||||
currentLocation.hasCellId,
|
||||
currentLocation.cellId,
|
||||
currentLocation.hasWorldspaceId,
|
||||
currentLocation.worldspaceId,
|
||||
networkMovementState.isMoving,
|
||||
networkMovementState.isSprinting,
|
||||
networkMovementState.isSneaking,
|
||||
networkMovementState.isJumping,
|
||||
networkMovementState.weaponDrawn,
|
||||
networkMovementState.movementSpeed,
|
||||
animationGraphSpeed);
|
||||
currentLocation.cellId,
|
||||
currentLocation.hasWorldspaceId,
|
||||
currentLocation.worldspaceId,
|
||||
networkMovementState.isMoving,
|
||||
networkMovementState.isSprinting,
|
||||
networkMovementState.isSneaking,
|
||||
networkMovementState.isJumping,
|
||||
networkMovementState.isCrouching,
|
||||
networkMovementState.weaponDrawn,
|
||||
networkMovementState.movementSpeed,
|
||||
animationGraphSpeed);
|
||||
if (sentTransform) {
|
||||
lastSentTransform = currentTransform;
|
||||
lastKnownLocation = currentLocation;
|
||||
|
||||
Reference in New Issue
Block a user