Milestone 1: Proxy actors now equip remote players' right-hand weapons and play armed idle/locomotion poses when weaponDrawn=true. Adds rightHand weapon slot to equippedItems protocol, weapon graph variable support (iSyncGunDown: 0=drawn, 1=holstered), and weapon FSM events (weaponDraw, readyStateEnter, gunDownStateEnter). Fixes two critical animation state churn bugs: frustum-visibility flicker no longer resets animation state on every update tick, and idle proxies no longer register as changed on every frame. Includes WeaponBehavior graph loading via AIProcess::RequestLoadAnimationsForWeaponChange() and manual weapon attachment fallback. New docs/weapon-animation-sync.md reference guide maps animation events and variables from F4-Animation-Research. Experimental alert-state reassertion for armed proxies to test NPC combat state gating of weapon-ready poses.
94 lines
3.3 KiB
C++
94 lines
3.3 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";
|
|
// Weapon animation graph variables (RaiderRootBehavior / WeaponBehavior)
|
|
inline constexpr const char* kSyncGunDown = "iSyncGunDown";
|
|
inline constexpr const char* kRifleDrawnStateID = "iRifleDrawnStateID";
|
|
}
|
|
|
|
// 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);
|
|
}
|