Introduce complexionFormId (TXST) into RemoteAppearance and the appearance protocol so complexion textures are captured and serialized. Updates: header/state, Networking JSON parse/serialize, proxy actor comparisons, main capture logic, protocol docs, fake client/player samples, changelog and dev-log. Capture reads PlayerCharacter::complexion and serializes it, but applying to proxies is intentionally a no-op because TESNPC proxies lack a complexion field (documented in comments/dev-log). Files: plugin/include/F4TRemotePlayerState.h, plugin/src/*, protocol/packets.md, server/fake_client.py, server/fake_player.py, changelog.md, docs/dev-log.md.
135 lines
4.1 KiB
C++
135 lines
4.1 KiB
C++
#pragma once
|
|
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace F4T::RemotePlayerState
|
|
{
|
|
// Represents a remote action event captured from another player
|
|
// Used for action replay on proxy actors (combat idles, equipping, etc.)
|
|
struct RemoteActionEvent
|
|
{
|
|
std::uint32_t type{}; // Action type (see ActorMediator::ActorAction enum)
|
|
std::string eventName; // Action event name (e.g., "ActorMovementStart")
|
|
std::vector<float> animationVariablesFloat; // Float variables snapshot (Speed, Direction, etc.)
|
|
std::vector<bool> animationVariablesBool; // Bool variables snapshot (IsSprinting, IsSneaking, etc.)
|
|
std::vector<std::uint32_t> animationVariablesInt; // Int variables snapshot (weapon types, etc.)
|
|
std::uint32_t actorStateFlags1{}; // ActorState.flags1 at action time
|
|
std::uint32_t actorStateFlags2{}; // ActorState.flags2 at action time
|
|
std::chrono::steady_clock::time_point captureTime{};
|
|
};
|
|
|
|
struct RemoteEquippedItem
|
|
{
|
|
std::string slot;
|
|
std::uint32_t formId{};
|
|
};
|
|
|
|
struct RemoteAppearanceMorph
|
|
{
|
|
std::uint32_t id{};
|
|
float value{};
|
|
};
|
|
|
|
struct RemoteAppearanceTintColor
|
|
{
|
|
std::uint8_t r{};
|
|
std::uint8_t g{};
|
|
std::uint8_t b{};
|
|
std::uint8_t a{};
|
|
};
|
|
|
|
struct RemoteAppearanceMorphWeight
|
|
{
|
|
float thin{};
|
|
float muscular{};
|
|
float large{};
|
|
};
|
|
|
|
// Bone-based facial morph (TESNPC::facialBoneRegionSliderValues entry).
|
|
// Mirrors BGSCharacterMorph::Transform (position, rotation, scale).
|
|
struct RemoteAppearanceFacialBoneMorph
|
|
{
|
|
std::uint32_t id{};
|
|
float position[3]{};
|
|
float rotation[3]{};
|
|
float scale[3]{ 1.0F, 1.0F, 1.0F };
|
|
};
|
|
|
|
// Character tint layer (TESNPC/PlayerCharacter tintingData entry).
|
|
// Drives skin tone, complexion, makeup, dirt, scars, and beard shade.
|
|
struct RemoteAppearanceTint
|
|
{
|
|
std::uint16_t id{}; // idLink, matches a race tint template uniqueID
|
|
std::uint32_t type{}; // BGSCharacterTint::EntryType (mask/palette/texture)
|
|
std::uint8_t value{}; // tingingValue intensity (0-255)
|
|
std::uint32_t color{}; // ARGB packed color (palette entries only)
|
|
std::uint16_t swatch{}; // swatchID (palette entries only)
|
|
bool hasColor{ false }; // whether color/swatch are meaningful
|
|
|
|
bool operator==(const RemoteAppearanceTint&) const = default;
|
|
};
|
|
|
|
struct RemoteAppearance
|
|
{
|
|
std::uint32_t version{ 3 };
|
|
std::uint32_t raceFormId{};
|
|
float height{ 1.0F };
|
|
RemoteAppearanceMorphWeight morphWeight;
|
|
RemoteAppearanceTintColor bodyTintColor;
|
|
std::uint32_t hairColorFormId{};
|
|
std::uint32_t facialHairColorFormId{};
|
|
std::uint32_t complexionFormId{};
|
|
std::vector<std::uint32_t> headPartFormIds;
|
|
std::vector<RemoteAppearanceMorph> morphSliders;
|
|
std::vector<float> morphRegionSliders;
|
|
std::vector<RemoteAppearanceFacialBoneMorph> facialBoneMorphs;
|
|
std::vector<RemoteAppearanceTint> tints;
|
|
};
|
|
|
|
struct RemotePlayerState
|
|
{
|
|
std::uint32_t playerId{};
|
|
float x{};
|
|
float y{};
|
|
float z{};
|
|
float angleZ{};
|
|
std::string movementType;
|
|
std::string cellId;
|
|
std::string worldspaceId;
|
|
std::optional<double> clientTime;
|
|
std::optional<double> serverTime;
|
|
bool isMoving{ false };
|
|
bool isSprinting{ false };
|
|
bool isSneaking{ false };
|
|
bool isJumping{ false };
|
|
bool isCrouching{ false };
|
|
bool weaponDrawn{ false };
|
|
float movementSpeed{ 0.0F };
|
|
float animationGraphSpeed{ -1.0F };
|
|
bool hasEquipmentUpdate{ false };
|
|
std::vector<RemoteEquippedItem> equippedItems;
|
|
bool hasAppearanceUpdate{ false };
|
|
RemoteAppearance appearance;
|
|
std::chrono::steady_clock::time_point lastReceivedLocalTime{};
|
|
|
|
// Phase 2: Actor state and action events (TiltedEvolution alignment)
|
|
std::uint32_t actorStateFlags1{};
|
|
std::uint32_t actorStateFlags2{};
|
|
std::vector<RemoteActionEvent> actionEvents;
|
|
};
|
|
|
|
void SetAssignedPlayerId(std::uint32_t a_playerId);
|
|
std::optional<std::uint32_t> GetAssignedPlayerId();
|
|
bool IsAssignedPlayerId(std::uint32_t a_playerId);
|
|
void ClearAssignedPlayerId();
|
|
|
|
void UpdateRemotePlayer(RemotePlayerState a_state);
|
|
bool RemoveRemotePlayer(std::uint32_t a_playerId);
|
|
void ClearRemotePlayers();
|
|
std::vector<RemotePlayerState> GetRemotePlayerSnapshot();
|
|
}
|