联机底层修改

This commit is contained in:
wuwenbo 2026-05-15 15:34:35 +08:00
parent 837f406908
commit ac1b31f33a
15 changed files with 1491 additions and 483 deletions

View File

@ -26,6 +26,7 @@ namespace Logic.Audio
private Dictionary<string, List<AudioClip>> _clips;
private GameObject AudioRoot;
private Dictionary<string, float> _musicRecord;
private readonly HashSet<uint> _ambientTerritoryGridSet = new HashSet<uint>();
/// <summary>
/// 获取当前正在播放的BGM名(用于UI侧暂存,无在播则返回null)
@ -491,7 +492,7 @@ namespace Logic.Audio
}
}
_allPlayer = _allPlayer.OrderBy(p => p.StartTime).ToList();
_allPlayer.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
// 限制最大同时播放数量
if (_allPlayer.Count >= 16) // FMOD默认最大通道数通常是32这里取一半作为安全值
{
@ -532,8 +533,10 @@ namespace Logic.Audio
bool isForest = false;
bool isSea = false;
var player = Main.MapData.CurPlayer;;
var gset = Main.MapData.GetPlayerTerritoryGridIdSet(player.Id);
foreach (var g in gset)
if (player == null) return;
_ambientTerritoryGridSet.Clear();
Main.MapData.GetPlayerTerritoryGridIdSet(player.Id, _ambientTerritoryGridSet);
foreach (var g in _ambientTerritoryGridSet)
{
if (!Main.MapData.GridMap.GetGridDataByGid(g, out var grid)) continue;
if (grid.Terrain != TerrainType.Land) isSea = true;
@ -686,4 +689,4 @@ namespace Logic.Audio
if (State == PlayerState.Playing) Source.volume = volumeRatio;
}
}
}
}

View File

@ -24,6 +24,7 @@ namespace TH1_Core.Events
{
private static UIEventManagerBinder _instance;
public static UIEventManagerBinder Instance => _instance ?? (_instance = new UIEventManagerBinder());
private bool _initialized;
private UIEventManagerBinder() { }
@ -32,6 +33,9 @@ namespace TH1_Core.Events
/// </summary>
public void Initialize()
{
if (_initialized) return;
_initialized = true;
// 订阅所有AnnounceUI事件
EventManager.Subscribe<ShowUIAnnounceMajorEvent>(HandleShowUIAnnounceMajorEvent);
EventManager.Subscribe<ShowUIAnnounceDiplomacy>(HandleShowUIAnnounceDiplomacy);
@ -124,6 +128,9 @@ namespace TH1_Core.Events
/// </summary>
public void Shutdown()
{
if (!_initialized) return;
_initialized = false;
EventManager.Unsubscribe<ShowUIAnnounceMajorEvent>(HandleShowUIAnnounceMajorEvent);
EventManager.Unsubscribe<ShowUIAnnounceDiplomacy>(HandleShowUIAnnounceDiplomacy);

View File

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using Logic.CrashSight;
namespace TH1_Core.Managers
{
@ -24,11 +25,16 @@ namespace TH1_Core.Managers
/// <param name="listener">事件发生时要执行的回调方法。</param>
public static void Subscribe<T>(Action<T> listener) where T : struct
{
if (listener == null) return;
Type eventType = typeof(T);
// 如果字典中已存在该事件类型的委托
if (eventListeners.TryGetValue(eventType, out Delegate existingDelegate))
{
foreach (var existing in existingDelegate.GetInvocationList())
{
if (existing == (Delegate)listener) return;
}
// 将新的监听器添加到现有委托链中
eventListeners[eventType] = Delegate.Combine(existingDelegate, listener);
}
@ -82,9 +88,25 @@ namespace TH1_Core.Managers
// 将通用的 Delegate 类型安全地转换为具体的 Action<T> 类型
// 使用 ?.Invoke() 可以安全地调用,即使委托为空也不会抛出异常
var action = existingDelegate as Action<T>;
action?.Invoke(eventData);
if (action == null) return;
foreach (var handler in action.GetInvocationList())
{
try
{
((Action<T>)handler).Invoke(eventData);
}
catch (Exception e)
{
LogSystem.LogError($"EventManager Publish<{typeof(T).Name}> listener failed: {e}");
}
}
}
// 如果没有找到任何订阅者,则什么也不做。这是一种正常情况。
}
public static void Clear()
{
eventListeners.Clear();
}
}
}
}

View File

