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);
}
}
}