Files
Commonwealth-Online-Public/plugin/include/F4TProxyAnimationSync.h
T
andrew a137821406 Improve proxy locomotion, tier & jump sync
Fix multiple proxy animation issues: stale graph Speed collapsing jog into walk, missing slow-walk/jog tiers, and jump takeoff/landing not playing. Key changes:

- Prefer position-derived movementSpeed when animationGraphSpeed is missing/stale (ResolveLocomotionGraphSpeed) and fall back to graph Speed only when it agrees within a tolerance; sender now transmits movementSpeed when local graph read is 0 and TryReadLocalAnimationGraphSpeed falls back to SpeedSmoothed.
- Introduce locomotion tiers (Walk/Jog/Run) with ComputeLocomotionTier and sync ints (iSyncWalkRun/iSyncLocomotionSpeed/iSyncJumpState) to the graph; fire corresponding tier events when starting or changing tier.
- Fix proxy motion application: persist per-proxy velocity and worldDelta so the Update hook does not zero-out controller motion; use measured frame motion to drive animation when network flags are sparse.
- Restore correct jump behavior: fire MT/weapon-compatible jump events for takeoff and landing (JumpUp/JumpStartFrom*/jumpLand/jumpLandTo*), clear bInJumpState on landing and remove the extra proxy-side jump hold so landings exit the jump loop.
- Misc: add constants and thresholds for tier/run/walk/run-land decisions and update logs to include tier labels.

Files updated: plugin/include/F4TProxyAnimationSync.h, plugin/src/F4TProxyAnimationSync.cpp, plugin/src/F4AnimationDescriptor.cpp, plugin/src/F4TProxyPuppet.cpp, plugin/src/F4TProxyActorController.cpp, plugin/src/main.cpp, and docs/dev-log.md.
2026-06-05 15:48:16 +12:00

91 lines
3.1 KiB
C++

#pragma once
#include "F4TRemotePlayerState.h"
#include <chrono>
#include <cstdint>
#include <optional>
#include <string_view>
namespace RE
{
class Actor;
}
namespace F4T::ProxyAnimationSync
{
// Authoritative FO4 humanoid movement-behavior graph variable names, taken from
// MTBehavior.hkx (hkbBehaviorGraphStringData::variableNames, with types from the
// parallel variableInfos array) via the F4-Animation-Research unpack:
// Speed, SpeedSmoothed, Direction -> VARIABLE_TYPE_REAL (float)
// IsSprinting, bInJumpState -> VARIABLE_TYPE_BOOL (bool)
// iIsInSneak -> VARIABLE_TYPE_INT32 (int)
// Earlier code guessed names like "speed"/"speedSampled"/"direction" that do not exist
// on this graph, so every SetGraphVariable* call silently failed.
namespace GraphVar
{
inline constexpr const char* kSpeed = "Speed";
inline constexpr const char* kSpeedSmoothed = "SpeedSmoothed";
inline constexpr const char* kDirection = "Direction";
inline constexpr const char* kIsSprinting = "IsSprinting";
inline constexpr const char* kInJumpState = "bInJumpState";
inline constexpr const char* kIsInSneak = "iIsInSneak";
inline constexpr const char* kSyncWalkRun = "iSyncWalkRun";
inline constexpr const char* kSyncLocomotionSpeed = "iSyncLocomotionSpeed";
inline constexpr const char* kSyncJumpState = "iSyncJumpState";
}
// MTLocomotionBlend_SM sub-states: Walk(0), Jog(1), Run(2). Selected by graph events
// "Walk"/"Jog"/"Run" and mirrored in iSyncLocomotionSpeed.
enum class LocomotionTier : std::int32_t
{
kWalk = 0,
kJog = 1,
kRun = 2,
};
LocomotionTier ComputeLocomotionTier(float a_graphSpeed, bool a_isSprinting);
// Maps network movementSpeed (units/sec) to graph Speed. Used when animationGraphSpeed
// is missing or unreliable.
float MapMovementSpeedToGraphSpeed(float a_movementSpeed, bool a_isSprinting);
// Picks the graph Speed to drive locomotion blending. Prefers animationGraphSpeed only
// when it is above the idle threshold; a read of 0.0 while moving is common and must
// not override the position-derived movementSpeed (which carries slow-walk/jog tiers).
float ResolveLocomotionGraphSpeed(
float a_movementSpeed,
float a_animationGraphSpeed,
bool a_isMoving,
bool a_isSprinting);
struct ProxyAppliedAnimationState
{
bool hasAppliedGraphState{ false };
bool lastIsMoving{ false };
bool lastIsSprinting{ false };
bool lastIsSneaking{ false };
bool lastIsJumping{ false };
bool lastIsCrouching{ false };
bool lastWeaponDrawn{ false };
float lastGraphSpeed{ 0.0F };
float lastDirection{ 0.0F };
std::optional<LocomotionTier> lastLocomotionTier{};
std::chrono::steady_clock::time_point movingHoldUntil{};
std::chrono::steady_clock::time_point jumpHoldUntil{};
};
void ResetAppliedAnimationState(ProxyAppliedAnimationState& a_state);
void ApplyProxyAnimationFromRemoteState(
ProxyAppliedAnimationState& a_state,
RE::Actor& a_proxy,
const RemotePlayerState::RemotePlayerState& a_remotePlayer);
void ClearProxyAnimationState(
ProxyAppliedAnimationState& a_state,
RE::Actor& a_proxy,
std::uint32_t a_remotePlayerId,
std::string_view a_reason);
}