2026-01-10 23:14:32 +08:00

302 lines
8.9 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.Buffers;
using System.Collections.Generic;
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;
[MemoryPackIgnore]
public float Ping;
[MemoryPackIgnore]
public float ErrorTime;
[MemoryPackIgnore]
public float ConfirmTime;
[MemoryPackConstructor]
public PlayerConfirmData()
{
}
public PlayerConfirmData(ulong id)
{
MemberId = id;
ConfirmTime = Time.time;
State = MemberNetState.OK;
}
public void OnSendHeartbeat()
{
PingRecordTime = Time.time;
}
public void OnReceiveHeartbeat()
{
ConfirmTime = Time.time;
}
public void OnReceiveHeartbeatReply()
{
Ping = Time.time - PingRecordTime;
PingRecordTime = 0f;
}
public float GetPing()
{
if (PingRecordTime != 0) return Time.time - PingRecordTime;
return Ping;
}
public void UpdateErrorTime()
{
if (State == MemberNetState.OK) ErrorTime = Time.time;
}
public bool IsNeedAI()
{
if (State == MemberNetState.Leaved) return true;
return State != MemberNetState.OK && Time.time - ErrorTime > 10f;
}
}
[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;
}
}
}
// 网络信息
[MemoryPackable]
public partial class NetData
{
// 网络模式
public NetMode Mode;
// 当前操作的玩家
public uint CurPlayerId;
// 地图哈希
[MemoryPackIgnore]
public string MapHash;
// SteamId => PlayerId 的索引
public Dictionary<ulong, uint> Players;
// 所有玩家行为的序列
public List<ActionNetData> Actions;
// 随机数种子 开始游戏时由房主进行初始化,整局游戏不可变
public int RandomSeed;
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;
GameStartTime = 0;
PlayerStartTime = 0;
}
// 这里单纯是因为深拷贝目前只用于 AI 演算,故不对 Players 和 Actions 赋值
public NetData(NetData copyData)
{
Mode = copyData.Mode;
CurPlayerId = copyData.CurPlayerId;
MapHash = copyData.MapHash;
RandomSeed = copyData.RandomSeed;
Players = new Dictionary<ulong, uint>();
Actions = new List<ActionNetData>();
}
// 这里单纯是因为深拷贝目前只用于 AI 演算,故不对 Players 和 Actions 赋值
public void DeepCopy(NetData copyData)
{
Mode = copyData.Mode;
CurPlayerId = copyData.CurPlayerId;
MapHash = copyData.MapHash;
RandomSeed = copyData.RandomSeed;
Players = new Dictionary<ulong, uint>();
Actions = new List<ActionNetData>();
}
// 只有 Action 的逻辑可以使用这个 Random, 因为要保证计数一致
public System.Random GetRandom(MapData map)
{
if (map != Main.MapData) return new System.Random(RandomSeed);
if (_random == null) _random = new System.Random(RandomSeed);
return _random;
}
public void RefreshPlayerNet(MapData mapData)
{
if (Mode != NetMode.Multi) return;
if (LobbyManager.Instance.Lobby.IsLobbyOwner())
{
// 创建 Player 的时候会分配一次 PlayerId这里直接使用
foreach (var memberCiv in mapData.MapConfig.MultiCivs)
{
if (memberCiv.PlayerId == 0) continue;
if (Players.ContainsKey(memberCiv.MemberId)) continue;
Players[memberCiv.MemberId] = memberCiv.PlayerId;
}
// 添加其他人
foreach (var memberId in LobbyManager.Instance.Lobby.GetAllMemberIds())
{
if (Players.ContainsKey(memberId)) continue;
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
if (Players.ContainsValue(player.Id)) continue;
Players[memberId] = player.Id;
break;
}
}
}
var selfMemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
if (Players.TryGetValue(selfMemberId, out var id))
mapData.PlayerMap.SelfPlayerId = id;
}
public void RefreshMapNet(MapData mapData)
{
// 地图哈希
MapHash = GetMapDataHash(mapData);
}
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(Players, net.Players, "Players", differences);
MapData.CompareComponent(Actions, net.Actions, "Actions", differences);
// 输出结果
foreach (var diff in differences) LogSystem.LogWarning($" - {diff}");
}
// 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)
{
var actions = mapData.Net.Actions;
mapData.Net.Actions = null;
byte[] bytes = MemoryPackSerializer.Serialize(mapData);
mapData.Net.Actions = actions;
// 使用 Unity 的 Hash128性能很好且稳定
var hash128 = new Hash128();
hash128.Append(bytes);
return hash128.ToString();
}
}
}