@ -27,6 +27,7 @@ using TH1_Logic.Core;
using TH1_Logic.MatchConfig;
using TH1_Logic.Net;
using TH1_Logic.Steam;
using TH1_Logic.Tools;
using TH1Renderer;
using UnityEngine;
using MemberInfo = TH1_Logic.Net.MemberInfo;
@ -213,7 +214,7 @@ namespace RuntimeData
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
if (player.PlayerCivId != member.CivId || player.PlayerForceId != member.ForceId) continue;
member.PlayerId = player.PlayerCivId;
member.PlayerId = player.Id;
}
if (member.PlayerId == 0)
@ -299,10 +300,13 @@ namespace RuntimeData
public NetData Net;
// 结算数据
public MatchSettlementInfo MatchSettlement;
[MemoryPackIgnore]
public bool DeserializedMissingCriticalData { get; private set; }
// 当前玩家
[MemoryPackIgnore]
public PlayerData CurPlayer => PlayerMap?.GetPlayerData(Net.CurPlayerId);
public PlayerData CurPlayer => PlayerMap?.GetPlayerData(Net?.CurPlayerId ?? 0);
// 城市 -> 玩家, 将引用关系转化为ID关系
public Dictionary<uint, uint> CityToPlayerDict;
@ -1329,10 +1333,20 @@ namespace RuntimeData
[MemoryPackOnDeserialized]
public void OnAfterMemoryPackDeserialize()
{
// 重绑定 ID
Net.RefreshPlayerNet(this);
// 重建缓存字典
DeserializedMissingCriticalData = MapConfig == null
|| GridMap == null
|| PlayerMap == null
|| CityMap == null
|| UnitMap == null
|| Net == null;
Net ??= new NetData();
Net.Players ??= new Dictionary<ulong, uint>();
Net.Actions ??= new List<ActionNetData>();
CityToPlayerDict ??= new Dictionary<uint, uint>();
UnitToCityDict ??= new Dictionary<uint, uint>();
UnitToGridDict ??= new Dictionary<uint, uint>();
CityToGridDict ??= new Dictionary<uint, uint>();
_gridToCityDict ??= new Dictionary<uint, uint>();
_gridToUnitDict ??= new Dictionary<uint, uint>();
_gridToCityDict.Clear();
@ -1343,11 +1357,12 @@ namespace RuntimeData
foreach (var kv in UnitToGridDict) _gridToUnitDict[kv.Value] = kv.Key;
// 重绑定
GridMap.BindMapConfig(MapConfig);
GridMap?.BindMapConfig(MapConfig);
// 刷新
foreach (var action in Net.Actions)
{
if (action?.Param == null) continue;
action.Param.MapData = this;
action.Param.RefreshParams();
}
@ -1356,11 +1371,13 @@ namespace RuntimeData
// 当场上有小兵受伤前
public void BeforeUnitDamaged(SettlementInfo info)
{
var copyUnits = new List<UnitData>(UnitMap.UnitList);
using var unitHandle = THCollectionPool.GetListHandle<UnitData>(out var copyUnits);
copyUnits.AddRange(UnitMap.UnitList);
foreach (var unit in copyUnits)
{
//避免在遍历的时候直接改到了skillsList
var copy = new List<SkillBase>(unit.Skills);
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(out var copy);
copy.AddRange(unit.Skills);
foreach (var skill in copy) skill.BeforeUnitDamaged(unit, this, info);
}
}
@ -1368,10 +1385,13 @@ namespace RuntimeData
// 当场上有小兵受伤时
public void OnUnitDamaged(SettlementInfo info)
{
foreach (var unit in UnitMap.UnitList)
using var unitHandle = THCollectionPool.GetListHandle<UnitData>(out var copyUnits);
copyUnits.AddRange(UnitMap.UnitList);
foreach (var unit in copyUnits)
{
//避免在遍历的时候直接改到了skillsList
var copy = new List<SkillBase>(unit.Skills);
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(out var copy);
copy.AddRange(unit.Skills);
foreach (var skill in copy) skill.OnUnitDamaged(unit, this, info);
}
}
@ -1379,22 +1399,29 @@ namespace RuntimeData
// 当场上有小兵移动时
public void OnAnyUnitMove(MapData map, UnitData moveUnit, GridData target, MoveType moveType)
{
foreach (var unit in UnitMap.UnitList)
using var unitHandle = THCollectionPool.GetListHandle<UnitData>(out var copyUnits);
copyUnits.AddRange(UnitMap.UnitList);
foreach (var unit in copyUnits)
{
var copy = new List<SkillBase>(unit.Skills);
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(out var copy);
copy.AddRange(unit.Skills);
foreach (var skill in copy) skill.OnAnyUnitMove(map, unit, moveUnit, target, moveType);
}
var gridCopy = new List<SkillBase>(target.Skills);
if (target?.Skills == null) return;
using var gridSkillHandle = THCollectionPool.GetListHandle<SkillBase>(out var gridCopy);
gridCopy.AddRange(target.Skills);
foreach (var skill in gridCopy) skill.OnAnyUnitMove(map, target, moveUnit, target, moveType);
}
// 当行为执行后
public void OnActionExecuted(ActionLogicBase logic, CommonActionParams param)
{
var unitListCopy = new List<UnitData>(UnitMap.UnitList);
using var unitHandle = THCollectionPool.GetListHandle<UnitData>(out var unitListCopy);
unitListCopy.AddRange(UnitMap.UnitList);
foreach (var unit in unitListCopy)
{
var copy = new List<SkillBase>(unit.Skills);
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(out var copy);
copy.AddRange(unit.Skills);
foreach (var skill in copy) skill.OnActionExecuted(logic, param, unit);
}
}
@ -1402,11 +1429,13 @@ namespace RuntimeData
// 当场上有小兵死亡时
public void OnAnyUnitDie(MapData map, UnitData dieUnit)
{
var unitListCopy = new List<UnitData>(UnitMap.UnitList);
using var unitHandle = THCollectionPool.GetListHandle<UnitData>(out var unitListCopy);
unitListCopy.AddRange(UnitMap.UnitList);
foreach (var unit in unitListCopy)
{
//避免在遍历的时候直接改到了skillsList
var copy = new List<SkillBase>(unit.Skills);
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(out var copy);
copy.AddRange(unit.Skills);
foreach (var skill in copy) skill.OnAnyUnitDie(map, unit, dieUnit);
}
}
@ -1414,17 +1443,20 @@ namespace RuntimeData
// 当场上有小兵创建时
public void OnAnyUnitCreate(MapData map, UnitData newUnit)
{
foreach (var unit in UnitMap.UnitList)
using var unitHandle = THCollectionPool.GetListHandle<UnitData>(out var unitListCopy);
unitListCopy.AddRange(UnitMap.UnitList);
foreach (var unit in unitListCopy)
{
//避免在遍历的时候直接改到了skillsList
var copy = new List<SkillBase>(unit.Skills);
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(out var copy);
copy.AddRange(unit.Skills);
foreach (var skill in copy) skill.OnAnyUnitCreate(map, unit, newUnit);
}
}
public static void SaveMatchConfig(MapConfig config)
public static bool SaveMatchConfig(MapConfig config)
{
if (config == null) return;
if (config == null) return false;
// 改为二进制文件扩展名
string path = Application.persistentDataPath + "/../Config/match_config.dat";
@ -1435,10 +1467,14 @@ namespace RuntimeData
try
{
byte[] bytes = MemoryPackSerializer.Serialize(config);
File.WriteAllBytes(path, bytes);
return;
if (FileTools.SafeWriteFile(path, bytes)) return true;
retryCount--;
if (retryCount <= 0)
{
LogSystem.LogError($"保存地图配置数据失败: 安全写入失败");
}
}
catch (IOException ex)
catch (Exception ex)
{
retryCount--;
if (retryCount <= 0)
@ -1447,13 +1483,31 @@ namespace RuntimeData
}
}
}
return false;
}
public static MapConfig GetMatchConfig()
{
string path = Application.persistentDataPath + "/../Config/match_config.dat";
return ReadMatchConfigWithBackup(path);
}
private static MapConfig ReadMatchConfigWithBackup(string path)
{
var config = ReadMatchConfigFile(path);
if (config != null) return config;
var backupPath = path + ".bak";
if (!File.Exists(backupPath)) return null;
LogSystem.LogWarning($"读取地图配置数据失败,尝试读取备份: {backupPath}");
return ReadMatchConfigFile(backupPath);
}
private static MapConfig ReadMatchConfigFile(string path)
{
if (!File.Exists(path)) return null;
int retryCount = 3;
while (retryCount > 0)
{
@ -1489,9 +1543,9 @@ namespace RuntimeData
return null;
}
public static void SaveMapData(MapData map, bool isBegin=false, bool isEnd=false)
public static bool SaveMapData(MapData map, bool isBegin=false, bool isEnd=false)
{
if (map == null) return;
if (map == null) return false;
// 改为二进制文件扩展名
string path = Application.persistentDataPath + "/../Config/map_archive";
@ -1507,10 +1561,14 @@ namespace RuntimeData
try
{
byte[] bytes = MemoryPackSerializer.Serialize(map);
File.WriteAllBytes(path, bytes);
return;
if (FileTools.SafeWriteFile(path, bytes)) return true;
retryCount--;
if (retryCount <= 0)
{
LogSystem.LogError($"保存地图数据失败: 安全写入失败");
}
}
catch (IOException ex)
catch (Exception ex)
{
retryCount--;
if (retryCount <= 0)
@ -1519,6 +1577,8 @@ namespace RuntimeData
}
}
}
return false;
}
public static MapData GetMapData(bool isMulti = false, bool isBegin = false, bool isEnd = false, uint mapId = 0)
@ -1535,7 +1595,9 @@ namespace RuntimeData
// 获取所有匹配的文件
if (!Directory.Exists(directory)) return null;
var files = Directory.GetFiles(directory, pattern);
var files = Directory.GetFiles(directory, pattern)
.Concat(Directory.GetFiles(directory, pattern + ".bak"))
.ToArray();
if (files.Length == 0) return null;
// 按文件修改时间从新到旧排序
var sortedFiles = files.OrderByDescending(File.GetLastWriteTime).ToList();
@ -1544,6 +1606,23 @@ namespace RuntimeData
if (mapId != 0) targetFile = sortedFiles.FirstOrDefault(f => f.Contains($"_{mapId}.dat"));
if (targetFile == null) return null;
return ReadMapDataWithBackup(targetFile);
}
private static MapData ReadMapDataWithBackup(string targetFile)
{
var mapData = ReadMapDataFile(targetFile);
if (mapData != null) return mapData;
var backupPath = targetFile + ".bak";
if (!File.Exists(backupPath)) return null;
LogSystem.LogWarning($"读取地图数据失败,尝试读取备份: {backupPath}");
return ReadMapDataFile(backupPath);
}
private static MapData ReadMapDataFile(string targetFile)
{
int retryCount = 3;
while (retryCount > 0)
{
@ -1553,7 +1632,14 @@ namespace RuntimeData
var mapData = MemoryPackSerializer.Deserialize<MapData>(bytes);
// 版本校验:检查反序列化后的数据是否有效
if (mapData?.MapConfig == null || mapData.GridMap == null || mapData.PlayerMap == null)
if (mapData == null
|| mapData.DeserializedMissingCriticalData
|| mapData.MapConfig == null
|| mapData.GridMap == null
|| mapData.PlayerMap == null
|| mapData.CityMap == null
|| mapData.UnitMap == null
|| mapData.Net == null)
{
LogSystem.LogError($"反序列化后的地图数据不完整,可能是版本不兼容");
return null;
@ -1698,11 +1784,14 @@ namespace RuntimeData
}
// 存档
SaveMapData(Main.MapData);
var saveSucceeded = SaveMapData(Main.MapData);
AchievementDataManager.Instance.SaveAchievementData();
if (Main.MapData.Net.Mode == NetMode.Single) PlayerPrefs.SetInt("Archive", 1);
if (Main.MapData.Net.Mode == NetMode.Multi) PlayerPrefs.SetInt("MultiArchive", 1);
PlayerPrefs.Save();
if (saveSucceeded)
{
if (Main.MapData.Net.Mode == NetMode.Single) PlayerPrefs.SetInt("Archive", 1);
if (Main.MapData.Net.Mode == NetMode.Multi) PlayerPrefs.SetInt("MultiArchive", 1);
PlayerPrefs.Save();
}
// 设置当前玩家
Main.PlayerLogic.StartPlayerTurn(this, nextPlayer.Id);
}

View File

@ -52,6 +52,7 @@ namespace RuntimeData
public float ErrorTime;
[MemoryPackIgnore]
public float ConfirmTime;
public bool AIControl;
[MemoryPackConstructor]
@ -65,6 +66,7 @@ namespace RuntimeData
MemberId = id;
ConfirmTime = Time.time;
State = MemberNetState.OK;
AIControl = false;
}
public void OnSendHeartbeat()
@ -97,8 +99,8 @@ namespace RuntimeData
public bool IsNeedAI()
{
if (State == MemberNetState.Leaved) return true;
return State != MemberNetState.OK && Time.time - ErrorTime > 10f;
// 房主可手动指定 AI 接管;异常/离线状态持续一段时间后也自动接管。
return AIControl || (State != MemberNetState.OK && Time.time - ErrorTime > 10f);
}
}
@ -201,19 +203,20 @@ namespace RuntimeData
return _random;
}
public void RefreshPlayerNet(MapData mapData)
public bool RefreshPlayerNet(MapData mapData)
{
if (Mode != NetMode.Multi) return;
if (mapData?.PlayerMap?.PlayerDataList == null) return;
if (Mode != NetMode.Multi) return true;
if (mapData?.PlayerMap?.PlayerDataList == null) return false;
var lobby = LobbyManager.Instance.Lobby;
if (lobby == null || !lobby.IsInLobby()) return;
if (lobby == null || !lobby.IsInLobby()) return false;
Players ??= new Dictionary<ulong, uint>();
var lobbyMemberIds = lobby.GetAllMemberIds();
if (lobbyMemberIds == null || lobbyMemberIds.Count == 0) return false;
var lobbyMemberSet = new HashSet<ulong>(lobbyMemberIds);
if (lobby.IsLobbyOwner())
{
var lobbyMemberIds = lobby.GetAllMemberIds();
var lobbyMemberSet = new HashSet<ulong>(lobbyMemberIds);
var staleMemberIds = new List<ulong>();
foreach (var kv in Players)
{
@ -255,10 +258,46 @@ namespace RuntimeData
}
var selfMemberId = lobby.GetSelfMemberId();
if (Players.TryGetValue(selfMemberId, out var id))
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}");
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}");
return false;
}
if (!seenPlayerIds.Add(kv.Value))
{
LogSystem.LogWarning($"RefreshPlayerNet duplicate PlayerId mapping: player={kv.Value}");
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}");
return false;
}
}
public uint GetActionVersion()

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Logic.CrashSight;
using Unity.VisualScripting;
using UnityEngine;
@ -27,6 +26,8 @@ public class Timer
// 注册定时任务
public void TimerRegister(object A, Action B, float C,string message)
{
if (B == null) return;
if (IsTargetDestroyed(A)) return;
tasks.Add(new TimerTask
{
target = A,
@ -36,22 +37,61 @@ public class Timer
});
}
public void CancelByTarget(object target)
{
if (target == null) return;
for (int i = tasks.Count - 1; i >= 0; i--)
{
if (ReferenceEquals(tasks[i].target, target))
tasks.RemoveAt(i);
}
}
public void CancelByMessage(string message)
{
if (string.IsNullOrEmpty(message)) return;
for (int i = tasks.Count - 1; i >= 0; i--)
{
if (tasks[i].errorMessage == message)
tasks.RemoveAt(i);
}
}
public void Clear()
{
tasks.Clear();
}
public void Update()
{
if (tasks.Count == 0) return;
float currentTime = Time.time;
for (int i = tasks.Count - 1; i >= 0; i--)
var i = tasks.Count - 1;
while (i >= 0)
{
if (currentTime >= tasks[i].executeTime)
if (i >= tasks.Count)
{
i = tasks.Count - 1;
continue;
}
var task = tasks[i];
if (IsTargetDestroyed(task.target))
{
tasks.RemoveAt(i);
i = Math.Min(i - 1, tasks.Count - 1);
continue;
}
if (currentTime >= task.executeTime)
{
try
{
tasks[i].method?.Invoke();
task.method?.Invoke();
}
catch (Exception e)
{
var task = tasks[i];
LogSystem.LogError($"Timer任务执行异常:\n" +
$"错误信息: {task.errorMessage}\n" +
$"异常类型: {e.GetType()}\n" +
@ -61,8 +101,18 @@ public class Timer
);
}
tasks.RemoveAt(i); // 执行完毕后移除
var taskIndex = tasks.IndexOf(task);
if (taskIndex >= 0) tasks.RemoveAt(taskIndex); // 执行完毕后移除
}
i = Math.Min(i - 1, tasks.Count - 1);
}
}
private static bool IsTargetDestroyed(object target)
{
if (target is UnityEngine.Object unityObject)
return unityObject == null;
return false;
}
}

