393 lines
15 KiB
C#
393 lines
15 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description:
|
||
* @Date: 2025年09月08日 星期一 17:09:18
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using Logic;
|
||
using Logic.Action;
|
||
using Logic.AI;
|
||
using Logic.CrashSight;
|
||
using RuntimeData;
|
||
using Steamworks;
|
||
using TH1_Logic.Chat;
|
||
using TH1_Logic.Core;
|
||
using TH1_Logic.Net;
|
||
|
||
|
||
namespace TH1_Logic.Steam
|
||
{
|
||
public class GameNetSender
|
||
{
|
||
public static GameNetSender Instance { get; } = new GameNetSender();
|
||
private static float RecordTime;
|
||
private const float RequestLobbyDataCooldown = 1f;
|
||
private const float RequestForceUpdateCooldown = 5f;
|
||
private float _lastRequestLobbyDataTime = -RequestLobbyDataCooldown;
|
||
private float _lastRequestForceUpdateTime = -RequestForceUpdateCooldown;
|
||
private bool _needLobbyDataFromHost;
|
||
|
||
// 发送消息给房主
|
||
public bool SendMessage(BaseMessage message)
|
||
{
|
||
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return false;
|
||
byte[] messageBytes = SerializeForNetwork(message);
|
||
if (LobbyManager.Instance.Lobby.SendMessageToPeer(LobbyManager.Instance.Lobby.GetLobbyOwnerId(), messageBytes)) return true;
|
||
LogSystem.LogError($"{message?.GetType().Name}: 发送给房主失败");
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.P2PMessageSendFailed);
|
||
return false;
|
||
}
|
||
|
||
// 发送消息给指定人
|
||
public bool SendMessageToPlayer(ulong memberId, BaseMessage message)
|
||
{
|
||
byte[] messageBytes = SerializeForNetwork(message);
|
||
if (LobbyManager.Instance.Lobby.SendMessageToPeer(memberId, messageBytes)) return true;
|
||
LogSystem.LogError($"{message?.GetType().Name}: 发送给成员失败 memberId={memberId}");
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.P2PMessageSendFailed);
|
||
return false;
|
||
}
|
||
|
||
// 房主广播消息给所有成员
|
||
public bool BroadcastMessage(BaseMessage message)
|
||
{
|
||
if (!LobbyManager.Instance.Lobby.IsLobbyOwner()) return false;
|
||
byte[] messageBytes = SerializeForNetwork(message);
|
||
if (LobbyManager.Instance.Lobby.BroadcastMessage(messageBytes)) return true;
|
||
LogSystem.LogError($"{message?.GetType().Name}: 房主广播失败");
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.P2PBroadcastFailed);
|
||
return false;
|
||
}
|
||
|
||
// 房主广播消息给所有成员
|
||
public bool SendMessageToAllPlayer(BaseMessage message)
|
||
{
|
||
byte[] messageBytes = SerializeForNetwork(message);
|
||
if (LobbyManager.Instance.Lobby.BroadcastMessage(messageBytes)) return true;
|
||
LogSystem.LogError($"{message?.GetType().Name}: 广播给所有成员失败");
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.P2PBroadcastFailed);
|
||
return false;
|
||
}
|
||
|
||
private static byte[] SerializeForNetwork(BaseMessage message)
|
||
{
|
||
var rawBytes = MemoryPack.MemoryPackSerializer.Serialize<BaseMessage>(message);
|
||
return NetworkPayloadCodec.Encode(rawBytes);
|
||
}
|
||
|
||
// 广播字符串
|
||
public void BroadcastString(string str)
|
||
{
|
||
var data = new StringMessage();
|
||
data.Content = str;
|
||
SendMessageToAllPlayer(data);
|
||
}
|
||
|
||
// 游戏开始
|
||
public bool GameStart()
|
||
{
|
||
if (!TryGetValidMultiMapForBroadcast("GameStart", out var mapData)) return false;
|
||
var data = new GameStartMessage();
|
||
data.MapData = mapData;
|
||
return BroadcastMessage(data);
|
||
}
|
||
|
||
// 请求行为 (成员 => 房主)
|
||
public bool ActionConfirm(CommonActionId id, CommonActionParams param)
|
||
{
|
||
var actionData = new ActionNetData();
|
||
actionData.Version = Main.MapData.Net.GetActionVersion();
|
||
actionData.MapHash = NetData.GetMapDataHash(Main.MapData);
|
||
actionData.Param = param;
|
||
actionData.ActionId = id;
|
||
|
||
var data = new ActionConfirmMessage();
|
||
data.ActionData = actionData;
|
||
return SendMessage(data);
|
||
}
|
||
|
||
// 行为执行 (房主 => 所有成员)
|
||
public bool ActionExecute(CommonActionId id, CommonActionParams param)
|
||
{
|
||
var actionData = new ActionNetData();
|
||
actionData.Version = Main.MapData.Net.GetActionVersion();
|
||
actionData.MapHash = NetData.GetMapDataHash(Main.MapData);
|
||
actionData.Param = param;
|
||
actionData.ActionId = id;
|
||
|
||
var data = new ActionExcuteMessage();
|
||
data.ActionData = actionData;
|
||
// // TODO 这里是测试,每帧最多只发送一次
|
||
// if (RecordTime < Time.time)
|
||
// {
|
||
// data.Map = Main.MapData;
|
||
// RecordTime = Time.time + 0.2f;
|
||
// }
|
||
|
||
return BroadcastMessage(data);
|
||
}
|
||
|
||
// 请求回合结束 (成员 => 房主)
|
||
public void TurnEndConfirm()
|
||
{
|
||
var data = new TurnEndMessage();
|
||
data.PlayerId = Main.MapData.PlayerMap.SelfPlayerId;
|
||
SendMessage(data);
|
||
}
|
||
|
||
// 游戏数据心跳校验 (成员 => 房主)
|
||
public void MapConfirm()
|
||
{
|
||
var data = new MapConfirmMessage();
|
||
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
data.PlayerId = Main.MapData.PlayerMap.SelfPlayerId;
|
||
data.Index = Main.MapData.Net.Actions.Count;
|
||
if (data.Index > 0) data.ActionData = Main.MapData.Net.Actions[^1];
|
||
SendMessage(data);
|
||
}
|
||
|
||
// 游戏数据心跳校验 (房主 => 所有成员)
|
||
public void BroadcastMapConfirm()
|
||
{
|
||
var data = new MapConfirmMessage();
|
||
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
data.PlayerId = Main.MapData.PlayerMap.SelfPlayerId;
|
||
data.Index = Main.MapData.Net.Actions.Count;
|
||
if (data.Index > 0) data.ActionData = Main.MapData.Net.Actions[^1];
|
||
BroadcastMessage(data);
|
||
}
|
||
|
||
// 强制更新 (房主 => 单成员)
|
||
public void ForceUpdate(ulong memberId)
|
||
{
|
||
if (memberId == 0) return;
|
||
if (!TryGetValidMultiMapForBroadcast("ForceUpdate", out var mapData)) return;
|
||
var data = new ForceUpdateMessage();
|
||
data.MapData = mapData;
|
||
SendMessageToPlayer(memberId, data);
|
||
}
|
||
|
||
// 强制更新 (房主 => 所有成员)
|
||
public void BroadcastForceUpdate()
|
||
{
|
||
if (!TryGetValidMultiMapForBroadcast("BroadcastForceUpdate", out var mapData)) return;
|
||
var data = new ForceUpdateMessage();
|
||
data.MapData = mapData;
|
||
BroadcastMessage(data);
|
||
}
|
||
|
||
private bool TryGetValidMultiMapForBroadcast(string context, out MapData mapData)
|
||
{
|
||
mapData = Main.MapData;
|
||
if (mapData?.Net == null
|
||
|| mapData.DeserializedMissingCriticalData
|
||
|| mapData.Net.Mode != NetMode.Multi
|
||
|| mapData.MapConfig == null
|
||
|| mapData.GridMap == null
|
||
|| mapData.PlayerMap == null
|
||
|| mapData.CityMap == null
|
||
|| mapData.UnitMap == null)
|
||
{
|
||
LogSystem.LogError($"{context}: 无效多人地图数据,取消发送");
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.InvalidNetworkMapData);
|
||
mapData = null;
|
||
return false;
|
||
}
|
||
|
||
if (!mapData.Net.RefreshPlayerNet(mapData))
|
||
{
|
||
LogSystem.LogError($"{context}: 玩家网络映射失败,取消发送");
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.NetworkPlayerMappingFailed);
|
||
mapData = null;
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// 修改成员房间配置(阵营/准备状态,成员 => 房主)
|
||
public bool ChangeCiv(MemberCiv memberCiv)
|
||
{
|
||
if (memberCiv == null)
|
||
{
|
||
LogSystem.LogError($"Get Self MemberCiv Error ");
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMemberConfigFailed);
|
||
return false;
|
||
}
|
||
|
||
var data = new ChangeCivMessage();
|
||
data.Civ = memberCiv;
|
||
if (!SendMessage(data)) return false;
|
||
MarkLobbyDataSyncRequired();
|
||
return true;
|
||
}
|
||
|
||
// 更新房间配置 (房主 => 所有成员)
|
||
public void UpdateLobbyData(MapConfig config)
|
||
{
|
||
if (!LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
|
||
var data = new UpdateLobbyDataMessage();
|
||
data.Config = config;
|
||
var selfId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
foreach (var memberId in LobbyManager.Instance.Lobby.GetAllMemberIds())
|
||
{
|
||
if (memberId == selfId) continue;
|
||
|
||
var target = new CSteamID(memberId);
|
||
if (!SimpleP2P.Instance.IsConnectedTo(target))
|
||
{
|
||
LogSystem.LogInfo($"UpdateLobbyData deferred until P2P connected: memberId={memberId}");
|
||
continue;
|
||
}
|
||
|
||
SendMessageToPlayer(memberId, data);
|
||
}
|
||
}
|
||
|
||
// 请求更新房间配置 (单成员 => 房主)
|
||
public bool RequestLobbyData(bool force = false)
|
||
{
|
||
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return false;
|
||
if (!LobbyManager.Instance.Lobby.IsInLobby()) return false;
|
||
var hostId = LobbyManager.Instance.Lobby.GetLobbyOwnerId();
|
||
if (hostId == 0 || !SimpleP2P.Instance.IsConnectedTo(new CSteamID(hostId)))
|
||
{
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyDataRequestFailed);
|
||
return false;
|
||
}
|
||
|
||
var now = UnityEngine.Time.time;
|
||
if (!force && now - _lastRequestLobbyDataTime < RequestLobbyDataCooldown) return false;
|
||
_lastRequestLobbyDataTime = now;
|
||
|
||
var data = new RequestLobbyDataMessage();
|
||
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
if (SendMessage(data)) return true;
|
||
|
||
_lastRequestLobbyDataTime = now - RequestLobbyDataCooldown + 0.2f;
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyDataRequestFailed);
|
||
return false;
|
||
}
|
||
|
||
public void MarkLobbyDataSyncRequired()
|
||
{
|
||
if (!_needLobbyDataFromHost)
|
||
_lastRequestLobbyDataTime = -RequestLobbyDataCooldown;
|
||
_needLobbyDataFromHost = true;
|
||
}
|
||
|
||
public void MarkLobbyDataSyncedFromHost()
|
||
{
|
||
_needLobbyDataFromHost = false;
|
||
}
|
||
|
||
public void ClearLobbyDataSyncState()
|
||
{
|
||
_needLobbyDataFromHost = false;
|
||
_lastRequestLobbyDataTime = -RequestLobbyDataCooldown;
|
||
}
|
||
|
||
public bool NeedsLobbyDataFromHost()
|
||
{
|
||
return _needLobbyDataFromHost;
|
||
}
|
||
|
||
// 更新房间配置 (房主 => 单成员)
|
||
public void SendLobbyData(MapConfig config, ulong memberId)
|
||
{
|
||
if (!LobbyManager.Instance.Lobby.IsLobbyOwner() || memberId == 0) return;
|
||
var data = new UpdateLobbyDataMessage();
|
||
data.Config = config;
|
||
SendMessageToPlayer(memberId, data);
|
||
}
|
||
|
||
// 心跳 (单成员 => 房主) 成员的心跳包
|
||
public void SendHeartbeat()
|
||
{
|
||
var confirm = Main.Instance.ConfirmMap.GetPlayerConfirm(LobbyManager.Instance.Lobby.GetLobbyOwnerId());
|
||
|
||
// 发送
|
||
var data = new HeartbeatMessage();
|
||
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
data.State = Main.Instance.GameLogic.GetCurState();
|
||
if (SendMessage(data))
|
||
{
|
||
// 确认发送记录
|
||
confirm.OnSendHeartbeat();
|
||
}
|
||
}
|
||
|
||
// 成员状态下发 (房主 => 成员) 房主的心跳包
|
||
public void SendMemberStateSync()
|
||
{
|
||
if (Main.MapData == null) return;
|
||
|
||
// 确认发送记录:遍历 lobby 所有成员,保证每个客机的 confirm 都写入 PingRecordTime
|
||
// (字典中没有该成员条目时通过 GetPlayerConfirm 懒加载创建,避免首次发送时字典为空导致 ping 永远 = 0)
|
||
var selfId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
foreach (var memberId in LobbyManager.Instance.Lobby.GetAllMemberIds())
|
||
{
|
||
if (memberId == selfId) continue;
|
||
Main.Instance.ConfirmMap.GetPlayerConfirm(memberId).OnSendHeartbeat();
|
||
}
|
||
|
||
// 发送
|
||
var data = new MemberStateSyncMessage();
|
||
data.State = Main.Instance.GameLogic.GetCurState();
|
||
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
data.PlayerConfirm = Main.Instance.ConfirmMap.PlayerConfirm;
|
||
BroadcastMessage(data);
|
||
}
|
||
|
||
// 请求重连 (单成员 => 房主)
|
||
public void SendRequestForceUpdate()
|
||
{
|
||
if (Main.Instance.GameLogic.GetCurState() == GameState.ForceUpdating) return;
|
||
|
||
var now = UnityEngine.Time.time;
|
||
if (now - _lastRequestForceUpdateTime < RequestForceUpdateCooldown)
|
||
{
|
||
LogSystem.LogWarning($"客户端请求重连冷却中: SendRequestForceUpdate, remain={RequestForceUpdateCooldown - (now - _lastRequestForceUpdateTime):F1}s");
|
||
return;
|
||
}
|
||
_lastRequestForceUpdateTime = now;
|
||
|
||
LogSystem.LogError($"客户端请求重连: SendRequestForceUpdate");
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.ReconnectRequested);
|
||
var data = new RequestForceUpdateMessage();
|
||
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
if (SendMessage(data))
|
||
{
|
||
NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.ReconnectStarted);
|
||
Main.Instance.GameLogic.ChangeState(GameState.ForceUpdating);
|
||
}
|
||
}
|
||
|
||
// 心跳包回复 (任意成员 => 任意成员)
|
||
public void SendHeartbeatReply(ulong memberId)
|
||
{
|
||
var data = new HeartbeatReplyMessage();
|
||
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
|
||
SendMessageToPlayer(memberId, data);
|
||
}
|
||
|
||
// 发送聊天内容 (单成员 => 房主)
|
||
public void SendChatMessage(ChatItem item)
|
||
{
|
||
var data = new ChatMessage();
|
||
data.Item = item;
|
||
SendMessage(data);
|
||
}
|
||
|
||
// 发送聊天内容 (房主 => 所有成员)
|
||
public void BroadcastChatMessage(ChatItem item)
|
||
{
|
||
var data = new ChatMessage();
|
||
data.Item = item;
|
||
BroadcastMessage(data);
|
||
}
|
||
}
|
||
}
|