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:
@@ -10,6 +10,7 @@ For testing notes, milestone summaries, known issues, and next steps, see [`docs
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Optional `isPipboyOpen` transform state so receivers can detect remote Pip-Boy open/close transitions. The state is synced and logged for diagnostics; no proxy Pip-Boy animation is driven (the third-person humanoid behavior graph has no Pip-Boy raise/use animation — it is first-person only), leaving room for a future custom third-person animation.
|
||||||
- Optional `appearance` transform snapshots for best-effort runtime proxy body and face visuals, including height, body morph weight, body tint, hair colors, head-part IDs, and morph slider values while leaving gender switching out.
|
- Optional `appearance` transform snapshots for best-effort runtime proxy body and face visuals, including height, body morph weight, body tint, hair colors, head-part IDs, and morph slider values while leaving gender switching out.
|
||||||
- Game-thread proxy appearance application for supported `appearance` fields after runtime proxy 3D is ready, with race switching deferred for future proxy-base compatibility work.
|
- Game-thread proxy appearance application for supported `appearance` fields after runtime proxy 3D is ready, with race switching deferred for future proxy-base compatibility work.
|
||||||
- `server/fake_client.py` parsing/output and `server/fake_player.py` sample payloads for relayed `appearance` transform data.
|
- `server/fake_client.py` parsing/output and `server/fake_player.py` sample payloads for relayed `appearance` transform data.
|
||||||
@@ -28,7 +29,11 @@ For testing notes, milestone summaries, known issues, and next steps, see [`docs
|
|||||||
- Game-thread proxy equipment application for synced apparel, including explicit tracked-slot unequips and cleanup when runtime proxy slots are held, reused, or reassigned.
|
- Game-thread proxy equipment application for synced apparel, including explicit tracked-slot unequips and cleanup when runtime proxy slots are held, reused, or reassigned.
|
||||||
- `server/fake_client.py` parsing and output for relayed `equippedItems` transform data.
|
- `server/fake_client.py` parsing and output for relayed `equippedItems` transform data.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Proxy Pip-Boy animation triggering (guessed graph notification events, `pipboyUp`/`bBlockPipboy` graph-variable writes, and the `TESIdleForm` playback fallback) after the F4-Animation-Research unpack confirmed the third-person humanoid behavior graph has no Pip-Boy raise/use animation; only the `isPipboyOpen` state sync is retained.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Idle animation state no longer logs as changed every frame while a proxy is stationary.
|
||||||
- New clients now receive existing players' last transform snapshots immediately after `welcome`, including `appearance`, so late joiners do not start with default proxy visuals while waiting for the next sender heartbeat.
|
- New clients now receive existing players' last transform snapshots immediately after `welcome`, including `appearance`, so late joiners do not start with default proxy visuals while waiting for the next sender heartbeat.
|
||||||
- Runtime proxy candidate isolation now ignores actors flagged as deleted, so a freshly spawned proxy is controlled instead of the slot adopting a still-present stale proxy that was marked for deletion the same frame.
|
- Runtime proxy candidate isolation now ignores actors flagged as deleted, so a freshly spawned proxy is controlled instead of the slot adopting a still-present stale proxy that was marked for deletion the same frame.
|
||||||
- Stale saved runtime `COPlayerProxy` actors are deleted before spawning a fresh PlaceAtMe proxy, preventing duplicate visible proxies when an old uncontrolled proxy persisted in a save.
|
- Stale saved runtime `COPlayerProxy` actors are deleted before spawning a fresh PlaceAtMe proxy, preventing duplicate visible proxies when an old uncontrolled proxy persisted in a save.
|
||||||
|
|||||||
Binary file not shown.
@@ -12,7 +12,7 @@ The current system is still a local prototype, but the Fallout 4 plugin now has
|
|||||||
both send and receive paths:
|
both send and receive paths:
|
||||||
|
|
||||||
1. The Fallout 4 plugin reads local player transform data.
|
1. The Fallout 4 plugin reads local player transform data.
|
||||||
2. The plugin derives basic movement state and captures visible equipped
|
2. The plugin derives basic movement/menu state and captures visible equipped
|
||||||
apparel and player appearance snapshots on the game-thread polling path, then
|
apparel and player appearance snapshots on the game-thread polling path, then
|
||||||
sends them as optional data on transform packets.
|
sends them as optional data on transform packets.
|
||||||
3. The plugin sends transform packets to the Python relay server. The default
|
3. The plugin sends transform packets to the Python relay server. The default
|
||||||
|
|||||||
+112
@@ -9,6 +9,118 @@ failed experiments, successful tests, and next steps.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-06-30 - Pip-Boy Third-Person Behavior Investigation
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Traced why no Pip-Boy animation plays on the proxy and established the engine-level root cause from the F4-Animation-Research unpack. Replaced the guessed graph events with the authoritative third-person Pip-Boy graph variables.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `plugin/include/F4TProxyAnimationSync.h`
|
||||||
|
- `plugin/src/F4TProxyAnimationSync.cpp`
|
||||||
|
- `changelog.md`
|
||||||
|
- `docs/dev-log.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
- Log analysis: `isPipboyOpen=true` reaches the receiver (`recvPipboyOpen=true`), so capture/relay/storage are correct. Every guessed Pip-Boy event (`PipboyOpen`, `PipBoyOpen`, `pipboyOpen`, `Pipboy_Open`) returned `graphAccepted=false`, and the idle scan found `0` Pip-Boy idle forms.
|
||||||
|
- Searched `F:\Repos\F4-Animation-Research\unpacked`. The Pip-Boy raise/use animation set is **first-person only** (`Character\_1stPerson\Behaviors\Pipboy.hkx`, `Character\_1stPerson\Animations\Pipboy\*`).
|
||||||
|
- The third-person root graph (`Character\Behaviors\RaiderRootBehavior.xml`, used by the humanoid proxy) exposes only limited Pip-Boy data:
|
||||||
|
- `PipboyOffsetAnimationGen` (`BSOffsetAnimationGenerator`) blending `Animations\PipboyOffset.hkt`, whose `fOffsetVariable` is bound to variable index 186 = `bIsFemale` (a gender-selected, effectively always-on wrist-clearance additive, not an open/close toggle).
|
||||||
|
- `pipboyUp` is variable index 289 (`VARIABLE_TYPE_BOOL`) but is **not** in `eventNames` (event index -1) and is bound to no node in this behavior.
|
||||||
|
- `bBlockPipboy` is variable index 191 (`VARIABLE_TYPE_BOOL`); `bBlockPipboyStart`/`bBlockPipboyStop` are events 785/786 but appear in `0` transitions/triggers here.
|
||||||
|
- Conclusion: the stock third-person humanoid behavior has no open/close Pip-Boy transition, so neither graph events nor idle forms can reproduce the first-person raise pose. The only third-person Pip-Boy clips are `DynamicAnims\PipboyIdle1/2/3` (wrist-glance idles) and furniture-chair flavor poses, none exposed as a behavior event/state.
|
||||||
|
- Code change: drive the real `pipboyUp` / `bBlockPipboy` bool graph variables with write + read-back diagnostics; removed the fictional event candidate list; kept `TESIdleForm` playback as a fallback only when the graph does not expose the variable.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Ran `xmake build`; plugin build succeeded (pre-existing C4702 warnings in `F4TProxyActorController.cpp` only).
|
||||||
|
- In-game re-test still required to confirm whether the proxy graph even exposes `pipboyUp` (`pipboyUpWritten` / `pipboyUpReadBack` in the new log line).
|
||||||
|
|
||||||
|
### Decision
|
||||||
|
- Per project owner: no Pip-Boy animation for now. A custom third-person animation may be authored later when an animator is available.
|
||||||
|
- Removed the proxy Pip-Boy animation-trigger code (guessed events, `pipboyUp`/`bBlockPipboy` variable writes, and the `TESIdleForm` fallback) and their now-unused helpers/includes. Kept the lightweight `isPipboyOpen` state sync (capture, relay, storage, and diagnostics) so a future custom animation has the signal available. Rebuilt successfully.
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- A faithful third-person "using Pip-Boy" pose is not reachable from the stock behavior graph; achieving it would require custom animation work (porting the first-person raise to a third-person additive/idle plus a behavior hook).
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Revisit only if a custom third-person Pip-Boy animation is authored; the `isPipboyOpen` flag is already available to drive it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-30 - Pip-Boy Idle Playback Fallback
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Diagnosed the first in-game Pip-Boy sync attempt and added an idle-form fallback for proxy playback.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `plugin/src/F4TProxyAnimationSync.cpp`
|
||||||
|
- `changelog.md`
|
||||||
|
- `docs/dev-log.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
- `CommonwealthOnline.log` showed `isPipboyOpen=true` reached the receiver, so capture and relay worked.
|
||||||
|
- The proxy rejected every diagnostic `NotifyAnimationGraph` Pip-Boy event candidate, matching the broader observation that raw locomotion graph events also return `graphAccepted=false` on the current proxy.
|
||||||
|
- Added a `TESIdleForm` scan for Pip-Boy-related idle records and a fallback that tries `AIProcess::PlayIdle` for open/close matching idles when graph event notification fails.
|
||||||
|
- Fixed idle-state change detection so missing locomotion tier does not make stationary proxy animation state log as changed every frame.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Ran `build.bat`; plugin build succeeded.
|
||||||
|
- In-game confirmation is still needed to see whether a matching Pip-Boy idle exists and returns `played=true` on the runtime proxy.
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- The fallback depends on loaded `TESIdleForm` records whose editor ID, anim event, or anim file includes `pip` and `open`/`close`.
|
||||||
|
- If no playable idle is found, the next step is using extracted HKX/AnimTextData to identify the actual Pip-Boy subgraph or adding a purpose-built proxy idle.
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Re-test with two clients, open/close Pip-Boy, and check `CommonwealthOnline.log` for `Runtime proxy Pip-Boy idle candidate` and `played=true/false` lines.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-30 - Pip-Boy State Animation Sync
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Added a backward-compatible Pip-Boy state flag to transform sync so remote proxies can detect when another player opens or closes the Pip-Boy and attempt matching graph events.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `plugin/include/F4TRemotePlayerState.h`
|
||||||
|
- `plugin/include/F4TNetworking.h`
|
||||||
|
- `plugin/include/F4TProxyAnimationSync.h`
|
||||||
|
- `plugin/src/F4TNetworking.cpp`
|
||||||
|
- `plugin/src/F4TProxyActorController.cpp`
|
||||||
|
- `plugin/src/F4TProxyAnimationSync.cpp`
|
||||||
|
- `plugin/src/main.cpp`
|
||||||
|
- `server/fake_client.py`
|
||||||
|
- `server/fake_player.py`
|
||||||
|
- `protocol/packets.md`
|
||||||
|
- `protocol/player-sync.md`
|
||||||
|
- `docs/protocol.md`
|
||||||
|
- `docs/protocol-phase2-extensions.md`
|
||||||
|
- `docs/architecture.md`
|
||||||
|
- `server/README.md`
|
||||||
|
- `changelog.md`
|
||||||
|
- `docs/dev-log.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
- Added optional `isPipboyOpen` to transform packets, remote-player state, fake-client output, and fake-player payloads.
|
||||||
|
- Captured local Pip-Boy activity from the C++ polling path using the Pip-Boy menu and `PipboyManager` state, so open/close changes trigger immediate transform sends.
|
||||||
|
- Added proxy-side edge detection for `isPipboyOpen` and a small diagnostic candidate event list for Pip-Boy open/close graph events.
|
||||||
|
- Logged graph acceptance for each candidate event so extracted HKX `AnimEventinfo` or Pip-Boy subgraph data can replace the diagnostic list once the real event names are confirmed.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Ran `python -m py_compile server\fake_client.py server\fake_player.py`; syntax check passed.
|
||||||
|
- Ran `build.bat`; plugin build succeeded.
|
||||||
|
- Build still reports pre-existing unreachable-code warnings in `F4TLocalAnimationGraphDebug.cpp` and `F4TProxyActorController.cpp`.
|
||||||
|
- In-game two-client confirmation is still required to verify which Pip-Boy event name, if any, the current proxy graph accepts.
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- The proxy Pip-Boy graph event names are still diagnostic candidates, not confirmed names from extracted HKX data.
|
||||||
|
- The first pass mirrors state/pose attempts only; it does not replicate the Pip-Boy UI screen on the remote actor.
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Open and close Pip-Boy with two connected Fallout 4 instances and check `CommonwealthOnline.log` for `isPipboyOpen` send/receive state and `graphAccepted` results.
|
||||||
|
- Use `F4-Animation-Research/extracted` `AnimTextData` / `AnimEventinfo` or Pip-Boy subgraph data to replace failed candidate event names.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-06-29 - Late-Join Appearance Snapshot Fix
|
## 2026-06-29 - Late-Join Appearance Snapshot Fix
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ This document describes the Phase 2 protocol extensions to support actor state f
|
|||||||
"isSneaking": false,
|
"isSneaking": false,
|
||||||
"isJumping": false,
|
"isJumping": false,
|
||||||
"weaponDrawn": false,
|
"weaponDrawn": false,
|
||||||
|
"isPipboyOpen": false,
|
||||||
"movementSpeed": 180.5,
|
"movementSpeed": 180.5,
|
||||||
"clientTime": 1717424000.123,
|
"clientTime": 1717424000.123,
|
||||||
"serverTime": 1717424000.456,
|
"serverTime": 1717424000.456,
|
||||||
@@ -66,6 +67,7 @@ This document describes the Phase 2 protocol extensions to support actor state f
|
|||||||
| `isSneaking` | Boolean | NO | Local player is sneaking (default: false) |
|
| `isSneaking` | Boolean | NO | Local player is sneaking (default: false) |
|
||||||
| `isJumping` | Boolean | NO | Local player is jumping (default: false) |
|
| `isJumping` | Boolean | NO | Local player is jumping (default: false) |
|
||||||
| `weaponDrawn` | Boolean | NO | Local player has weapon drawn (default: false) |
|
| `weaponDrawn` | Boolean | NO | Local player has weapon drawn (default: false) |
|
||||||
|
| `isPipboyOpen` | Boolean | NO | Local player has Pip-Boy open, opening, closing, or raising (default: false) |
|
||||||
| `movementSpeed` | Number (float) | NO | Movement speed in game units/frame (default: 0.0) |
|
| `movementSpeed` | Number (float) | NO | Movement speed in game units/frame (default: 0.0) |
|
||||||
| `clientTime` | Number (double) | NO | Client-side timestamp (optional) |
|
| `clientTime` | Number (double) | NO | Client-side timestamp (optional) |
|
||||||
| `serverTime` | Number (double) | NO | Server timestamp for diagnostics (optional) |
|
| `serverTime` | Number (double) | NO | Server timestamp for diagnostics (optional) |
|
||||||
|
|||||||
+4
-3
@@ -60,7 +60,8 @@ If UDP discovery is unavailable, the plugin may fall back to probing
|
|||||||
|
|
||||||
The protocol should be testable outside Fallout 4 before it is used inside the F4SE plugin.
|
The protocol should be testable outside Fallout 4 before it is used inside the F4SE plugin.
|
||||||
|
|
||||||
`transform` packets carry position, movement state, optional `equippedItems`
|
`transform` packets carry position, movement/action state such as locomotion,
|
||||||
apparel snapshots, and optional `appearance` snapshots for best-effort proxy
|
weapon, and Pip-Boy flags, optional `equippedItems` apparel snapshots, and
|
||||||
body/face visuals. The canonical field list and compatibility rules live in
|
optional `appearance` snapshots for best-effort proxy body/face visuals. The
|
||||||
|
canonical field list and compatibility rules live in
|
||||||
[`protocol/packets.md`](../protocol/packets.md).
|
[`protocol/packets.md`](../protocol/packets.md).
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace F4T::Networking
|
|||||||
bool a_isJumping = false,
|
bool a_isJumping = false,
|
||||||
bool a_isCrouching = false,
|
bool a_isCrouching = false,
|
||||||
bool a_weaponDrawn = false,
|
bool a_weaponDrawn = false,
|
||||||
|
bool a_isPipboyOpen = false,
|
||||||
float a_movementSpeed = 0.0F,
|
float a_movementSpeed = 0.0F,
|
||||||
float a_animationGraphSpeed = -1.0F,
|
float a_animationGraphSpeed = -1.0F,
|
||||||
std::span<const F4T::RemotePlayerState::RemoteEquippedItem> a_equippedItems = {},
|
std::span<const F4T::RemotePlayerState::RemoteEquippedItem> a_equippedItems = {},
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ namespace F4T::ProxyAnimationSync
|
|||||||
bool lastIsJumping{ false };
|
bool lastIsJumping{ false };
|
||||||
bool lastIsCrouching{ false };
|
bool lastIsCrouching{ false };
|
||||||
bool lastWeaponDrawn{ false };
|
bool lastWeaponDrawn{ false };
|
||||||
|
bool lastIsPipboyOpen{ false };
|
||||||
float lastGraphSpeed{ 0.0F };
|
float lastGraphSpeed{ 0.0F };
|
||||||
float lastDirection{ 0.0F };
|
float lastDirection{ 0.0F };
|
||||||
std::optional<LocomotionTier> lastLocomotionTier{};
|
std::optional<LocomotionTier> lastLocomotionTier{};
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ namespace F4T::RemotePlayerState
|
|||||||
bool isJumping{ false };
|
bool isJumping{ false };
|
||||||
bool isCrouching{ false };
|
bool isCrouching{ false };
|
||||||
bool weaponDrawn{ false };
|
bool weaponDrawn{ false };
|
||||||
|
bool isPipboyOpen{ false };
|
||||||
float movementSpeed{ 0.0F };
|
float movementSpeed{ 0.0F };
|
||||||
float animationGraphSpeed{ -1.0F };
|
float animationGraphSpeed{ -1.0F };
|
||||||
bool hasEquipmentUpdate{ false };
|
bool hasEquipmentUpdate{ false };
|
||||||
|
|||||||
@@ -723,6 +723,7 @@ namespace
|
|||||||
ReadBool(a_packet, "isJumping").value_or(false),
|
ReadBool(a_packet, "isJumping").value_or(false),
|
||||||
ReadBool(a_packet, "isCrouching").value_or(false),
|
ReadBool(a_packet, "isCrouching").value_or(false),
|
||||||
ReadBool(a_packet, "weaponDrawn").value_or(false),
|
ReadBool(a_packet, "weaponDrawn").value_or(false),
|
||||||
|
ReadBool(a_packet, "isPipboyOpen").value_or(false),
|
||||||
static_cast<float>(movementSpeed),
|
static_cast<float>(movementSpeed),
|
||||||
static_cast<float>(animationGraphSpeed),
|
static_cast<float>(animationGraphSpeed),
|
||||||
hasEquipmentUpdate,
|
hasEquipmentUpdate,
|
||||||
@@ -755,7 +756,7 @@ namespace
|
|||||||
|
|
||||||
if (ShouldLogRemoteMovementState(*playerId)) {
|
if (ShouldLogRemoteMovementState(*playerId)) {
|
||||||
LogInfoWithLocalPlayerPrefix(std::format(
|
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.playerId,
|
||||||
remoteState.isMoving,
|
remoteState.isMoving,
|
||||||
remoteState.isSprinting,
|
remoteState.isSprinting,
|
||||||
@@ -763,6 +764,7 @@ namespace
|
|||||||
remoteState.isJumping,
|
remoteState.isJumping,
|
||||||
remoteState.isCrouching,
|
remoteState.isCrouching,
|
||||||
remoteState.weaponDrawn,
|
remoteState.weaponDrawn,
|
||||||
|
remoteState.isPipboyOpen,
|
||||||
remoteState.movementSpeed,
|
remoteState.movementSpeed,
|
||||||
remoteState.actorStateFlags1,
|
remoteState.actorStateFlags1,
|
||||||
remoteState.actorStateFlags2));
|
remoteState.actorStateFlags2));
|
||||||
@@ -1083,6 +1085,7 @@ namespace F4T::Networking
|
|||||||
bool a_isJumping,
|
bool a_isJumping,
|
||||||
bool a_isCrouching,
|
bool a_isCrouching,
|
||||||
bool a_weaponDrawn,
|
bool a_weaponDrawn,
|
||||||
|
bool a_isPipboyOpen,
|
||||||
float a_movementSpeed,
|
float a_movementSpeed,
|
||||||
float a_animationGraphSpeed,
|
float a_animationGraphSpeed,
|
||||||
std::span<const F4T::RemotePlayerState::RemoteEquippedItem> a_equippedItems,
|
std::span<const F4T::RemotePlayerState::RemoteEquippedItem> a_equippedItems,
|
||||||
@@ -1109,6 +1112,7 @@ namespace F4T::Networking
|
|||||||
{ "isJumping", a_isJumping },
|
{ "isJumping", a_isJumping },
|
||||||
{ "isCrouching", a_isCrouching },
|
{ "isCrouching", a_isCrouching },
|
||||||
{ "weaponDrawn", a_weaponDrawn },
|
{ "weaponDrawn", a_weaponDrawn },
|
||||||
|
{ "isPipboyOpen", a_isPipboyOpen },
|
||||||
{ "movementSpeed", a_movementSpeed }
|
{ "movementSpeed", a_movementSpeed }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -360,6 +360,7 @@ namespace
|
|||||||
std::optional<bool> lastObservedSneakingState;
|
std::optional<bool> lastObservedSneakingState;
|
||||||
std::optional<bool> lastObservedJumpingState;
|
std::optional<bool> lastObservedJumpingState;
|
||||||
std::optional<bool> lastObservedWeaponDrawnState;
|
std::optional<bool> lastObservedWeaponDrawnState;
|
||||||
|
std::optional<bool> lastObservedPipboyOpenState;
|
||||||
std::optional<std::string> lastObservedMovementType;
|
std::optional<std::string> lastObservedMovementType;
|
||||||
std::optional<std::string> lastObservedRemoteCellId;
|
std::optional<std::string> lastObservedRemoteCellId;
|
||||||
std::optional<ProxyAnimationSpeedBucket> lastObservedMovementSpeedBucket;
|
std::optional<ProxyAnimationSpeedBucket> lastObservedMovementSpeedBucket;
|
||||||
@@ -6052,6 +6053,7 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
|||||||
a_slot.lastObservedSneakingState.reset();
|
a_slot.lastObservedSneakingState.reset();
|
||||||
a_slot.lastObservedJumpingState.reset();
|
a_slot.lastObservedJumpingState.reset();
|
||||||
a_slot.lastObservedWeaponDrawnState.reset();
|
a_slot.lastObservedWeaponDrawnState.reset();
|
||||||
|
a_slot.lastObservedPipboyOpenState.reset();
|
||||||
a_slot.lastObservedMovementType.reset();
|
a_slot.lastObservedMovementType.reset();
|
||||||
a_slot.lastObservedMovementSpeedBucket.reset();
|
a_slot.lastObservedMovementSpeedBucket.reset();
|
||||||
ResetSlotVisualSneakTracking(a_slot);
|
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.lastObservedSneakingState || *a_slot.lastObservedSneakingState != a_remotePlayer.isSneaking ||
|
||||||
!a_slot.lastObservedJumpingState || *a_slot.lastObservedJumpingState != a_remotePlayer.isJumping ||
|
!a_slot.lastObservedJumpingState || *a_slot.lastObservedJumpingState != a_remotePlayer.isJumping ||
|
||||||
!a_slot.lastObservedWeaponDrawnState || *a_slot.lastObservedWeaponDrawnState != a_remotePlayer.weaponDrawn ||
|
!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.lastObservedMovementType || *a_slot.lastObservedMovementType != a_remotePlayer.movementType ||
|
||||||
!a_slot.lastObservedMovementSpeedBucket || *a_slot.lastObservedMovementSpeedBucket != speedBucket;
|
!a_slot.lastObservedMovementSpeedBucket || *a_slot.lastObservedMovementSpeedBucket != speedBucket;
|
||||||
|
|
||||||
if (shouldLogInitial || changed) {
|
if (shouldLogInitial || changed) {
|
||||||
LogInfoWithLocalPlayerPrefix(std::format(
|
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",
|
shouldLogInitial ? "initial" : "changed",
|
||||||
a_remotePlayer.playerId,
|
a_remotePlayer.playerId,
|
||||||
a_proxy.GetFormID(),
|
a_proxy.GetFormID(),
|
||||||
@@ -6479,6 +6482,7 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
|||||||
a_remotePlayer.isSneaking,
|
a_remotePlayer.isSneaking,
|
||||||
a_remotePlayer.isJumping,
|
a_remotePlayer.isJumping,
|
||||||
a_remotePlayer.weaponDrawn,
|
a_remotePlayer.weaponDrawn,
|
||||||
|
a_remotePlayer.isPipboyOpen,
|
||||||
a_remotePlayer.movementType));
|
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.lastObservedSneakingState = a_remotePlayer.isSneaking;
|
||||||
a_slot.lastObservedJumpingState = a_remotePlayer.isJumping;
|
a_slot.lastObservedJumpingState = a_remotePlayer.isJumping;
|
||||||
a_slot.lastObservedWeaponDrawnState = a_remotePlayer.weaponDrawn;
|
a_slot.lastObservedWeaponDrawnState = a_remotePlayer.weaponDrawn;
|
||||||
|
a_slot.lastObservedPipboyOpenState = a_remotePlayer.isPipboyOpen;
|
||||||
a_slot.lastObservedMovementType = a_remotePlayer.movementType;
|
a_slot.lastObservedMovementType = a_remotePlayer.movementType;
|
||||||
a_slot.lastObservedMovementSpeedBucket = speedBucket;
|
a_slot.lastObservedMovementSpeedBucket = speedBucket;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "RE/T/TESObjectREFR.h"
|
#include "RE/T/TESObjectREFR.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -257,6 +256,7 @@ namespace
|
|||||||
bool isJumping{ false };
|
bool isJumping{ false };
|
||||||
bool isCrouching{ false };
|
bool isCrouching{ false };
|
||||||
bool weaponDrawn{ false };
|
bool weaponDrawn{ false };
|
||||||
|
bool isPipboyOpen{ false };
|
||||||
float graphSpeed{ 0.0F };
|
float graphSpeed{ 0.0F };
|
||||||
float direction{ 0.0F };
|
float direction{ 0.0F };
|
||||||
};
|
};
|
||||||
@@ -273,6 +273,7 @@ namespace
|
|||||||
desired.isJumping = a_remotePlayer.isJumping;
|
desired.isJumping = a_remotePlayer.isJumping;
|
||||||
desired.isCrouching = a_remotePlayer.isCrouching;
|
desired.isCrouching = a_remotePlayer.isCrouching;
|
||||||
desired.weaponDrawn = a_remotePlayer.weaponDrawn;
|
desired.weaponDrawn = a_remotePlayer.weaponDrawn;
|
||||||
|
desired.isPipboyOpen = a_remotePlayer.isPipboyOpen;
|
||||||
desired.graphSpeed = ComputeGraphSpeed(a_remotePlayer);
|
desired.graphSpeed = ComputeGraphSpeed(a_remotePlayer);
|
||||||
desired.direction = a_remotePlayer.angleZ;
|
desired.direction = a_remotePlayer.angleZ;
|
||||||
return desired;
|
return desired;
|
||||||
@@ -333,13 +334,18 @@ namespace
|
|||||||
const auto currentTier =
|
const auto currentTier =
|
||||||
F4T::ProxyAnimationSync::ComputeLocomotionTier(a_desired.graphSpeed, a_desired.isSprinting);
|
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 ||
|
return a_state.lastIsMoving != a_desired.isMoving ||
|
||||||
a_state.lastIsSprinting != a_desired.isSprinting ||
|
a_state.lastIsSprinting != a_desired.isSprinting ||
|
||||||
a_state.lastIsSneaking != a_desired.isSneaking ||
|
a_state.lastIsSneaking != a_desired.isSneaking ||
|
||||||
a_state.lastIsJumping != a_desired.isJumping ||
|
a_state.lastIsJumping != a_desired.isJumping ||
|
||||||
a_state.lastIsCrouching != a_desired.isCrouching ||
|
a_state.lastIsCrouching != a_desired.isCrouching ||
|
||||||
a_state.lastWeaponDrawn != a_desired.weaponDrawn ||
|
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.lastGraphSpeed, a_desired.graphSpeed) ||
|
||||||
AreFloatsDifferent(a_state.lastDirection, a_desired.direction);
|
AreFloatsDifferent(a_state.lastDirection, a_desired.direction);
|
||||||
}
|
}
|
||||||
@@ -630,7 +636,7 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
LogInfoWithLocalPlayerPrefix(std::format(
|
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_initial ? "initial" : "changed",
|
||||||
a_remotePlayerId,
|
a_remotePlayerId,
|
||||||
a_actorId,
|
a_actorId,
|
||||||
@@ -641,6 +647,7 @@ namespace
|
|||||||
a_desired.isJumping,
|
a_desired.isJumping,
|
||||||
a_desired.isCrouching,
|
a_desired.isCrouching,
|
||||||
a_desired.weaponDrawn,
|
a_desired.weaponDrawn,
|
||||||
|
a_desired.isPipboyOpen,
|
||||||
a_desired.graphSpeed,
|
a_desired.graphSpeed,
|
||||||
a_desired.direction));
|
a_desired.direction));
|
||||||
}
|
}
|
||||||
@@ -735,11 +742,12 @@ namespace F4T::ProxyAnimationSync
|
|||||||
const auto haveSpeedSmoothed =
|
const auto haveSpeedSmoothed =
|
||||||
diagHolder->GetGraphVariableImplFloat(RE::BSFixedString{ F4T::ProxyAnimationSync::GraphVar::kSpeedSmoothed }, readSpeedSmoothed);
|
diagHolder->GetGraphVariableImplFloat(RE::BSFixedString{ F4T::ProxyAnimationSync::GraphVar::kSpeedSmoothed }, readSpeedSmoothed);
|
||||||
LogInfoWithLocalPlayerPrefix(std::format(
|
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,
|
a_remotePlayer.playerId,
|
||||||
actorId,
|
actorId,
|
||||||
a_remotePlayer.isMoving,
|
a_remotePlayer.isMoving,
|
||||||
a_remotePlayer.isSprinting,
|
a_remotePlayer.isSprinting,
|
||||||
|
a_remotePlayer.isPipboyOpen,
|
||||||
a_remotePlayer.movementSpeed,
|
a_remotePlayer.movementSpeed,
|
||||||
a_remotePlayer.animationGraphSpeed,
|
a_remotePlayer.animationGraphSpeed,
|
||||||
desired.graphSpeed,
|
desired.graphSpeed,
|
||||||
@@ -780,6 +788,7 @@ namespace F4T::ProxyAnimationSync
|
|||||||
a_state.lastIsJumping = desired.isJumping;
|
a_state.lastIsJumping = desired.isJumping;
|
||||||
a_state.lastIsCrouching = desired.isCrouching;
|
a_state.lastIsCrouching = desired.isCrouching;
|
||||||
a_state.lastWeaponDrawn = desired.weaponDrawn;
|
a_state.lastWeaponDrawn = desired.weaponDrawn;
|
||||||
|
a_state.lastIsPipboyOpen = desired.isPipboyOpen;
|
||||||
a_state.lastGraphSpeed = desired.graphSpeed;
|
a_state.lastGraphSpeed = desired.graphSpeed;
|
||||||
a_state.lastDirection = desired.direction;
|
a_state.lastDirection = desired.direction;
|
||||||
if (desired.isJumping) {
|
if (desired.isJumping) {
|
||||||
|
|||||||
+30
-1
@@ -14,6 +14,9 @@
|
|||||||
#include "RE/B/BipedAnim.h"
|
#include "RE/B/BipedAnim.h"
|
||||||
#include "RE/B/BSFixedString.h"
|
#include "RE/B/BSFixedString.h"
|
||||||
#include "RE/I/IAnimationGraphManagerHolder.h"
|
#include "RE/I/IAnimationGraphManagerHolder.h"
|
||||||
|
#include "RE/P/PipboyManager.h"
|
||||||
|
#include "RE/P/PipboyMenu.h"
|
||||||
|
#include "RE/U/UI.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -51,6 +54,7 @@ namespace
|
|||||||
bool isJumping;
|
bool isJumping;
|
||||||
bool isCrouching;
|
bool isCrouching;
|
||||||
bool weaponDrawn;
|
bool weaponDrawn;
|
||||||
|
bool isPipboyOpen;
|
||||||
float movementSpeed;
|
float movementSpeed;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -422,6 +426,22 @@ namespace
|
|||||||
return false;
|
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(
|
bool ShouldLogSneakDetection(
|
||||||
const SneakDetectionState& a_sneakDetection,
|
const SneakDetectionState& a_sneakDetection,
|
||||||
std::chrono::steady_clock::time_point a_currentTime)
|
std::chrono::steady_clock::time_point a_currentTime)
|
||||||
@@ -512,6 +532,7 @@ namespace
|
|||||||
heldJumping,
|
heldJumping,
|
||||||
GetCrouchState(a_player),
|
GetCrouchState(a_player),
|
||||||
a_player.GetWeaponMagicDrawn(),
|
a_player.GetWeaponMagicDrawn(),
|
||||||
|
IsLocalPipboyOpen(),
|
||||||
a_movementSpeed
|
a_movementSpeed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -545,7 +566,8 @@ namespace
|
|||||||
a_previous.isSneaking != a_current.isSneaking ||
|
a_previous.isSneaking != a_current.isSneaking ||
|
||||||
a_previous.isJumping != a_current.isJumping ||
|
a_previous.isJumping != a_current.isJumping ||
|
||||||
a_previous.isCrouching != a_current.isCrouching ||
|
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)
|
bool HasMeaningfulTransformDelta(const PlayerTransform& a_previous, const PlayerTransform& a_current)
|
||||||
@@ -726,6 +748,7 @@ namespace
|
|||||||
debugMovementState.isJumping,
|
debugMovementState.isJumping,
|
||||||
debugMovementState.isCrouching,
|
debugMovementState.isCrouching,
|
||||||
debugMovementState.weaponDrawn,
|
debugMovementState.weaponDrawn,
|
||||||
|
debugMovementState.isPipboyOpen,
|
||||||
debugMovementState.movementSpeed,
|
debugMovementState.movementSpeed,
|
||||||
-1.0F,
|
-1.0F,
|
||||||
currentEquipmentState,
|
currentEquipmentState,
|
||||||
@@ -818,6 +841,7 @@ namespace
|
|||||||
networkMovementState.isJumping,
|
networkMovementState.isJumping,
|
||||||
networkMovementState.isCrouching,
|
networkMovementState.isCrouching,
|
||||||
networkMovementState.weaponDrawn,
|
networkMovementState.weaponDrawn,
|
||||||
|
networkMovementState.isPipboyOpen,
|
||||||
networkMovementState.movementSpeed,
|
networkMovementState.movementSpeed,
|
||||||
animationGraphSpeed,
|
animationGraphSpeed,
|
||||||
currentEquipmentState,
|
currentEquipmentState,
|
||||||
@@ -839,6 +863,11 @@ namespace
|
|||||||
} else if (hasAppearanceChanged) {
|
} else if (hasAppearanceChanged) {
|
||||||
LogInfoWithLocalPlayerPrefix("Detected local appearance change. Sending immediate transform update.");
|
LogInfoWithLocalPlayerPrefix("Detected local appearance change. Sending immediate transform update.");
|
||||||
lastLogTime = currentTime;
|
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) {
|
} else if (currentTime - lastLogTime >= kTransformLogInterval) {
|
||||||
LogPlayerTransform(currentTransform);
|
LogPlayerTransform(currentTransform);
|
||||||
lastLogTime = currentTime;
|
lastLogTime = currentTime;
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ Example after server processing:
|
|||||||
"isSneaking": false,
|
"isSneaking": false,
|
||||||
"isJumping": false,
|
"isJumping": false,
|
||||||
"weaponDrawn": false,
|
"weaponDrawn": false,
|
||||||
|
"isPipboyOpen": false,
|
||||||
"movementSpeed": 186.4,
|
"movementSpeed": 186.4,
|
||||||
"equippedItems": [
|
"equippedItems": [
|
||||||
{ "slot": "body", "formId": "0001F66A" },
|
{ "slot": "body", "formId": "0001F66A" },
|
||||||
@@ -106,6 +107,7 @@ isSprinting Optional movement-state flag; defaults to false when missing.
|
|||||||
isSneaking Optional movement-state flag; defaults to false when missing.
|
isSneaking Optional movement-state flag; defaults to false when missing.
|
||||||
isJumping Optional movement-state flag; defaults to false when missing.
|
isJumping Optional movement-state flag; defaults to false when missing.
|
||||||
weaponDrawn Optional weapon drawn state; defaults to false when missing.
|
weaponDrawn Optional weapon drawn state; defaults to false when missing.
|
||||||
|
isPipboyOpen Optional Pip-Boy open/raising/closing state; defaults to false when missing.
|
||||||
movementSpeed Optional derived movement speed in game units per second; defaults to 0.0 when missing.
|
movementSpeed Optional derived movement speed in game units per second; defaults to 0.0 when missing.
|
||||||
equippedItems Optional full snapshot of tracked visible apparel slots. Each entry has a string `slot` and hex-string `formId`; an empty `formId` means the slot is intentionally unequipped.
|
equippedItems Optional full snapshot of tracked visible apparel slots. Each entry has a string `slot` and hex-string `formId`; an empty `formId` means the slot is intentionally unequipped.
|
||||||
appearance Optional versioned player appearance snapshot for runtime proxy visuals.
|
appearance Optional versioned player appearance snapshot for runtime proxy visuals.
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ isSneaking
|
|||||||
isJumping
|
isJumping
|
||||||
isCrouching
|
isCrouching
|
||||||
weaponDrawn
|
weaponDrawn
|
||||||
|
isPipboyOpen
|
||||||
movementSpeed
|
movementSpeed
|
||||||
animationGraphSpeed
|
animationGraphSpeed
|
||||||
equippedItems
|
equippedItems
|
||||||
@@ -78,8 +79,8 @@ Required transform packet fields are `type`, `playerId`, `x`, `y`, `z`,
|
|||||||
`angleZ`, and `cellId`. `worldspaceId`, `movementType`, `clientTime`, and
|
`angleZ`, and `cellId`. `worldspaceId`, `movementType`, `clientTime`, and
|
||||||
`serverTime` are optional; interior test cells may omit `worldspaceId`.
|
`serverTime` are optional; interior test cells may omit `worldspaceId`.
|
||||||
Movement state fields are also optional and default to not moving, not
|
Movement state fields are also optional and default to not moving, not
|
||||||
sprinting, not sneaking, not jumping, weapon holstered, and speed `0.0` when
|
sprinting, not sneaking, not jumping, weapon holstered, Pip-Boy closed, and
|
||||||
missing.
|
speed `0.0` when missing.
|
||||||
|
|
||||||
`equippedItems` is also optional. New clients send it as a complete snapshot of
|
`equippedItems` is also optional. New clients send it as a complete snapshot of
|
||||||
tracked visible apparel slots with `{ slot, formId }` entries; empty `formId`
|
tracked visible apparel slots with `{ slot, formId }` entries; empty `formId`
|
||||||
@@ -125,7 +126,7 @@ player exists.
|
|||||||
local test cell, then snaps back to a valid same-cell remote player before
|
local test cell, then snaps back to a valid same-cell remote player before
|
||||||
resuming smoothing.
|
resuming smoothing.
|
||||||
- Transform packets include movement state data (`isMoving`, `movementSpeed`,
|
- Transform packets include movement state data (`isMoving`, `movementSpeed`,
|
||||||
`isSprinting`, `isSneaking`, `isJumping`, `weaponDrawn`).
|
`isSprinting`, `isSneaking`, `isJumping`, `weaponDrawn`, `isPipboyOpen`).
|
||||||
- Transform packets can include optional `equippedItems` snapshots for visible
|
- Transform packets can include optional `equippedItems` snapshots for visible
|
||||||
clothing, armor, hats, and eyewear slots. The game-thread proxy controller
|
clothing, armor, hats, and eyewear slots. The game-thread proxy controller
|
||||||
applies those snapshots after proxy 3D is loaded.
|
applies those snapshots after proxy 3D is loaded.
|
||||||
|
|||||||
+3
-3
@@ -14,9 +14,9 @@ can send a `discover` packet (see `docs/protocol.md`) and receive a
|
|||||||
`discoverResponse` with the game port and current player count.
|
`discoverResponse` with the game port and current player count.
|
||||||
|
|
||||||
Transform packets may include optional movement-state fields such as
|
Transform packets may include optional movement-state fields such as
|
||||||
`isMoving`, `isSprinting`, `isSneaking`, `isJumping`, `isCrouching`, `weaponDrawn`, and
|
`isMoving`, `isSprinting`, `isSneaking`, `isJumping`, `isCrouching`, `weaponDrawn`,
|
||||||
`movementSpeed`. The server preserves these fields automatically because it
|
`isPipboyOpen`, and `movementSpeed`. The server preserves these fields automatically
|
||||||
broadcasts the original transform packet after adding server-owned fields.
|
because it broadcasts the original transform packet after adding server-owned fields.
|
||||||
|
|
||||||
This is still a local prototype. It does not make Fallout 4 multiplayer
|
This is still a local prototype. It does not make Fallout 4 multiplayer
|
||||||
playable yet.
|
playable yet.
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ def update_remote_player_state(packet: dict[str, Any]) -> None:
|
|||||||
"isJumping": get_optional_bool(packet, "isJumping"),
|
"isJumping": get_optional_bool(packet, "isJumping"),
|
||||||
"isCrouching": get_optional_bool(packet, "isCrouching"),
|
"isCrouching": get_optional_bool(packet, "isCrouching"),
|
||||||
"weaponDrawn": get_optional_bool(packet, "weaponDrawn"),
|
"weaponDrawn": get_optional_bool(packet, "weaponDrawn"),
|
||||||
|
"isPipboyOpen": get_optional_bool(packet, "isPipboyOpen"),
|
||||||
"movementSpeed": get_optional_float(packet, "movementSpeed"),
|
"movementSpeed": get_optional_float(packet, "movementSpeed"),
|
||||||
"actorStateFlags1": get_optional_uint32(packet, "actorStateFlags1"),
|
"actorStateFlags1": get_optional_uint32(packet, "actorStateFlags1"),
|
||||||
"actorStateFlags2": get_optional_uint32(packet, "actorStateFlags2"),
|
"actorStateFlags2": get_optional_uint32(packet, "actorStateFlags2"),
|
||||||
@@ -254,7 +255,8 @@ def print_remote_player(player: dict[str, Any]) -> None:
|
|||||||
"Movement State: "
|
"Movement State: "
|
||||||
f"moving={player['isMoving']}, sprinting={player['isSprinting']}, "
|
f"moving={player['isMoving']}, sprinting={player['isSprinting']}, "
|
||||||
f"sneaking={player['isSneaking']}, jumping={player['isJumping']}, "
|
f"sneaking={player['isSneaking']}, jumping={player['isJumping']}, "
|
||||||
f"crouching={player['isCrouching']}, weaponDrawn={player['weaponDrawn']}, speed={player['movementSpeed']:.1f}"
|
f"crouching={player['isCrouching']}, weaponDrawn={player['weaponDrawn']}, "
|
||||||
|
f"pipboyOpen={player['isPipboyOpen']}, speed={player['movementSpeed']:.1f}"
|
||||||
),
|
),
|
||||||
f"Actor State: flags1={player['actorStateFlags1']:08X}, flags2={player['actorStateFlags2']:08X}",
|
f"Actor State: flags1={player['actorStateFlags1']:08X}, flags2={player['actorStateFlags2']:08X}",
|
||||||
f"Equipment: {equipment_text}",
|
f"Equipment: {equipment_text}",
|
||||||
@@ -294,7 +296,8 @@ def print_remote_players() -> None:
|
|||||||
f"angleZ={player['angleZ']:.2f}, movementType={player['movementType']}, "
|
f"angleZ={player['angleZ']:.2f}, movementType={player['movementType']}, "
|
||||||
f"moving={player['isMoving']}, sprinting={player['isSprinting']}, "
|
f"moving={player['isMoving']}, sprinting={player['isSprinting']}, "
|
||||||
f"sneaking={player['isSneaking']}, jumping={player['isJumping']}, "
|
f"sneaking={player['isSneaking']}, jumping={player['isJumping']}, "
|
||||||
f"crouching={player['isCrouching']}, weaponDrawn={player['weaponDrawn']}, speed={player['movementSpeed']:.1f}, "
|
f"crouching={player['isCrouching']}, weaponDrawn={player['weaponDrawn']}, "
|
||||||
|
f"pipboyOpen={player['isPipboyOpen']}, speed={player['movementSpeed']:.1f}, "
|
||||||
f"flags1={player['actorStateFlags1']:08X}, flags2={player['actorStateFlags2']:08X}, "
|
f"flags1={player['actorStateFlags1']:08X}, flags2={player['actorStateFlags2']:08X}, "
|
||||||
f"equipment={equipment_text}, "
|
f"equipment={equipment_text}, "
|
||||||
f"appearance={appearance_text}, "
|
f"appearance={appearance_text}, "
|
||||||
|
|||||||
@@ -577,6 +577,7 @@ class FakePlayerClient:
|
|||||||
"isJumping": jumping,
|
"isJumping": jumping,
|
||||||
"isCrouching": crouching,
|
"isCrouching": crouching,
|
||||||
"weaponDrawn": False,
|
"weaponDrawn": False,
|
||||||
|
"isPipboyOpen": False,
|
||||||
"movementSpeed": movement_speed,
|
"movementSpeed": movement_speed,
|
||||||
"appearance": DEFAULT_APPEARANCE,
|
"appearance": DEFAULT_APPEARANCE,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user