View File

@ -1125,11 +1125,19 @@ namespace Logic.Action
{
if (LobbyManager.Instance.Lobby.IsLobbyOwner())
{
GameNetSender.Instance.ActionExecute(_actionId, actionParams);
if (!GameNetSender.Instance.ActionExecute(_actionId, actionParams))
{
LogSystem.LogError($"ActionExecute broadcast failed, abort owner execute: {ActionId.GetStringLog()}");
return false;
}
}
else
{
GameNetSender.Instance.ActionConfirm(_actionId, actionParams);
if (!GameNetSender.Instance.ActionConfirm(_actionId, actionParams))
{
LogSystem.LogError($"ActionConfirm send failed, abort local execute: {ActionId.GetStringLog()}");
return false;
}
if (ActionId.ActionType == CommonActionType.TurnEnd) return false;
}
}
@ -2465,4 +2473,4 @@ namespace Logic.Action
return player.PlayerCultureInfo.CheckCanBuyCultureCard(map, player, _actionId.CultureCardType);
}
}
}
}

View File

@ -45,7 +45,8 @@ namespace Logic
private Main _main;
private AILogic _aiLogic;
private Dictionary<GameState, GameStateBase> _gameStateDict;
private float _reconnectTime;
private float _reconnectCheckTime;
private float _connectionStateCheckTime;
private float _confirmTime;
private float _heartbeatTime;
private float _forceUpdateTime;
@ -146,10 +147,9 @@ namespace Logic
var memberId = Main.MapData.Net.GetmemberIdId(Main.MapData.CurPlayer.Id);
if (memberId == 0) return true;
return false;
if (!LobbyManager.Instance.Lobby.IsMemberInLobby(memberId)) return true;
var confirm = Main.Instance.ConfirmMap.GetPlayerConfirm(memberId);
if (confirm == null) return true;
if (confirm.AIControl) return true;
return confirm.IsNeedAI();
}
@ -158,9 +158,9 @@ namespace Logic
{
if (!LobbyManager.Instance.Lobby.IsInLobby()) return;
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
_reconnectTime += Time.deltaTime;
if (_reconnectTime < 2f) return;
_reconnectTime = 0;
_reconnectCheckTime += Time.deltaTime;
if (_reconnectCheckTime < 2f) return;
_reconnectCheckTime = 0;
LobbyManager.Instance.Lobby.CheckConnectionStatus();
}
@ -192,9 +192,9 @@ namespace Logic
{
if (Main.MapData == null) return;
if (!LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
_reconnectTime += Time.deltaTime;
if (_reconnectTime < 2f) return;
_reconnectTime = 0;
_connectionStateCheckTime += Time.deltaTime;
if (_connectionStateCheckTime < 2f) return;
_connectionStateCheckTime = 0;
foreach (var kv in Main.Instance.ConfirmMap.PlayerConfirm)
{

View File

@ -131,7 +131,6 @@ namespace TH1_Logic.Core
void Start()
{
PlayerPrefs.SetInt("MultiArchive", 1);
Application.runInBackground = true;
IsNetActionExecuting = false;
//step #1 mapData清空
@ -211,11 +210,11 @@ namespace TH1_Logic.Core
// 开始单机游戏
public void StartMatch()
{
//step #1 初始化Audio
InitGameAudio();
MapData = new MapData(MapConfig, NetMode.Single);
MapData.Net.Mode = NetMode.Single;
//step #1 初始化Audio
InitGameAudio();
// 收集数据刷新
CollectManager.Instance.Init();
@ -271,8 +270,6 @@ namespace TH1_Logic.Core
Main.Instance.CheckMapData = MemoryPack.MemoryPackSerializer.Deserialize<MapData>(bt);
#endif
MapData.RefreshTurn();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
// 继续单机游戏
@ -280,14 +277,14 @@ namespace TH1_Logic.Core
{
//如果没有存档,退出
if (!HasArchive()) return false;
//step #1 初始化Audio
InitGameAudio();
//step #2 读取存档的map
var resumeMap = MapData.GetMapData();
if (resumeMap == null) return false;
MapData = resumeMap;
//step #1 初始化Audio
InitGameAudio();
MapData.PlayerMap.SelfPlayerId = MapData.PlayerMap.PlayerDataList[0].Id;
// 成就绑定
@ -311,80 +308,101 @@ namespace TH1_Logic.Core
camera.CameraFocusOnGrid(grid,true);
MapData.RefreshTurn();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
return true;
}
// 房主开始多人游戏
public void MainMemberStartMatch()
public bool MainMemberStartMatch()
{
//停止音乐然后为这句游戏初始化一个AudioManager的子模块
AudioManager.Instance.StopMusic();
AudioManager.Instance.InGameAudioInit(this,MapData);
MapData = new MapData(MapConfig, NetMode.Multi);
MapData.Net.Mode = NetMode.Multi;
MapData.Net.RefreshPlayerNet(MapData);
// 收集数据刷新
CollectManager.Instance.Init();
//清空MapRenderer,然后重新初始化
//TODO 和文波确认流程
MapRenderer.Dispose();
MapRenderer.Initialize(this,MapData);
UIManager.Instance.OnMatchStart();
//初始化交互相关的logic
InputLogic = new InputLogic(this,MapData);
MapInteractionLogic = new MapInteraction(this,MapData);
//初始化地图生成器
MapGeneratorLogic = new MapGenerator(this,MapData);
bool useCustomMap = false;
if (MapConfig.IsCustomMap)
var previousMap = MapData;
var previousInput = InputLogic;
var previousInteraction = MapInteractionLogic;
var previousGenerator = MapGeneratorLogic;
try
{
var mapRecord = MapRecordManager.Instance.LoadMapRecord(MapConfig.MapName);
if (MapData == null)
var newMapData = new MapData(MapConfig, NetMode.Multi);
newMapData.Net.Mode = NetMode.Multi;
if (!newMapData.Net.RefreshPlayerNet(newMapData))
{
LogSystem.LogError($"StartMatch: 未能加载自定义地图 {MapConfig.MapName}");
LogSystem.LogError("MainMemberStartMatch: 玩家网络映射失败");
return false;
}
else
MapData = newMapData;
//停止音乐然后为这句游戏初始化一个AudioManager的子模块
AudioManager.Instance.StopMusic();
AudioManager.Instance.InGameAudioInit(this,MapData);
// 收集数据刷新
CollectManager.Instance.Init();
//清空MapRenderer,然后重新初始化
//TODO 和文波确认流程
MapRenderer.Dispose();
MapRenderer.Initialize(this,MapData);
UIManager.Instance.OnMatchStart();
//初始化交互相关的logic
InputLogic = new InputLogic(this,MapData);
MapInteractionLogic = new MapInteraction(this,MapData);
//初始化地图生成器
MapGeneratorLogic = new MapGenerator(this,MapData);
bool useCustomMap = false;
if (MapConfig.IsCustomMap)
{
MapData.RegenerateMap(mapRecord);
useCustomMap = true;
var mapRecord = MapRecordManager.Instance.LoadMapRecord(MapConfig.MapName);
if (mapRecord == null)
{
LogSystem.LogError($"StartMatch: 未能加载自定义地图 {MapConfig.MapName}");
AbortHostMultiStart("MainMemberStartMatch: 自定义地图加载失败", previousMap, previousInput, previousInteraction, previousGenerator);
return false;
}
else
{
MapData.RegenerateMap(mapRecord);
useCustomMap = true;
}
}
if (!useCustomMap) MapGeneratorLogic.GenerateMap(MapData);
MapRenderer.Instance.FirstRenderMap();
MapGeneratorLogic.GenerateMapAfterMapRenderer(MapData);
AfterMapAddtion();
AIActionScoreCalculator.RefreshCalMap(MapData, true);
AchievementDataManager.Instance.OnGameStart(MapData);
if (!GameNetSender.Instance.GameStart())
{
AbortHostMultiStart("MainMemberStartMatch: GameStart 广播失败", previousMap, previousInput, previousInteraction, previousGenerator);
return false;
}
//移动镜头+显示开局提示
var camera = GameObject.Find("Main Camera").GetComponent<CameraController>();
var selfp = MapData.PlayerMap.SelfPlayerData;
if(MapData.GetCapitalCityDataByPlayerId(selfp.Id, out var cap) && MapData.GetGridDataByCityId(cap.Id, out var grid))
camera.CameraFocusOnGrid(grid,true);
Timer.Instance.TimerRegister(this, () =>
{
//start game
var announcement = new ShowUIAnnounceMajorEvent { EventType = UIAnnounceMajorEventType.StartGame};
EventManager.Publish(announcement);
//UIManager.Instance.CenterMessageUI.SetCenterMessageShow(UICenterMessageID.StartGame,MapData.PlayerMap.SelfPlayerData);
},1.5f,"Main_CenterMessage_Anim");
MapData.SaveMapData(MapData, true);
MapData.RefreshTurn();
LogSystem.LogInfo($"MainMemberStartMatch : {NetData.GetMapDataHash(MapData)}");
return true;
}
if (!useCustomMap) MapGeneratorLogic.GenerateMap(MapData);
MapRenderer.Instance.FirstRenderMap();
MapGeneratorLogic.GenerateMapAfterMapRenderer(MapData);
AfterMapAddtion();
AIActionScoreCalculator.RefreshCalMap(MapData, true);
AchievementDataManager.Instance.OnGameStart(MapData);
//移动镜头+显示开局提示
var camera = GameObject.Find("Main Camera").GetComponent<CameraController>();
var selfp = MapData.PlayerMap.SelfPlayerData;
if(MapData.GetCapitalCityDataByPlayerId(selfp.Id, out var cap) && MapData.GetGridDataByCityId(cap.Id, out var grid))
camera.CameraFocusOnGrid(grid,true);
Timer.Instance.TimerRegister(this, () =>
catch (Exception e)
{
//start game
var announcement = new ShowUIAnnounceMajorEvent { EventType = UIAnnounceMajorEventType.StartGame};
EventManager.Publish(announcement);
//UIManager.Instance.CenterMessageUI.SetCenterMessageShow(UICenterMessageID.StartGame,MapData.PlayerMap.SelfPlayerData);
},1.5f,"Main_CenterMessage_Anim");
GameNetSender.Instance.GameStart();
MapData.SaveMapData(MapData, true);
MapData.RefreshTurn();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
LogSystem.LogInfo($"MainMemberStartMatch : {NetData.GetMapDataHash(MapData)}");
AbortHostMultiStart($"MainMemberStartMatch failed: {e}", previousMap, previousInput, previousInteraction, previousGenerator);
return false;
}
}
// 房主继续多人游戏
@ -392,141 +410,240 @@ namespace TH1_Logic.Core
{
//如果没有存档,退出
if (!HasMultiArchive()) return false;
//step #1 初始化Audio
InitGameAudio();
//step #2 读取存档的map
var resumeMap = MapData.GetMapData();
if (resumeMap == null) return false;
MapData = resumeMap;
MapData.Net.RefreshPlayerNet(MapData);
// 收集数据刷新
CollectManager.Instance.Init();
// 成就绑定
AchievementDataManager.Instance.OnGameStart(MapData);
// AI 计算提前备份
AIActionScoreCalculator.RefreshCalMap(MapData, true);
//step #3 初始化map相关的模块
InitMapAddtion();
AfterMapAddtion();
//step #4 渲染map
MapRenderer.Instance.FirstRenderMap();
var previousMap = MapData;
var previousInput = InputLogic;
var previousInteraction = MapInteractionLogic;
var previousGenerator = MapGeneratorLogic;
try
{
//step #1 读取存档的map
var resumeMap = MapData.GetMapData(isMulti: true);
if (resumeMap == null) return false;
if (resumeMap.Net == null || resumeMap.Net.Mode != NetMode.Multi)
{
LogSystem.LogError("MainMemberResumeMatch: 读取到的不是有效多人存档");
return false;
}
if (!resumeMap.Net.RefreshPlayerNet(resumeMap))
{
LogSystem.LogError("MainMemberResumeMatch: 玩家网络映射失败");
return false;
}
MapData = resumeMap;
var camera = GameObject.Find("Main Camera").GetComponent<CameraController>();
var selfp = MapData.PlayerMap.SelfPlayerData;
if(MapData.GetCapitalCityDataByPlayerId(selfp.Id, out var cap)
&& MapData.GetGridDataByCityId(cap.Id, out var grid))
camera.CameraFocusOnGrid(grid,true);
//step #2 初始化Audio
InitGameAudio();
GameNetSender.Instance.GameStart();
MapData.RefreshTurn();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
LogSystem.LogInfo($"MainMemberResumeMatch : {NetData.GetMapDataHash(MapData)}");
return true;
// 收集数据刷新
CollectManager.Instance.Init();
// 成就绑定
AchievementDataManager.Instance.OnGameStart(MapData);
// AI 计算提前备份
AIActionScoreCalculator.RefreshCalMap(MapData, true);
//step #3 初始化map相关的模块
InitMapAddtion();
AfterMapAddtion();
//step #4 渲染map
MapRenderer.Instance.FirstRenderMap();
var camera = GameObject.Find("Main Camera").GetComponent<CameraController>();
var selfp = MapData.PlayerMap.SelfPlayerData;
if(MapData.GetCapitalCityDataByPlayerId(selfp.Id, out var cap)
&& MapData.GetGridDataByCityId(cap.Id, out var grid))
camera.CameraFocusOnGrid(grid,true);
if (!GameNetSender.Instance.GameStart())
{
AbortHostMultiStart("MainMemberResumeMatch: GameStart 广播失败", previousMap, previousInput, previousInteraction, previousGenerator);
return false;
}
MapData.RefreshTurn();
LogSystem.LogInfo($"MainMemberResumeMatch : {NetData.GetMapDataHash(MapData)}");
return true;
}
catch (Exception e)
{
AbortHostMultiStart($"MainMemberResumeMatch failed: {e}", previousMap, previousInput, previousInteraction, previousGenerator);
return false;
}
}
private void AbortHostMultiStart(string reason, MapData previousMap, InputLogic previousInput,
MapInteraction previousInteraction, MapGenerator previousGenerator)
{
LogSystem.LogError(reason);
Timer.Instance.CancelByMessage("Main_CenterMessage_Anim");
MapGeneratorLogic = previousGenerator;
RestoreNetworkMatchState(previousMap, previousInput, previousInteraction, reason);
}
// 成员客户端开始多人游戏
public void NetStartGame(MapData map)
public bool NetStartGame(MapData map)
{
//停止音乐然后为这句游戏初始化一个AudioManager的子模块
AudioManager.Instance.StopMusic();
AudioManager.Instance.InGameAudioInit(this,MapData);
// 收集数据刷新
CollectManager.Instance.Init();
MapData = map;
MapData.Net.RefreshPlayerNet(MapData);
AIActionScoreCalculator.RefreshCalMap(MapData, true);
AchievementDataManager.Instance.OnGameStart(MapData);
//清空MapRenderer,然后重新初始化
MapRenderer.Dispose();
MapRenderer.Initialize(this,MapData);
//byte[] bt = MemoryPack.MemoryPackSerializer.Serialize(MapData);
//var test = MemoryPack.MemoryPackSerializer.Deserialize<MapData>(bt);
UIManager.Instance.OnMatchStart();
// Main.MapData.CompareEqual(test);
// var diff = MapData.FindDifferences(Main.MapData, test);
// var allStr = "";
// foreach (var str in diff) allStr+= str + "\n";
// LogSystem.LogError($"{allStr}");
//初始化交互相关的logic
InputLogic = new InputLogic(this,MapData);
MapInteractionLogic = new MapInteraction(this,MapData);
MapRenderer.Instance.FirstRenderMap();
// 对齐其他 Match 入口:补 AfterMapAddtion触发 UIManager.AfterMatchStart
// → BottomBarController.RefreshLimitEffects(),确保 NextTurn 按钮初始可见。
// 否则成员客户端进局后 NextTurn 按钮一直处于 Hide 状态,自己回合也不显示。
AfterMapAddtion();
var camera = GameObject.Find("Main Camera").GetComponent<CameraController>();
var selfp = MapData.PlayerMap.SelfPlayerData;
if(MapData.GetCapitalCityDataByPlayerId(selfp.Id, out var cap)
&& MapData.GetGridDataByCityId(cap.Id, out var grid))
camera.CameraFocusOnGrid(grid,true);
Timer.Instance.TimerRegister(this, () =>
if (!ValidateNetworkStartMap(map, "NetStartGame"))
{
//start game
var announcement = new ShowUIAnnounceMajorEvent { EventType = UIAnnounceMajorEventType.StartGame};
EventManager.Publish(announcement);
//UIManager.Instance.CenterMessageUI.SetCenterMessageShow(UICenterMessageID.StartGame,MapData.PlayerMap.SelfPlayerData);
},1.5f,"Main_CenterMessage_Anim");
LogSystem.LogInfo($"NetStartGame : {NetData.GetMapDataHash(MapData)}");
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
return false;
}
var previousMap = MapData;
var previousInput = InputLogic;
var previousInteraction = MapInteractionLogic;
try
{
MapData = map;
//停止音乐然后为这句游戏初始化一个AudioManager的子模块
AudioManager.Instance.StopMusic();
AudioManager.Instance.InGameAudioInit(this,MapData);
// 收集数据刷新
CollectManager.Instance.Init();
AIActionScoreCalculator.RefreshCalMap(MapData, true);
AchievementDataManager.Instance.OnGameStart(MapData);
//清空MapRenderer,然后重新初始化
MapRenderer.Dispose();
MapRenderer.Initialize(this,MapData);
UIManager.Instance.OnMatchStart();
//初始化交互相关的logic
InputLogic = new InputLogic(this,MapData);
MapInteractionLogic = new MapInteraction(this,MapData);
MapRenderer.Instance.FirstRenderMap();
// 对齐其他 Match 入口:补 AfterMapAddtion触发 UIManager.AfterMatchStart
// → BottomBarController.RefreshLimitEffects(),确保 NextTurn 按钮初始可见。
// 否则成员客户端进局后 NextTurn 按钮一直处于 Hide 状态,自己回合也不显示。
AfterMapAddtion();
var camera = GameObject.Find("Main Camera").GetComponent<CameraController>();
var selfp = MapData.PlayerMap.SelfPlayerData;
if(MapData.GetCapitalCityDataByPlayerId(selfp.Id, out var cap)
&& MapData.GetGridDataByCityId(cap.Id, out var grid))
camera.CameraFocusOnGrid(grid,true);
Timer.Instance.TimerRegister(this, () =>
{
//start game
var announcement = new ShowUIAnnounceMajorEvent { EventType = UIAnnounceMajorEventType.StartGame};
EventManager.Publish(announcement);
//UIManager.Instance.CenterMessageUI.SetCenterMessageShow(UICenterMessageID.StartGame,MapData.PlayerMap.SelfPlayerData);
},1.5f,"Main_CenterMessage_Anim");
LogSystem.LogInfo($"NetStartGame : {NetData.GetMapDataHash(MapData)}");
return true;
}
catch (System.Exception e)
{
LogSystem.LogError($"NetStartGame failed: {e}");
RestoreNetworkMatchState(previousMap, previousInput, previousInteraction, "NetStartGame");
return false;
}
}
// 成员客户端断线重连
public void NetResumeMatch(MapData map)
public bool NetResumeMatch(MapData map)
{
//停止音乐然后为这句游戏初始化一个AudioManager的子模块
AudioManager.Instance.StopMusic();
AudioManager.Instance.InGameAudioInit(this,MapData);
MapData = map;
MapData.Net.RefreshPlayerNet(MapData);
AIActionScoreCalculator.RefreshCalMap(MapData, true);
AchievementDataManager.Instance.OnGameStart(MapData);
//清空MapRenderer,然后重新初始化
MapRenderer.Dispose();
MapRenderer.Initialize(this,MapData);
UIManager.Instance.OnMatchStart();
//初始化交互相关的logic
InputLogic = new InputLogic(this,MapData);
MapInteractionLogic = new MapInteraction(this,MapData);
MapRenderer.Instance.FirstRenderMap();
// 对齐其他 Match 入口:补 AfterMapAddtion触发 UIManager.AfterMatchStart
// → BottomBarController.RefreshLimitEffects(),确保 NextTurn 按钮初始可见。
// 否则成员客户端断线重连后 NextTurn 按钮一直处于 Hide 状态,自己回合也不显示。
AfterMapAddtion();
var camera = GameObject.Find("Main Camera").GetComponent<CameraController>();
var selfp = MapData.PlayerMap.SelfPlayerData;
if(MapData.GetCapitalCityDataByPlayerId(selfp.Id, out var cap)
&& MapData.GetGridDataByCityId(cap.Id, out var grid))
camera.CameraFocusOnGrid(grid,true);
/*Timer.Instance.TimerRegister(this, () =>
if (!ValidateNetworkStartMap(map, "NetResumeMatch"))
{
//start game
var announcement = new ShowUIAnnounceMajorEvent { EventType = UIAnnounceMajorEventType.StartGame};
EventManager.Publish(announcement);
//UIManager.Instance.CenterMessageUI.SetCenterMessageShow(UICenterMessageID.StartGame,MapData.PlayerMap.SelfPlayerData);
},1.5f,"Main_CenterMessage_Anim");*/
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
LogSystem.LogInfo($"NetStartGame : {NetData.GetMapDataHash(MapData)}");
return false;
}
var previousMap = MapData;
var previousInput = InputLogic;
var previousInteraction = MapInteractionLogic;
try
{
AIActionScoreCalculator.RefreshCalMap(map, true);
MapData = map;
//停止音乐然后为这句游戏初始化一个AudioManager的子模块
AudioManager.Instance.StopMusic();
AudioManager.Instance.InGameAudioInit(this,MapData);
AchievementDataManager.Instance.OnGameStart(MapData);
//清空MapRenderer,然后重新初始化
MapRenderer.Dispose();
MapRenderer.Initialize(this,MapData);
UIManager.Instance.OnMatchStart();
//初始化交互相关的logic
InputLogic = new InputLogic(this,MapData);
MapInteractionLogic = new MapInteraction(this,MapData);
MapRenderer.Instance.FirstRenderMap();
// 对齐其他 Match 入口:补 AfterMapAddtion触发 UIManager.AfterMatchStart
// → BottomBarController.RefreshLimitEffects(),确保 NextTurn 按钮初始可见。
// 否则成员客户端断线重连后 NextTurn 按钮一直处于 Hide 状态,自己回合也不显示。
AfterMapAddtion();
var camera = GameObject.Find("Main Camera").GetComponent<CameraController>();
var selfp = MapData.PlayerMap.SelfPlayerData;
if(MapData.GetCapitalCityDataByPlayerId(selfp.Id, out var cap)
&& MapData.GetGridDataByCityId(cap.Id, out var grid))
camera.CameraFocusOnGrid(grid,true);
LogSystem.LogInfo($"NetStartGame : {NetData.GetMapDataHash(MapData)}");
return true;
}
catch (System.Exception e)
{
LogSystem.LogError($"NetResumeMatch failed: {e}");
RestoreNetworkMatchState(previousMap, previousInput, previousInteraction, "NetResumeMatch");
return false;
}
}
private bool ValidateNetworkStartMap(MapData map, string context)
{
if (map?.Net == null
|| map.DeserializedMissingCriticalData
|| map.MapConfig == null
|| map.GridMap == null
|| map.PlayerMap == null
|| map.CityMap == null
|| map.UnitMap == null)
{
LogSystem.LogError($"{context}: map is invalid");
return false;
}
if (!map.Net.RefreshPlayerNet(map))
{
LogSystem.LogError($"{context}: 玩家网络映射失败");
return false;
}
if (map.PlayerMap.SelfPlayerData == null)
{
LogSystem.LogError($"{context}: SelfPlayerData is invalid");
return false;
}
return true;
}
private void RestoreNetworkMatchState(MapData previousMap, InputLogic previousInput, MapInteraction previousInteraction, string context)
{
MapData = previousMap;
InputLogic = previousInput;
MapInteractionLogic = previousInteraction;
try
{
MapRenderer.Dispose();
if (previousMap == null)
{
UIManager.Instance.OnMatchEnd();
return;
}
MapRenderer.Initialize(this, previousMap);
UIManager.Instance.OnMatchStart();
MapRenderer.Instance.FirstRenderMap();
}
catch (System.Exception restoreException)
{
LogSystem.LogError($"{context}: restore previous match state failed: {restoreException}");
}
}
// 开始观战
@ -564,8 +681,6 @@ namespace TH1_Logic.Core
GameLogic.ChangeState(GameState.Spectate);
MapData.RefreshTurn();
GC.Collect();
GC.WaitForPendingFinalizers();
}
// Update is called once per frame

View File

@ -92,7 +92,7 @@ namespace TH1_Logic.Net
public bool SendMessageToPeer(ulong member, byte[] data, bool reliable = true);
// 广播P2P消息
public void BroadcastMessage(byte[] data, bool reliable = true);
public bool BroadcastMessage(byte[] data, bool reliable = true);
// 获取当前房间ID用于分享
public ulong GetShareableLobbyId();
@ -236,9 +236,9 @@ namespace TH1_Logic.Net
return false;
}
public void BroadcastMessage(byte[] data, bool reliable = true)
public bool BroadcastMessage(byte[] data, bool reliable = true)
{
return;
return false;
}
public ulong GetShareableLobbyId()
@ -251,4 +251,4 @@ namespace TH1_Logic.Net
return false;
}
}
}
}

