联机底层修改
This commit is contained in:
parent
837f406908
commit
ac1b31f33a
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
@ -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();
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user