504 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @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 TH1_Logic.Tools;
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<ulong, PlayerConfirmData> PlayerConfirm;
public PlayerConfirmMap()
{
PlayerConfirm = new Dictionary<ulong, PlayerConfirmData>();
}
public PlayerConfirmData GetPlayerConfirm(ulong memberId)
{
if (!PlayerConfirm.ContainsKey(memberId))
PlayerConfirm[memberId] = new PlayerConfirmData(memberId);
return PlayerConfirm[memberId];
}
public void UpdatePlayerConfirm(Dictionary<ulong, PlayerConfirmData> 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<ulong, uint> Players;
// 所有玩家行为的序列
public List<ActionNetData> Actions;
// 随机数种子 开始游戏时由房主进行初始化,整局游戏不可变
public int RandomSeed;
// 同步随机源的消费次数,用于 ForceUpdate/重连后恢复随机游标
public int RandomUseCount;
[MemoryPackIgnore]
private System.Random _random;
private static readonly ArrayBufferWriter<byte> _bufferWriter = new ArrayBufferWriter<byte>(64 * 1024);
// 玩家开始时间记录
[MemoryPackIgnore]
public float PlayerStartTime;
[MemoryPackIgnore]
public float GameStartTime;
[MemoryPackConstructor]
public NetData()
{
Mode = NetMode.None;
CurPlayerId = 0;
Players = new Dictionary<ulong, uint>();
Actions = new List<ActionNetData>();
// 生成确定性种子(由房主生成并广播)
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<ulong, uint>();
Actions = new List<ActionNetData>();
}
// 这里单纯是因为深拷贝目前只用于 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<ulong, uint>();
Actions = new List<ActionNetData>();
}
// 只有 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<ulong, uint>();
var lobbyMemberIds = lobby.GetAllMemberIds();
if (lobbyMemberIds == null || lobbyMemberIds.Count == 0)
{
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMembersNotSynced);
return false;
}
var lobbyMemberSet = new HashSet<ulong>(lobbyMemberIds);
if (lobby.IsLobbyOwner())
{
var staleMemberIds = new List<ulong>();
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<uint>();
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<string>();
// 使用序列化比较各个组件
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<ulong, uint>();
Players.Clear();
Actions ??= new List<ActionNetData>();
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();
// TH1Serialization.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();
TH1Serialization.Serialize(_bufferWriter, mapData);
var hash128 = new Hash128();
var writtenMemory = _bufferWriter.WrittenMemory;
if (MemoryMarshal.TryGetArray(writtenMemory, out ArraySegment<byte> 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;
}
}
}
}
}