/* * @Author: 白哉 * @Description: * @Date: 2025年04月03日 星期四 11:04:31 * @Modify: */ using System; using System.Buffers; using System.Collections.Generic; using System.Runtime.InteropServices; using Logic.AI; using Logic.CrashSight; using MemoryPack; using TH1_Logic.Core; using TH1_Logic.Net; using UnityEngine; namespace RuntimeData { public enum NetMode { None, Single, Multi, Spectator, } public enum MemberNetState { None, OK, Leaved, Disconnected, Timeout, Error, } [MemoryPackable] public partial class PlayerConfirmData { public ulong MemberId; public MemberNetState State; [MemoryPackIgnore] public float PingRecordTime; public float Ping; [MemoryPackIgnore] public float ErrorTime; [MemoryPackIgnore] public float ConfirmTime; public bool AIControl; [MemoryPackConstructor] public PlayerConfirmData() { } public PlayerConfirmData(ulong id) { MemberId = id; ConfirmTime = Time.time; State = MemberNetState.OK; AIControl = false; } public void OnSendHeartbeat() { if (PingRecordTime == 0 || Time.time - PingRecordTime > 5f) PingRecordTime = Time.time; } public void OnReceiveHeartbeat() { ConfirmTime = Time.time; } public void OnReceiveHeartbeatReply() { if (PingRecordTime == 0f) return; Ping = Time.time - PingRecordTime; PingRecordTime = 0f; } public float GetPing() { return Ping; } public void UpdateErrorTime() { if (State == MemberNetState.OK) ErrorTime = Time.time; } public bool IsNeedAI() { // 房主可手动指定 AI 接管 return AIControl && State != MemberNetState.OK; } } [MemoryPackable] public partial class PlayerConfirmMap { public Dictionary PlayerConfirm; public PlayerConfirmMap() { PlayerConfirm = new Dictionary(); } public PlayerConfirmData GetPlayerConfirm(ulong memberId) { if (!PlayerConfirm.ContainsKey(memberId)) PlayerConfirm[memberId] = new PlayerConfirmData(memberId); return PlayerConfirm[memberId]; } public void UpdatePlayerConfirm(Dictionary playerConfirm) { foreach (var confirm in playerConfirm) { var data = GetPlayerConfirm(confirm.Key); data.State = confirm.Value.State; data.AIControl = confirm.Value.AIControl; if (confirm.Key == LobbyManager.Instance.Lobby.GetSelfMemberId()) continue; if (confirm.Key == LobbyManager.Instance.Lobby.GetLobbyOwnerId()) continue; data.Ping = confirm.Value.Ping; } } public void InitFromMapSlots(MapData mapData) { var lobby = LobbyManager.Instance.Lobby; if (mapData?.MapConfig?.MultiCivs == null || lobby == null || !lobby.IsInLobby()) return; foreach (var slot in mapData.MapConfig.MultiCivs) { if (slot == null || slot.MemberId == 0) continue; var confirm = GetPlayerConfirm(slot.MemberId); var inLobby = lobby.IsMemberInLobby(slot.MemberId); confirm.State = inLobby ? MemberNetState.OK : MemberNetState.Leaved; confirm.AIControl = false; } } } // 网络信息 [MemoryPackable] public partial class NetData { // 网络模式 public NetMode Mode; // 当前操作的玩家 public uint CurPlayerId; // SteamId => PlayerId 的索引 public Dictionary Players; // 所有玩家行为的序列 public List Actions; // 随机数种子 开始游戏时由房主进行初始化,整局游戏不可变 public int RandomSeed; // 同步随机源的消费次数,用于 ForceUpdate/重连后恢复随机游标 public int RandomUseCount; [MemoryPackIgnore] private System.Random _random; private static readonly ArrayBufferWriter _bufferWriter = new ArrayBufferWriter(64 * 1024); // 玩家开始时间记录 [MemoryPackIgnore] public float PlayerStartTime; [MemoryPackIgnore] public float GameStartTime; [MemoryPackConstructor] public NetData() { Mode = NetMode.None; CurPlayerId = 0; Players = new Dictionary(); Actions = new List(); // 生成确定性种子(由房主生成并广播) RandomSeed = System.Environment.TickCount; RandomUseCount = 0; GameStartTime = 0; PlayerStartTime = 0; } // 这里单纯是因为深拷贝目前只用于 AI 演算,故不对 Players 和 Actions 赋值 public NetData(NetData copyData) { Mode = copyData.Mode; CurPlayerId = copyData.CurPlayerId; RandomSeed = copyData.RandomSeed; RandomUseCount = copyData.RandomUseCount; Players = new Dictionary(); Actions = new List(); } // 这里单纯是因为深拷贝目前只用于 AI 演算,故不对 Players 和 Actions 赋值 public void DeepCopy(NetData copyData) { Mode = copyData.Mode; CurPlayerId = copyData.CurPlayerId; RandomSeed = copyData.RandomSeed; RandomUseCount = copyData.RandomUseCount; _random = null; Players = new Dictionary(); Actions = new List(); } // 只有 Action 的逻辑可以使用这个 Random, 因为要保证计数一致 public System.Random GetRandom(MapData map) { if (map != Main.MapData) return CreateRandom(RandomUseCount, null); if (_random == null) _random = CreateRandom(RandomUseCount, () => RandomUseCount++); return _random; } private System.Random CreateRandom(int useCount, System.Action onUse) { var random = new CountingRandom(RandomSeed, onUse); random.Advance(useCount); return random; } private sealed class CountingRandom : System.Random { private readonly System.Action _onUse; private bool _advancing; public CountingRandom(int seed, System.Action onUse) : base(seed) { _onUse = onUse; } public void Advance(int count) { if (count <= 0) return; _advancing = true; for (var i = 0; i < count; i++) base.Next(); _advancing = false; } private void CountUse() { if (!_advancing) _onUse?.Invoke(); } public override int Next() { CountUse(); return base.Next(); } public override int Next(int maxValue) { CountUse(); return base.Next(maxValue); } public override int Next(int minValue, int maxValue) { CountUse(); return base.Next(minValue, maxValue); } public override double NextDouble() { CountUse(); return base.NextDouble(); } public override void NextBytes(byte[] buffer) { CountUse(); base.NextBytes(buffer); } } public bool RefreshPlayerNet(MapData mapData) { if (Mode != NetMode.Multi) return true; if (mapData?.PlayerMap?.PlayerDataList == null) { NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.InvalidNetworkMapData); return false; } var lobby = LobbyManager.Instance.Lobby; if (lobby == null || !lobby.IsInLobby()) { NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.NetworkPlayerMappingFailed); return false; } Players ??= new Dictionary(); var lobbyMemberIds = lobby.GetAllMemberIds(); if (lobbyMemberIds == null || lobbyMemberIds.Count == 0) { NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMembersNotSynced); return false; } var lobbyMemberSet = new HashSet(lobbyMemberIds); if (lobby.IsLobbyOwner()) { var staleMemberIds = new List(); foreach (var kv in Players) { if (lobbyMemberSet.Contains(kv.Key)) continue; staleMemberIds.Add(kv.Key); } foreach (var memberId in staleMemberIds) { Players.Remove(memberId); LogSystem.LogWarning($"RefreshPlayerNet removed stale member mapping: {memberId}"); } // 创建 Player 的时候会分配一次 PlayerId,这里直接使用 if (mapData.MapConfig?.MultiCivs != null) { foreach (var memberCiv in mapData.MapConfig.MultiCivs) { if (memberCiv == null || memberCiv.PlayerId == 0) continue; if (!lobbyMemberSet.Contains(memberCiv.MemberId)) continue; if (Players.TryGetValue(memberCiv.MemberId, out var mappedPlayerId)) { if (mappedPlayerId != memberCiv.PlayerId) Players[memberCiv.MemberId] = memberCiv.PlayerId; continue; } if (Players.ContainsValue(memberCiv.PlayerId)) continue; Players[memberCiv.MemberId] = memberCiv.PlayerId; } } // 添加其他人 foreach (var memberId in lobbyMemberIds) { if (Players.ContainsKey(memberId)) continue; foreach (var player in mapData.PlayerMap.PlayerDataList) { if (player == null) continue; if (Players.ContainsValue(player.Id)) continue; Players[memberId] = player.Id; break; } } } var selfMemberId = lobby.GetSelfMemberId(); foreach (var memberId in lobbyMemberIds) { if (!Players.TryGetValue(memberId, out var playerId) || playerId == 0 || !mapData.PlayerMap.GetPlayerDataByPlayerID(playerId, out _)) { LogSystem.LogWarning($"RefreshPlayerNet missing PlayerId mapping for lobby member: member={memberId}, player={playerId}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.NetworkPlayerMappingFailed); return false; } } var seenPlayerIds = new HashSet(); foreach (var kv in Players) { if (!lobby.IsMemberInLobby(kv.Key)) continue; if (kv.Value == 0 || !mapData.PlayerMap.GetPlayerDataByPlayerID(kv.Value, out _)) { LogSystem.LogWarning($"RefreshPlayerNet invalid PlayerId mapping: member={kv.Key}, player={kv.Value}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.NetworkPlayerMappingFailed); return false; } if (!seenPlayerIds.Add(kv.Value)) { LogSystem.LogWarning($"RefreshPlayerNet duplicate PlayerId mapping: player={kv.Value}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.NetworkPlayerMappingFailed); return false; } } if (Players.TryGetValue(selfMemberId, out var id) && id != 0 && mapData.PlayerMap.GetPlayerDataByPlayerID(id, out _)) { mapData.PlayerMap.SelfPlayerId = id; return true; } else { LogSystem.LogWarning($"RefreshPlayerNet could not find PlayerId for self member: {selfMemberId}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.NetworkPlayerMappingFailed); return false; } } public uint GetActionVersion() { if (Actions.Count == 0) return 0; return Actions[^1].Version + 1; } public ulong GetmemberIdId(uint playerId) { foreach (var kv in Players) { if (kv.Value == playerId) return kv.Key; } return 0; } public uint GetPlayerId(ulong memberId) { return Players.GetValueOrDefault(memberId); } public void CompareEqual(NetData net) { var differences = new List(); // 使用序列化比较各个组件 MapData.CompareComponent(Mode, net.Mode, "Mode", differences); MapData.CompareComponent(CurPlayerId, net.CurPlayerId, "CurPlayerId", differences); MapData.CompareComponent(RandomUseCount, net.RandomUseCount, "RandomUseCount", differences); MapData.CompareComponent(Players, net.Players, "Players", differences); MapData.CompareComponent(Actions, net.Actions, "Actions", differences); // 输出结果 foreach (var diff in differences) LogSystem.LogWarning($" - {diff}"); } public void Clear() { Mode = NetMode.None; CurPlayerId = 0; Players ??= new Dictionary(); Players.Clear(); Actions ??= new List(); Actions.Clear(); // 生成确定性种子(由房主生成并广播) RandomSeed = System.Environment.TickCount; RandomUseCount = 0; _random = null; GameStartTime = 0; PlayerStartTime = 0; } // public static string GetMapDataHash(MapData mapData) // { // var actions = mapData.Net.Actions; // mapData.Net.Actions = null; // // _bufferWriter.Clear(); // MemoryPackSerializer.Serialize(_bufferWriter, mapData); // // mapData.Net.Actions = actions; // // var hash128 = new Hash128(); // hash128.Append(_bufferWriter.WrittenSpan.ToArray()); // 这里仍有 GC // return hash128.ToString(); // } public static string GetMapDataHash(MapData mapData) { lock (_bufferWriter) { var actions = mapData.Net.Actions; mapData.Net.Actions = null; try { _bufferWriter.Clear(); MemoryPackSerializer.Serialize(_bufferWriter, mapData); var hash128 = new Hash128(); var writtenMemory = _bufferWriter.WrittenMemory; if (MemoryMarshal.TryGetArray(writtenMemory, out ArraySegment segment) && segment.Array != null) { hash128.Append(segment.Array, segment.Offset, segment.Count); } else { hash128.Append(writtenMemory.ToArray()); } return hash128.ToString(); } finally { mapData.Net.Actions = actions; } } } } }