329 lines
10 KiB
C#
329 lines
10 KiB
C#
/*
|
||
* @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;
|
||
|
||
|
||
[MemoryPackConstructor]
|
||
public PlayerConfirmData()
|
||
{
|
||
|
||
}
|
||
|
||
public PlayerConfirmData(ulong id)
|
||
{
|
||
MemberId = id;
|
||
ConfirmTime = Time.time;
|
||
State = MemberNetState.OK;
|
||
}
|
||
|
||
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()
|
||
{
|
||
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;
|
||
if (confirm.Key == LobbyManager.Instance.Lobby.GetSelfMemberId()) continue;
|
||
if (confirm.Key == LobbyManager.Instance.Lobby.GetLobbyOwnerId()) continue;
|
||
data.Ping = confirm.Value.Ping;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// 网络信息
|
||
[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;
|
||
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;
|
||
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;
|
||
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 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 void Clear()
|
||
{
|
||
Mode = NetMode.None;
|
||
CurPlayerId = 0;
|
||
Players ??= new Dictionary<ulong, uint>();
|
||
Players.Clear();
|
||
Actions ??= new List<ActionNetData>();
|
||
Actions.Clear();
|
||
// 生成确定性种子(由房主生成并广播)
|
||
RandomSeed = System.Environment.TickCount;
|
||
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<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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|