View File

@ -18,25 +18,32 @@ namespace TH1_Logic.Steam
public void OnMessageReceived(byte[] data)
{
var message = MemoryPack.MemoryPackSerializer.Deserialize<BaseMessage>(data);
if (message == null) return;
if (message.MessageType == P2PMsgType.String) OnReceivedString((StringMessage)message);
if (message.MessageType == P2PMsgType.GameStart) OnReceivedGameStart((GameStartMessage)message);
if (message.MessageType == P2PMsgType.ActionConfirm) OnReceivedActionConfirm((ActionConfirmMessage)message);
if (message.MessageType == P2PMsgType.ActionExcute) OnReceivedActionExcute((ActionExcuteMessage)message);
if (message.MessageType == P2PMsgType.TurnEnd) OnReceivedTurnEnd((TurnEndMessage)message);
if (message.MessageType == P2PMsgType.MapConfirm) OnReceivedMapConfirm((MapConfirmMessage)message);
if (message.MessageType == P2PMsgType.ForceUpdate) OnReceivedForceUpdate((ForceUpdateMessage)message);
if (message.MessageType == P2PMsgType.ChangeCiv) OnReceivedChangeCiv((ChangeCivMessage)message);
if (message.MessageType == P2PMsgType.UpdateLobbyData) OnReceivedUpdateLobbyData((UpdateLobbyDataMessage)message);
if (message.MessageType == P2PMsgType.RequestLobbyData) OnReceivedRequestLobbyData((RequestLobbyDataMessage)message);
if (message.MessageType == P2PMsgType.Heartbeat) OnReceivedHeartbeat((HeartbeatMessage)message);
if (message.MessageType == P2PMsgType.MemberStateSync) OnReceivedMemberStateSync((MemberStateSyncMessage)message);
if (message.MessageType == P2PMsgType.RequestForceUpdate) OnReceivedRequestForceUpdate((RequestForceUpdateMessage)message);
if (message.MessageType == P2PMsgType.HeartbeatReply) OnReceivedHeartbeatReply((HeartbeatReplyMessage)message);
if (message.MessageType == P2PMsgType.ChatMessage) OnReceivedChatMessage((ChatMessage)message);
if (message.MessageType == P2PMsgType.InviteMessage) OnReceivedInviteMessage((InviteMessage)message);
try
{
var message = MemoryPack.MemoryPackSerializer.Deserialize<BaseMessage>(data);
if (message == null) return;
if (message.MessageType == P2PMsgType.String) OnReceivedString((StringMessage)message);
if (message.MessageType == P2PMsgType.GameStart) OnReceivedGameStart((GameStartMessage)message);
if (message.MessageType == P2PMsgType.ActionConfirm) OnReceivedActionConfirm((ActionConfirmMessage)message);
if (message.MessageType == P2PMsgType.ActionExcute) OnReceivedActionExcute((ActionExcuteMessage)message);
if (message.MessageType == P2PMsgType.TurnEnd) OnReceivedTurnEnd((TurnEndMessage)message);
if (message.MessageType == P2PMsgType.MapConfirm) OnReceivedMapConfirm((MapConfirmMessage)message);
if (message.MessageType == P2PMsgType.ForceUpdate) OnReceivedForceUpdate((ForceUpdateMessage)message);
if (message.MessageType == P2PMsgType.ChangeCiv) OnReceivedChangeCiv((ChangeCivMessage)message);
if (message.MessageType == P2PMsgType.UpdateLobbyData) OnReceivedUpdateLobbyData((UpdateLobbyDataMessage)message);
if (message.MessageType == P2PMsgType.RequestLobbyData) OnReceivedRequestLobbyData((RequestLobbyDataMessage)message);
if (message.MessageType == P2PMsgType.Heartbeat) OnReceivedHeartbeat((HeartbeatMessage)message);
if (message.MessageType == P2PMsgType.MemberStateSync) OnReceivedMemberStateSync((MemberStateSyncMessage)message);
if (message.MessageType == P2PMsgType.RequestForceUpdate) OnReceivedRequestForceUpdate((RequestForceUpdateMessage)message);
if (message.MessageType == P2PMsgType.HeartbeatReply) OnReceivedHeartbeatReply((HeartbeatReplyMessage)message);
if (message.MessageType == P2PMsgType.ChatMessage) OnReceivedChatMessage((ChatMessage)message);
if (message.MessageType == P2PMsgType.InviteMessage) OnReceivedInviteMessage((InviteMessage)message);
}
catch (System.Exception e)
{
LogSystem.LogError($"OnMessageReceived 处理失败, bytes: {data?.Length ?? 0}, error: {e}");
}
}
// 基础字符串消息
@ -61,18 +68,22 @@ namespace TH1_Logic.Steam
return;
}
if (message.MapData == null) return;
if (!IsValidIncomingMultiMap(message.MapData))
{
LogSystem.LogError("OnReceivedGameStart 收到无效多人地图数据");
return;
}
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
LogSystem.LogWarning($"OnReceivedGameStart : {NetData.GetMapDataHash(message.MapData)}");
//客户端开启游戏
//如果loading失败 直接开始游戏并关闭当前页面
UIManager.Instance.ShowLoading();
// 兜底NetStartGame 内部任何阶段抛异常都不能阻塞房客关闭多人面板,
// 否则会出现"房主已开始、房客 UIOutsideMultiplay 残留导致看似没进游戏"的现象。
// 只有真正进局成功后才关闭多人面板;失败时保留房间 UI 方便重试或提示。
var started = false;
try
{
Main.Instance.NetStartGame(message.MapData);
started = Main.Instance.NetStartGame(message.MapData);
}
catch (System.Exception e)
{
@ -80,7 +91,7 @@ namespace TH1_Logic.Steam
}
finally
{
EventManager.Publish(new HideUIOutsideMultiplay(){});
if (started) EventManager.Publish(new HideUIOutsideMultiplay(){});
}
}
@ -196,6 +207,7 @@ namespace TH1_Logic.Steam
}
if (Main.MapData == null) return;
if (Main.MapData.Net?.Actions == null) return;
// 房主收到校验信息时校验状态
if (LobbyManager.Instance.Lobby.IsLobbyOwner())
{
@ -214,6 +226,11 @@ namespace TH1_Logic.Steam
confirm.State = MemberNetState.Error;
}
}
else if (message.ActionData == null)
{
LogSystem.LogError($"房主端message.ActionData == null");
confirm.State = MemberNetState.Error;
}
else if (!Main.MapData.Net.Actions[message.Index - 1].IsEqual(message.ActionData))
{
LogSystem.LogError($"房主端:!Main.MapData.Net.Actions[message.Index - 1].IsEqual(message.ActionData)");
@ -227,11 +244,17 @@ namespace TH1_Logic.Steam
// 成员收到校验信息时校验状态
else
{
if (Main.MapData.Net.Actions.Count == 0 || message.Index == 0) return;
if (message.Index > Main.MapData.Net.Actions.Count)
{
LogSystem.LogError($"成员端: message.Index > Main.MapData.Net.Actions.Count");
GameNetSender.Instance.SendRequestForceUpdate();
}
else if (message.ActionData == null)
{
LogSystem.LogError($"成员端: message.ActionData == null");
GameNetSender.Instance.SendRequestForceUpdate();
}
else if (!Main.MapData.Net.Actions[message.Index - 1].IsEqual(message.ActionData))
{
LogSystem.LogError($"成员端: !Main.MapData.Net.Actions[message.Index - 1].IsEqual(message.ActionData)");
@ -249,31 +272,76 @@ namespace TH1_Logic.Steam
return;
}
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
if (!IsValidIncomingMultiMap(message.MapData))
{
LogSystem.LogError("OnReceivedForceUpdate 收到无效多人地图数据");
return;
}
try
{
if (!message.MapData.Net.RefreshPlayerNet(message.MapData))
{
LogSystem.LogError("OnReceivedForceUpdate 玩家网络映射失败");
return;
}
}
catch (System.Exception e)
{
LogSystem.LogError($"OnReceivedForceUpdate RefreshPlayerNet failed: {e}");
return;
}
var confirm = Main.Instance.ConfirmMap.GetPlayerConfirm(LobbyManager.Instance.Lobby.GetSelfMemberId());
LogSystem.LogError($"触发断线重连, 触发原因: {confirm?.State}");
Main.MapData?.FindDifferences(message.MapData);
try
{
Main.MapData?.FindDifferences(message.MapData);
}
catch (System.Exception e)
{
LogSystem.LogError($"OnReceivedForceUpdate FindDifferences failed: {e}");
}
// TODO 强制断线重连在这里实现
// Step #1 强制结束当前游戏对局
Main.Instance.GameLogic.ChangeState(GameState.Menu);
// Step #2 强制关闭当前所有UI
var previousState = Main.Instance.GameLogic.GetCurState();
Main.Instance.GameLogic.ChangeState(GameState.ForceUpdating);
try
{
if (!Main.Instance.NetResumeMatch(message.MapData))
{
Main.Instance.GameLogic.ChangeState(previousState);
return;
}
}
catch (System.Exception e)
{
LogSystem.LogError($"OnReceivedForceUpdate NetResumeMatch failed: {e}");
Main.Instance.GameLogic.ChangeState(previousState);
return;
}
EventManager.Publish(new HideUIOutsideAll());
//UIManager.Instance.GameUI.CloseAllGameUI();
EventManager.Publish(new HideUIOutsideMultiplay());
//TODO Hint重做
//UIManager.Instance.GameUI.NetHint.SetActive(true);
Timer.Instance.TimerRegister(this, () =>
{
//TODO Hint重做
//UIManager.Instance.GameUI.NetHint.SetActive(false);
},2f,"NetHint Wrong");
// Step #3 强制继续联机对局
Main.Instance.NetResumeMatch(message.MapData);
// Main.MapData = message.MapData;
}
private bool IsValidIncomingMultiMap(MapData mapData)
{
return 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;
}
// 只有房主会收到
private void OnReceivedChangeCiv(ChangeCivMessage message)
@ -453,4 +521,4 @@ namespace TH1_Logic.Steam
}
}
}
}

