using System.Collections.Generic; using Steamworks; using UnityEngine; namespace MegaKoop.Game.Networking { /// /// Lightweight cache of the latest character transform messages so systems (AI, prediction) can query peer positions. /// [DefaultExecutionOrder(-900)] public class SteamCharacterStateCache : MonoBehaviour { public readonly struct CharacterState { public readonly int NetworkId; public readonly Vector3 Position; public readonly Quaternion Rotation; public readonly Vector3 Velocity; public readonly float LastUpdateTime; public readonly ulong SourceSteamId; public CharacterState(int networkId, Vector3 position, Quaternion rotation, Vector3 velocity, float timestamp, ulong steamId) { NetworkId = networkId; Position = position; Rotation = rotation; Velocity = velocity; LastUpdateTime = timestamp; SourceSteamId = steamId; } } private static SteamCharacterStateCache instance; private readonly Dictionary states = new(); private SteamCoopNetworkManager networkManager; private bool isRegistered; public static SteamCharacterStateCache Instance { get { if (instance == null) { var go = new GameObject("SteamCharacterStateCache"); instance = go.AddComponent(); DontDestroyOnLoad(go); } return instance; } } private void Awake() { if (instance != null && instance != this) { Destroy(gameObject); return; } instance = this; DontDestroyOnLoad(gameObject); } private void OnEnable() { EnsureSubscription(); } private void Update() { EnsureSubscription(); } private void OnDisable() { Unsubscribe(); } private void EnsureSubscription() { var current = SteamCoopNetworkManager.Instance; if (current == networkManager) { if (networkManager != null && !isRegistered) { networkManager.RegisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage); isRegistered = true; } return; } Unsubscribe(); networkManager = current; if (networkManager != null) { networkManager.RegisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage); isRegistered = true; } } private void Unsubscribe() { if (networkManager != null && isRegistered) { networkManager.UnregisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage); isRegistered = false; } } private void HandleCharacterTransformMessage(NetworkMessage message) { CharacterTransformMessage transformMessage = CharacterTransformMessage.Deserialize(message.Payload); StoreState(transformMessage.NetworkId, transformMessage.Position, transformMessage.Rotation, transformMessage.Velocity, message.Sender); } private void StoreState(int networkId, Vector3 position, Quaternion rotation, Vector3 velocity, ulong steamId) { if (networkId == 0) { return; } states[networkId] = new CharacterState(networkId, position, rotation, velocity, Time.time, steamId); } private static ulong GetLocalSteamId() { #if STEAMWORKSNET if (MegaKoop.Steam.SteamManager.Initialized) { return SteamUser.GetSteamID().m_SteamID; } #else if (SteamBootstrap.IsInitialized) { return SteamUser.GetSteamID().m_SteamID; } #endif return 0UL; } public static bool TryGetState(int networkId, out CharacterState state) { if (Instance.states.TryGetValue(networkId, out state)) { return true; } state = default; return false; } public static void ReportLocalState(int networkId, Vector3 position, Quaternion rotation, Vector3 velocity) { if (networkId == 0) { return; } Instance.StoreState(networkId, position, rotation, velocity, GetLocalSteamId()); } public static void RemoveState(int networkId) { if (networkId == 0) { return; } Instance.states.Remove(networkId); } } }