View File

@ -24,33 +24,41 @@ namespace TH1_Logic.Steam
private static float RecordTime;
// 发送消息给房主
public void SendMessage(BaseMessage message)
public bool SendMessage(BaseMessage message)
{
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return false;
byte[] messageBytes = MemoryPack.MemoryPackSerializer.Serialize(message);
LobbyManager.Instance.Lobby.SendMessageToPeer(LobbyManager.Instance.Lobby.GetLobbyOwnerId(), messageBytes);
if (LobbyManager.Instance.Lobby.SendMessageToPeer(LobbyManager.Instance.Lobby.GetLobbyOwnerId(), messageBytes)) return true;
LogSystem.LogError($"{message?.GetType().Name}: 发送给房主失败");
return false;
}
// 发送消息给指定人
public void SendMessageToPlayer(ulong memberId, BaseMessage message)
public bool SendMessageToPlayer(ulong memberId, BaseMessage message)
{
byte[] messageBytes = MemoryPack.MemoryPackSerializer.Serialize(message);
LobbyManager.Instance.Lobby.SendMessageToPeer(memberId, messageBytes);
if (LobbyManager.Instance.Lobby.SendMessageToPeer(memberId, messageBytes)) return true;
LogSystem.LogError($"{message?.GetType().Name}: 发送给成员失败 memberId={memberId}");
return false;
}
// 房主广播消息给所有成员
public void BroadcastMessage(BaseMessage message)
public bool BroadcastMessage(BaseMessage message)
{
if (!LobbyManager.Instance.Lobby.IsLobbyOwner()) return;
if (!LobbyManager.Instance.Lobby.IsLobbyOwner()) return false;
byte[] messageBytes = MemoryPack.MemoryPackSerializer.Serialize(message);
LobbyManager.Instance.Lobby.BroadcastMessage(messageBytes);
if (LobbyManager.Instance.Lobby.BroadcastMessage(messageBytes)) return true;
LogSystem.LogError($"{message?.GetType().Name}: 房主广播失败");
return false;
}
// 房主广播消息给所有成员
public void SendMessageToAllPlayer(BaseMessage message)
public bool SendMessageToAllPlayer(BaseMessage message)
{
byte[] messageBytes = MemoryPack.MemoryPackSerializer.Serialize(message);
LobbyManager.Instance.Lobby.BroadcastMessage(messageBytes);
if (LobbyManager.Instance.Lobby.BroadcastMessage(messageBytes)) return true;
LogSystem.LogError($"{message?.GetType().Name}: 广播给所有成员失败");
return false;
}
// 广播字符串
@ -62,15 +70,16 @@ namespace TH1_Logic.Steam
}
// 游戏开始
public void GameStart()
public bool GameStart()
{
if (!TryGetValidMultiMapForBroadcast("GameStart", out var mapData)) return false;
var data = new GameStartMessage();
data.MapData = Main.MapData;
BroadcastMessage(data);
data.MapData = mapData;
return BroadcastMessage(data);
}
// 请求行为 (成员 => 房主)
public void ActionConfirm(CommonActionId id, CommonActionParams param)
public bool ActionConfirm(CommonActionId id, CommonActionParams param)
{
var actionData = new ActionNetData();
actionData.Version = Main.MapData.Net.GetActionVersion();
@ -80,11 +89,11 @@ namespace TH1_Logic.Steam
var data = new ActionConfirmMessage();
data.ActionData = actionData;
SendMessage(data);
return SendMessage(data);
}
// 行为执行 (房主 => 所有成员)
public void ActionExecute(CommonActionId id, CommonActionParams param)
public bool ActionExecute(CommonActionId id, CommonActionParams param)
{
var actionData = new ActionNetData();
actionData.Version = Main.MapData.Net.GetActionVersion();
@ -101,7 +110,7 @@ namespace TH1_Logic.Steam
// RecordTime = Time.time + 0.2f;
// }
BroadcastMessage(data);
return BroadcastMessage(data);
}
// 请求回合结束 (成员 => 房主)
@ -138,18 +147,47 @@ namespace TH1_Logic.Steam
public void ForceUpdate(ulong memberId)
{
if (memberId == 0) return;
if (!TryGetValidMultiMapForBroadcast("ForceUpdate", out var mapData)) return;
var data = new ForceUpdateMessage();
data.MapData = Main.MapData;
data.MapData = mapData;
SendMessageToPlayer(memberId, data);
}
// 强制更新 (房主 => 所有成员)
public void BroadcastForceUpdate()
{
if (!TryGetValidMultiMapForBroadcast("BroadcastForceUpdate", out var mapData)) return;
var data = new ForceUpdateMessage();
data.MapData = Main.MapData;
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}: 无效多人地图数据,取消发送");
mapData = null;
return false;
}
if (!mapData.Net.RefreshPlayerNet(mapData))
{
LogSystem.LogError($"{context}: 玩家网络映射失败,取消发送");
mapData = null;
return false;
}
return true;
}
// 修改阵营 (成员 => 房主)
public void ChangeCiv(MemberCiv memberCiv)
@ -194,15 +232,17 @@ namespace TH1_Logic.Steam
// 心跳 (单成员 => 房主) 成员的心跳包
public void SendHeartbeat()
{
// 确认发送记录
var confirm = Main.Instance.ConfirmMap.GetPlayerConfirm(LobbyManager.Instance.Lobby.GetLobbyOwnerId());
confirm.OnSendHeartbeat();
// 发送
var data = new HeartbeatMessage();
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
data.State = Main.Instance.GameLogic.GetCurState();
SendMessage(data);
if (SendMessage(data))
{
// 确认发送记录
confirm.OnSendHeartbeat();
}
}
// 成员状态下发 (房主 => 成员) 房主的心跳包
@ -235,8 +275,10 @@ namespace TH1_Logic.Steam
LogSystem.LogError($"客户端请求重连: SendRequestForceUpdate");
var data = new RequestForceUpdateMessage();
data.MemberId = LobbyManager.Instance.Lobby.GetSelfMemberId();
SendMessage(data);
Main.Instance.GameLogic.ChangeState(GameState.ForceUpdating);
if (SendMessage(data))
{
Main.Instance.GameLogic.ChangeState(GameState.ForceUpdating);
}
}
// 心跳包回复 (任意成员 => 任意成员)
@ -263,4 +305,4 @@ namespace TH1_Logic.Steam
BroadcastMessage(data);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -257,6 +257,7 @@ namespace TH1_Logic.Steam
SimpleP2P.Instance.OnPeerConnectedEvent += OnP2PPeerConnected;
SimpleP2P.Instance.OnPeerDisconnectedEvent += OnP2PPeerDisconnected;
SimpleP2P.Instance.OnConnectionErrorEvent += OnP2PConnectionError;
SimpleP2P.Instance.OnMessageSendFailedEvent += OnP2PMessageSendFailed;
LogSystem.LogInfo("SteamLobbyManager initialized");
_isLobbyInitialized = true;
@ -1162,32 +1163,84 @@ namespace TH1_Logic.Steam
{
LogSystem.LogError($"P2P connection error: {error}");
}
private void OnP2PMessageSendFailed(CSteamID steamID, string reason)
{
var error = $"P2P message send failed: target={steamID}, reason={reason}";
LogSystem.LogError(error);
OnLobbyErrorEvent?.Invoke(error);
}
// 发送P2P消息
public bool SendMessageToPeer(ulong member, byte[] data, bool reliable = true)
{
if (!IsInLobby())
{
LogSystem.LogError("Not in lobby");
return false;
return ReportP2PSendPrecheckFailed(member, "Not in lobby");
}
if (!IsMemberInLobby(member)) return false;
if (member == GetSelfMemberId()) return false;
if (data == null || data.Length == 0)
return ReportP2PSendPrecheckFailed(member, "Trying to send null or empty data");
if (!IsMemberInLobby(member))
return ReportP2PSendPrecheckFailed(member, $"Target member is not in lobby: {member}");
if (member == GetSelfMemberId())
return ReportP2PSendPrecheckFailed(member, $"Trying to send P2P message to self: {member}");
var cSteamId = new CSteamID(member);
return SimpleP2P.Instance.SendTo(cSteamId, data, reliable);
}
// 广播P2P消息
public void BroadcastMessage(byte[] data, bool reliable = true)
public bool BroadcastMessage(byte[] data, bool reliable = true)
{
if (!IsInLobby())
{
LogSystem.LogError("Not in lobby");
return;
OnLobbyErrorEvent?.Invoke("P2P broadcast failed: Not in lobby");
LogSystem.LogError("P2P broadcast failed: Not in lobby");
return false;
}
SimpleP2P.Instance.Broadcast(data, reliable);
if (data == null || data.Length == 0)
{
OnLobbyErrorEvent?.Invoke("P2P broadcast failed: Trying to broadcast null or empty data");
LogSystem.LogError("P2P broadcast failed: Trying to broadcast null or empty data");
return false;
}
var selfMemberId = GetSelfMemberId();
var targets = new List<CSteamID>();
foreach (var memberId in GetAllMemberIds())
{
if (memberId == selfMemberId) continue;
targets.Add(new CSteamID(memberId));
}
if (targets.Count == 0) return true;
if (!SimpleP2P.Instance.CanQueueMessages(targets, data.Length, out var failedTarget, out var reason))
{
if (failedTarget.IsValid()) OnP2PMessageSendFailed(failedTarget, reason);
else
{
var error = $"P2P broadcast preflight failed: {reason}";
LogSystem.LogError(error);
OnLobbyErrorEvent?.Invoke(error);
}
return false;
}
foreach (var target in targets)
{
if (SimpleP2P.Instance.SendTo(target, data, reliable)) continue;
OnP2PMessageSendFailed(target, $"P2P broadcast enqueue failed after preflight: bytes={data.Length}");
return false;
}
return true;
}
private bool ReportP2PSendPrecheckFailed(ulong member, string reason)
{
OnP2PMessageSendFailed(new CSteamID(member), reason);
return false;
}
// 枚举房间成员
@ -1362,6 +1415,10 @@ namespace TH1_Logic.Steam
public void Cleanup()
{
LeaveLobby();
SimpleP2P.Instance.OnPeerConnectedEvent -= OnP2PPeerConnected;
SimpleP2P.Instance.OnPeerDisconnectedEvent -= OnP2PPeerDisconnected;
SimpleP2P.Instance.OnConnectionErrorEvent -= OnP2PConnectionError;
SimpleP2P.Instance.OnMessageSendFailedEvent -= OnP2PMessageSendFailed;
SimpleP2P.Instance.Cleanup();
_cbLobbyCreated?.Dispose();

View File

@ -737,20 +737,29 @@ namespace TH1_UI.View.Outside
//如果loading失败 直接开始游戏并关闭当前页面
if (!UIManager.Instance.ShowLoading())
{
if(!resume)
Main.Instance.MainMemberStartMatch();
else
Main.Instance.MainMemberResumeMatch();
//OnstartGame会自动执行controller的Close close会负责触发view的CloseView
OnStartGame?.Invoke();
if (TryStartHostGame(resume))
{
//OnstartGame会自动执行controller的Close close会负责触发view的CloseView
OnStartGame?.Invoke();
}
return;
}
//否则带有延时执行
Timer.Instance.TimerRegister(this,resume ? () => { Main.Instance.MainMemberResumeMatch(); } : Main.Instance.MainMemberStartMatch,0.5f,"Multi_ShowLoading");
Timer.Instance.TimerRegister(this, ()=> { OnStartGame?.Invoke(); },0.5f,"Multi_ShowLoading");
Timer.Instance.TimerRegister(this, () =>
{
if (TryStartHostGame(resume))
{
OnStartGame?.Invoke();
}
},0.5f,"Multi_ShowLoading");
}
private bool TryStartHostGame(bool resume)
{
return resume ? Main.Instance.MainMemberResumeMatch() : Main.Instance.MainMemberStartMatch();
}
public void SelectInGroup(string v, List<Button> buttons)