/* * @Author: 白哉 * @Description: * @Date: 2025年04月03日 星期四 11:04:31 * @Modify: */ using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using Logic; using Logic.Action; using Logic.AI; using Logic.CrashSight; using Logic.Pool; using Logic.Skill; using MemoryPack; using TH1_Core.Events; using TH1_Core.Managers; using TH1_Logic.Collect; using TH1_Logic.Config; using TH1_Logic.Core; using TH1_Logic.GameArchive; using TH1_Logic.MatchConfig; using TH1_Logic.Net; using TH1_Logic.Steam; using TH1_Logic.Tools; using TH1Renderer; using TH1_Renderer; using UnityEngine; using MemberInfo = TH1_Logic.Net.MemberInfo; namespace RuntimeData { public enum GameMode { DOMINATION, PERFECT, CREATIVE } // 一场游戏的设置数据 [MemoryPackable] public partial class MapConfig { public uint Id; public uint Width; public uint Height; public uint PlayerCount; public AIDifficult AIDiff; public GameMode GameMode; // 指定地图配置 public bool IsCustomMap; public string MapName; // 游戏结算配置 public MatchSettlementType MatchSettlement; public List PlayerSettlements; // 旧存档兼容字段。新逻辑统一从 MultiCivs 读取单机/联机玩家槽位。 public uint selfCivId; public uint selfForceId; // 玩家位置槽位。旧名保留用于兼容现有序列化/联机消息。 public List MultiCivs; private Dictionary _memberCivs; // 校验用 [MemoryPackIgnore] private string _hash; // 关卡限制数据 public List MatchLimits; // 超时时间 public bool IsLimitTime = false; public int TimeLimitSeconds = 180; // 水域类型,默认为 Pangea public Logic.MapWaterType WaterType = Logic.MapWaterType.Pangea; public const int NoTeamId = 0; [MemoryPackIgnore] public bool IsResumeArchiveSelected; [MemoryPackConstructor] public MapConfig() { MultiCivs = new List(); PlayerSettlements = new List(); _memberCivs = new Dictionary(); MatchLimits = new List(); } public MapConfig(uint width, uint height, uint playerCount,uint civId, uint forceId, AIDifficult aiDiff = AIDifficult.LUNATIC) { Width = width; Height = height; PlayerCount = playerCount; AIDiff = aiDiff; MultiCivs = new List(); _memberCivs = new Dictionary(); PlayerSettlements = new List(); MatchLimits = new List(); SetSinglePlayerCiv(civId, forceId); } public MapConfig CreateRuntimeCopy() { var bytes = MemoryPackSerializer.Serialize(this); return MemoryPackSerializer.Deserialize(bytes); } // MemoryPack 反序列化之后的后处理 [MemoryPackOnDeserialized] public void OnAfterMemoryPackDeserialize() { MatchSettlement = MatchSettlementInfo.NormalizeType(MatchSettlement); // 旧存档兼容:WaterType 字段不存在时默认为 Pangea if (!System.Enum.IsDefined(typeof(Logic.MapWaterType), WaterType)) WaterType = Logic.MapWaterType.Pangea; MultiCivs ??= new List(); PlayerSettlements ??= new List(); MatchLimits ??= new List(); EnsureSerializedPlayerSlots(); } private void EnsureSerializedPlayerSlots(int minSlotCount = 0) { MultiCivs ??= new List(); var targetCount = Math.Max(0, (int)PlayerCount); targetCount = Math.Max(targetCount, Math.Max(0, minSlotCount)); targetCount = Math.Max(targetCount, MultiCivs.Count); PlayerCount = (uint)targetCount; for (int i = 0; i < MultiCivs.Count; i++) { MultiCivs[i] ??= CreateDefaultPlayerSlot(i, NetMode.Multi); } while (MultiCivs.Count < targetCount) { MultiCivs.Add(CreateDefaultPlayerSlot(MultiCivs.Count, NetMode.Multi)); } for (int i = 0; i < MultiCivs.Count; i++) { NormalizeSerializedPlayerSlot(MultiCivs[i], i); } RefreshMultiCivsDict(); } public void EnsurePlayerSlots(NetMode netMode = NetMode.Multi) { MultiCivs ??= new List(); var targetCount = GetRequiredPlayerSlotCount((int)PlayerCount, netMode); PlayerCount = (uint)targetCount; for (int i = 0; i < MultiCivs.Count; i++) { MultiCivs[i] ??= CreateDefaultPlayerSlot(i, netMode); } while (MultiCivs.Count < targetCount) { MultiCivs.Add(CreateDefaultPlayerSlot(MultiCivs.Count, netMode)); } if (MultiCivs.Count > targetCount) { TrimRemovablePlayerSlots(targetCount); if (MultiCivs.Count > targetCount) { targetCount = MultiCivs.Count; PlayerCount = (uint)targetCount; LogSystem.LogWarning($"玩家槽位中真人/保留槽位数量超过目标人数,保留现有槽位: playerCount={PlayerCount}"); } } for (int i = 0; i < MultiCivs.Count; i++) { NormalizePlayerSlot(MultiCivs[i], i); } if (netMode == NetMode.Single) ApplySinglePlayerSlotDefaults(); RefreshMultiCivsDict(); } public bool SetPlayerCount(uint playerCount, NetMode netMode = NetMode.Multi) { var oldPlayerCount = PlayerCount; var oldSlotCount = MultiCivs?.Count ?? 0; PlayerCount = playerCount; EnsurePlayerSlots(netMode); return oldPlayerCount != PlayerCount || oldSlotCount != (MultiCivs?.Count ?? 0); } private int GetRequiredPlayerSlotCount(int targetCount, NetMode netMode) { targetCount = Math.Max(0, targetCount); var lobby = LobbyManager.Instance?.Lobby; if (netMode != NetMode.Multi || lobby == null || !lobby.IsInLobby()) return targetCount; var memberIds = lobby.GetAllMemberIds(); return Math.Max(targetCount, memberIds?.Count ?? 0); } private void TrimRemovablePlayerSlots(int targetCount) { for (int i = MultiCivs.Count - 1; i >= 0 && MultiCivs.Count > targetCount; i--) { if (!CanRemovePlayerSlot(MultiCivs[i])) continue; MultiCivs.RemoveAt(i); } } private static bool CanRemovePlayerSlot(MemberCiv slot) { return slot == null || slot.MemberId == 0 && slot.IsAI; } private MemberCiv CreateDefaultPlayerSlot(int index, NetMode netMode) { var slot = new MemberCiv { Index = index, MemberId = 0, PlayerId = 0, CivId = 0, ForceId = 0, TeamId = NoTeamId, IsAI = true, IsReady = false, IsCivFixed = false }; return slot; } private void ApplySinglePlayerSlotDefaults() { if (MultiCivs.Count == 0) return; var selfSlot = MultiCivs[0]; PrepareSinglePlayerSlot(selfSlot, index: 0, isAI: false); selfSlot.IsCivFixed = true; var usedCivs = new HashSet { selfSlot.CivId }; for (int i = 1; i < MultiCivs.Count; i++) { var slot = MultiCivs[i]; PrepareSinglePlayerSlot(slot, i, isAI: true); if (!slot.IsCivFixed || usedCivs.Contains(slot.CivId)) { var civId = PickDefaultCivId(i, usedCivs); slot.CivId = civId; slot.ForceId = civId; slot.IsCivFixed = true; } usedCivs.Add(slot.CivId); } } private static void PrepareSinglePlayerSlot(MemberCiv slot, int index, bool isAI) { slot.Index = index; slot.MemberId = 0; slot.IsReady = false; slot.PlayerId = 0; slot.TeamId = NoTeamId; slot.IsAI = isAI; } private static uint PickDefaultCivId(int preferredIndex, HashSet usedCivs) { const int defaultCivCount = 17; for (int offset = 0; offset < defaultCivCount; offset++) { var civId = (uint)((preferredIndex + offset) % defaultCivCount); if (!usedCivs.Contains(civId)) return civId; } return (uint)preferredIndex; } private void NormalizePlayerSlot(MemberCiv slot, int index) { slot.Index = index; if (slot.TeamId < NoTeamId) slot.TeamId = NoTeamId; if (slot.MemberId != 0) slot.IsAI = false; if (slot.MemberId == 0) slot.PlayerId = 0; } private static void NormalizeSerializedPlayerSlot(MemberCiv slot, int index) { slot.Index = index; if (slot.TeamId < NoTeamId) slot.TeamId = NoTeamId; if (slot.MemberId != 0) slot.IsAI = false; } // 根据房间成员信息更新 mapconfig 信息 public bool UpdateLobbyMember(Dictionary memberInfos) { if (memberInfos == null) return false; EnsurePlayerSlots(NetMode.Multi); var changed = false; // 先解绑已离开 lobby 的成员,避免槽位留下幽灵真人。 foreach (var slot in MultiCivs) { if (slot == null || slot.MemberId == 0 || memberInfos.ContainsKey(slot.MemberId)) continue; ClearPlayerSlotMember(slot, makeAi: true, clearCiv: true); changed = true; } if (changed) EnsurePlayerSlots(NetMode.Multi); RefreshMultiCivsDict(); foreach (var kv in memberInfos) { if (_memberCivs.ContainsKey(kv.Key)) continue; var slot = GetFirstAssignableSlot(); if (slot == null) { LogSystem.LogError($"房间人数超过玩家槽位数量: member={kv.Key}, playerCount={PlayerCount}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMembersNotSynced); continue; } slot.MemberId = kv.Key; slot.PlayerId = 0; slot.IsAI = false; slot.IsReady = LobbyManager.Instance.Lobby.IsInLobby() && kv.Key == LobbyManager.Instance.Lobby.GetLobbyOwnerId(); changed = true; } changed |= EnsureLobbyOwnerReady(); RefreshMultiCivsDict(); return changed; } private MemberCiv GetFirstAssignableSlot() { foreach (var slot in MultiCivs) { if (slot == null || slot.MemberId != 0) continue; return slot; } return null; } private static void ClearPlayerSlotMember(MemberCiv slot, bool makeAi, bool clearCiv = false) { slot.MemberId = 0; slot.PlayerId = 0; slot.IsReady = false; slot.IsAI = makeAi; if (!clearCiv) return; slot.CivId = 0; slot.ForceId = 0; slot.IsCivFixed = false; } private bool EnsureLobbyOwnerReady() { if (!LobbyManager.Instance.Lobby.IsInLobby()) return false; var ownerId = LobbyManager.Instance.Lobby.GetLobbyOwnerId(); if (ownerId == 0) return false; var changed = false; foreach (var memberCiv in MultiCivs) { if (memberCiv == null || memberCiv.MemberId != ownerId || memberCiv.IsReady) continue; memberCiv.IsReady = true; changed = true; } return changed; } // 内部刷新 private void RefreshMultiCivsDict() { _memberCivs ??= new Dictionary(); MultiCivs ??= new List(); _memberCivs.Clear(); foreach (var memberCiv in MultiCivs) { if (memberCiv == null) continue; if (memberCiv.MemberId == 0) continue; _memberCivs[memberCiv.MemberId] = memberCiv; } } public MemberCiv GetMemberCiv(ulong memberId) { MultiCivs ??= new List(); foreach (var memberCiv in MultiCivs) { if (memberCiv == null) continue; if (memberCiv.MemberId == memberId) return memberCiv; } return null; } public MemberCiv GetPlayerSlot(int index, NetMode netMode = NetMode.Multi) { EnsurePlayerSlots(netMode); if (index < 0 || index >= MultiCivs.Count) return null; return MultiCivs[index]; } public bool SetPlayerSlotTeam(int index, int teamId, NetMode netMode = NetMode.Multi) { var slot = GetPlayerSlot(index, netMode); if (slot == null) return false; teamId = Math.Max(NoTeamId, teamId); if (slot.TeamId == teamId) return false; slot.TeamId = teamId; return true; } public bool SetPlayerSlotAI(int index, bool isAI, NetMode netMode = NetMode.Multi) { var slot = GetPlayerSlot(index, netMode); if (slot == null) return false; if (isAI) { if (slot.MemberId != 0) return false; var changed = !slot.IsAI; ClearPlayerSlotMember(slot, makeAi: true); return changed; } if (!slot.IsAI) return false; slot.IsAI = false; return true; } public bool SetPlayerSlotCiv(int index, uint civId, uint forceId, NetMode netMode = NetMode.Multi) { var slot = GetPlayerSlot(index, netMode); if (slot == null) return false; if (slot.CivId == civId && slot.ForceId == forceId && slot.IsCivFixed) return false; slot.CivId = civId; slot.ForceId = forceId; slot.IsCivFixed = true; return true; } public bool SetSinglePlayerSlotCiv(int index, uint civId, uint forceId) { var slot = GetPlayerSlot(index, NetMode.Single); if (slot == null) return false; var isAI = index != 0; var changed = slot.CivId != civId || slot.ForceId != forceId || slot.IsAI != isAI || !slot.IsCivFixed || slot.MemberId != 0; PrepareSinglePlayerSlot(slot, index, isAI); slot.CivId = civId; slot.ForceId = forceId; slot.IsCivFixed = true; RefreshMultiCivsDict(); return changed; } public bool SetSinglePlayerCiv(uint civId, uint forceId) { var changed = SetSinglePlayerSlotCiv(0, civId, forceId); ApplySinglePlayerSlotDefaults(); RefreshMultiCivsDict(); return changed; } public bool SetMemberSlot(ulong memberId, int index) { if (memberId == 0) return false; EnsurePlayerSlots(NetMode.Multi); if (index < 0 || index >= MultiCivs.Count) return false; var current = GetMemberCiv(memberId); var target = MultiCivs[index]; if (target.MemberId != 0 && target.MemberId != memberId) return false; if (current == target) return false; if (current != null) ClearPlayerSlotMember(current, makeAi: true, clearCiv: true); target.MemberId = memberId; target.PlayerId = 0; target.IsAI = false; target.IsReady = false; EnsureLobbyOwnerReady(); RefreshMultiCivsDict(); return true; } public bool TryGetTeamIdByPlayerId(uint playerId, out int teamId) { teamId = NoTeamId; if (playerId == 0 || MultiCivs == null) return false; foreach (var slot in MultiCivs) { if (slot == null || slot.PlayerId != playerId) continue; teamId = slot.TeamId; return teamId != NoTeamId; } return false; } public bool ArePlayersInSameTeam(uint playerIdA, uint playerIdB) { if (playerIdA == 0 || playerIdB == 0 || playerIdA == playerIdB) return false; return TryGetTeamIdByPlayerId(playerIdA, out var teamA) && TryGetTeamIdByPlayerId(playerIdB, out var teamB) && teamA != NoTeamId && teamA == teamB; } public void ApplyTeamDiplomacy(MapData mapData) { if (mapData?.PlayerMap?.PlayerDataList == null) return; EnsureSerializedPlayerSlots(mapData.PlayerMap.PlayerDataList.Count); BindSlotPlayerIdsByIndex(mapData); foreach (var player in mapData.PlayerMap.PlayerDataList) { if (player == null) continue; foreach (var target in mapData.PlayerMap.PlayerDataList) { if (target == null || player.Id == target.Id) continue; if (!ArePlayersInSameTeam(player.Id, target.Id)) continue; if (player.GetCountryDiplomacyInfo(target.Id, out var info)) { ApplyTeammateDiplomacy(info); } } } } private void BindSlotPlayerIdsByIndex(MapData mapData) { if (mapData?.PlayerMap?.PlayerDataList == null || MultiCivs == null) return; for (int i = 0; i < MultiCivs.Count; i++) { if (i >= mapData.PlayerMap.PlayerDataList.Count) break; if (MultiCivs[i] == null) continue; MultiCivs[i].PlayerId = mapData.PlayerMap.PlayerDataList[i].Id; } } private static void ApplyTeammateDiplomacy(CountryDiplomacyInfo info) { if (info == null) return; info.IsTeammate = true; info.DiplomacyState = DiplomacyState.League; info.IsLeagueRequest = false; info.IsLeagueRupture = false; } // 主从端一致的更新某一个成员信息 public bool UpdateMemberCiv(MemberCiv civ) { if (civ == null) return false; var lobby = LobbyManager.Instance.Lobby; if (lobby.IsInLobby() && civ.MemberId == lobby.GetLobbyOwnerId()) civ.IsReady = true; if (LobbyManager.Instance.Lobby.IsInLobby() && !LobbyManager.Instance.Lobby.IsLobbyOwner()) { var selfMemberId = LobbyManager.Instance.Lobby.GetSelfMemberId(); if (civ.MemberId != selfMemberId) { LogSystem.LogError($"客户端只能修改自己的阵营: self={selfMemberId}, target={civ.MemberId}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMemberConfigFailed); return false; } if (!GameNetSender.Instance.ChangeCiv(civ)) { NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMemberConfigFailed); return false; } ApplyMemberCivLocal(civ); return true; } if (LobbyManager.Instance.Lobby.IsInLobby() && !LobbyManager.Instance.Lobby.IsMemberInLobby(civ.MemberId)) { LogSystem.LogError($"不能修改不在房间内的成员阵营: target={civ.MemberId}"); NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyMemberConfigFailed); return false; } return ApplyMemberCivLocal(civ); } public bool UpdateSelfMemberSlot(int index) { if (!LobbyManager.Instance.Lobby.IsInLobby()) return false; var selfMemberId = LobbyManager.Instance.Lobby.GetSelfMemberId(); var memberCiv = GetMemberCiv(selfMemberId); if (memberCiv == null) return false; var targetSlot = GetPlayerSlot(index, NetMode.Multi); if (targetSlot == null) return false; if (targetSlot.MemberId != 0 && targetSlot.MemberId != selfMemberId) return false; var next = CopyMemberCiv(memberCiv); next.Index = index; next.IsReady = false; return UpdateMemberCiv(next); } public bool UpdateSelfMemberTeam(int teamId) { if (!LobbyManager.Instance.Lobby.IsInLobby()) return false; var memberCiv = GetMemberCiv(LobbyManager.Instance.Lobby.GetSelfMemberId()); if (memberCiv == null) return false; var next = CopyMemberCiv(memberCiv); next.TeamId = Math.Max(NoTeamId, teamId); next.IsReady = false; return UpdateMemberCiv(next); } public bool UpdateSelfMemberCiv(uint civId, uint forceId) { if (!LobbyManager.Instance.Lobby.IsInLobby()) return false; var memberCiv = GetMemberCiv(LobbyManager.Instance.Lobby.GetSelfMemberId()); if (memberCiv == null) return false; var next = CopyMemberCiv(memberCiv); next.CivId = civId; next.ForceId = forceId; next.IsCivFixed = true; next.IsReady = false; return UpdateMemberCiv(next); } public bool UpdateSelfMemberConfig(int index, uint civId, uint forceId, int teamId) { if (!LobbyManager.Instance.Lobby.IsInLobby()) return false; var selfMemberId = LobbyManager.Instance.Lobby.GetSelfMemberId(); var memberCiv = GetMemberCiv(selfMemberId); if (memberCiv == null) return false; var targetSlot = GetPlayerSlot(index, NetMode.Multi); if (targetSlot == null) return false; if (targetSlot.MemberId != 0 && targetSlot.MemberId != selfMemberId) return false; var next = CopyMemberCiv(memberCiv); next.Index = index; next.CivId = civId; next.ForceId = forceId; next.TeamId = Math.Max(NoTeamId, teamId); next.IsCivFixed = true; next.IsReady = false; return UpdateMemberCiv(next); } private static MemberCiv CopyMemberCiv(MemberCiv memberCiv) { return new MemberCiv { Index = memberCiv.Index, MemberId = memberCiv.MemberId, PlayerId = memberCiv.PlayerId, CivId = memberCiv.CivId, ForceId = memberCiv.ForceId, TeamId = memberCiv.TeamId, IsAI = memberCiv.IsAI, IsCivFixed = memberCiv.IsCivFixed, IsReady = memberCiv.IsReady }; } public bool UpdateMemberReady(ulong memberId, bool isReady) { var memberCiv = GetMemberCiv(memberId); if (memberCiv == null) return false; var lobby = LobbyManager.Instance.Lobby; var ownerId = lobby.IsInLobby() ? lobby.GetLobbyOwnerId() : 0; var next = new MemberCiv { Index = memberCiv.Index, MemberId = memberCiv.MemberId, PlayerId = memberCiv.PlayerId, CivId = memberCiv.CivId, ForceId = memberCiv.ForceId, TeamId = memberCiv.TeamId, IsAI = memberCiv.IsAI, IsCivFixed = memberCiv.IsCivFixed, IsReady = memberId == ownerId || isReady }; return UpdateMemberCiv(next); } public bool SetSelfReady(bool isReady) { if (!LobbyManager.Instance.Lobby.IsInLobby()) return false; return UpdateMemberReady(LobbyManager.Instance.Lobby.GetSelfMemberId(), isReady); } public bool ToggleSelfReady() { if (!LobbyManager.Instance.Lobby.IsInLobby()) return false; var selfMemberId = LobbyManager.Instance.Lobby.GetSelfMemberId(); return UpdateMemberReady(selfMemberId, !IsMemberReady(selfMemberId)); } public bool IsMemberReady(ulong memberId) { if (LobbyManager.Instance.Lobby.IsInLobby() && memberId == LobbyManager.Instance.Lobby.GetLobbyOwnerId()) { return true; } return GetMemberCiv(memberId)?.IsReady ?? false; } public bool AreAllLobbyMembersReady() { if (!LobbyManager.Instance.Lobby.IsInLobby()) return false; var memberInfos = LobbyManager.Instance.Lobby.GetAllMemberInfo(); if (!HasSameLobbyMembers(memberInfos)) return false; foreach (var slot in MultiCivs) { if (slot == null) return false; if (slot.MemberId == 0 && !slot.IsAI) return false; } var ownerId = LobbyManager.Instance.Lobby.GetLobbyOwnerId(); foreach (var memberId in memberInfos.Keys) { if (memberId == ownerId) continue; if (!IsMemberReady(memberId)) return false; } return true; } public bool ResetGuestReadyStates() { if (!LobbyManager.Instance.Lobby.IsInLobby() || !LobbyManager.Instance.Lobby.IsLobbyOwner()) return false; MultiCivs ??= new List(); var ownerId = LobbyManager.Instance.Lobby.GetLobbyOwnerId(); var changed = false; foreach (var memberCiv in MultiCivs) { if (memberCiv == null) continue; var shouldReady = memberCiv.MemberId == ownerId; if (memberCiv.IsReady == shouldReady) continue; memberCiv.IsReady = shouldReady; changed = true; } return changed; } public bool SetResumeArchiveSelected(bool selected) { if (IsResumeArchiveSelected == selected) return false; IsResumeArchiveSelected = selected; return true; } private bool ApplyMemberCivLocal(MemberCiv civ) { EnsurePlayerSlots(NetMode.Multi); var memberCiv = civ.MemberId != 0 ? GetMemberCiv(civ.MemberId) : null; var targetIndex = civ.Index; var targetSlot = targetIndex >= 0 && targetIndex < MultiCivs.Count ? MultiCivs[targetIndex] : null; if (memberCiv != null && targetSlot != null && memberCiv != targetSlot && targetSlot.MemberId == 0) { ClearPlayerSlotMember(memberCiv, makeAi: true, clearCiv: true); memberCiv = targetSlot; } memberCiv ??= targetSlot; if (memberCiv == null) return false; var nextMemberId = civ.MemberId; var nextIsAI = nextMemberId == 0 && civ.IsAI; if (nextMemberId != 0) nextIsAI = false; if (memberCiv.MemberId == nextMemberId && memberCiv.CivId == civ.CivId && memberCiv.ForceId == civ.ForceId && memberCiv.PlayerId == civ.PlayerId && memberCiv.TeamId == civ.TeamId && memberCiv.IsAI == nextIsAI && memberCiv.IsCivFixed == civ.IsCivFixed && memberCiv.IsReady == civ.IsReady) return false; memberCiv.MemberId = nextMemberId; memberCiv.CivId = civ.CivId; memberCiv.ForceId = civ.ForceId; memberCiv.PlayerId = civ.PlayerId; memberCiv.TeamId = civ.TeamId; memberCiv.IsAI = nextIsAI; memberCiv.IsCivFixed = civ.IsCivFixed; memberCiv.IsReady = civ.IsReady; NormalizePlayerSlot(memberCiv, memberCiv.Index); RefreshMultiCivsDict(); return true; } public bool HasSameLobbyMembers(Dictionary memberInfos) { if (memberInfos == null) return false; EnsurePlayerSlots(NetMode.Multi); var seen = new HashSet(); foreach (var memberCiv in MultiCivs) { if (memberCiv == null) return false; if (memberCiv.MemberId == 0) continue; if (!memberInfos.ContainsKey(memberCiv.MemberId)) return false; if (!seen.Add(memberCiv.MemberId)) return false; } return seen.Count == memberInfos.Count; } // 主从端一致的本地数据检测 public void CheckMapConfigChanged() { if (!LobbyManager.Instance.Lobby.IsInLobby()) return; byte[] bytes = MemoryPackSerializer.Serialize(this); var hash128 = new Hash128(); hash128.Append(bytes); var hash = hash128.ToString(); if (hash == _hash) return; _hash = hash; EventManager.Publish(new UpdateUIOutsideMultiplayRoomSetting()); if (LobbyManager.Instance.Lobby.IsLobbyOwner()) GameNetSender.Instance.UpdateLobbyData(Main.Instance.MapConfig); } public void ClearMultiCivs() { if (MultiCivs != null) { foreach (var slot in MultiCivs) { ClearPlayerSlotMember(slot, makeAi: true, clearCiv: true); slot.TeamId = NoTeamId; } } _memberCivs?.Clear(); } public void ChangeByMapConfig(MapConfig other) { // [LimitsDebug] 追溯 MatchLimits 污染来源 var thisLimitsStr = MatchLimits != null ? string.Join(",", MatchLimits) : "null"; var otherLimitsStr = other?.MatchLimits != null ? string.Join(",", other.MatchLimits) : "null"; UnityEngine.Debug.Log($"[LimitsDebug] ChangeByMapConfig BEFORE: this.MatchLimits=[{thisLimitsStr}] (count={MatchLimits?.Count ?? -1}) other.MatchLimits=[{otherLimitsStr}] (count={other?.MatchLimits?.Count ?? -1}) thisHash={GetHashCode()} otherHash={other?.GetHashCode()}\n{System.Environment.StackTrace}"); if (other == null) { // 没有匹配到关卡配置时,回退为普通随机地图 IsCustomMap = false; MapName = null; return; } GameMode = other.GameMode; IsCustomMap = other.IsCustomMap; MapName = other.MapName; MatchSettlement = other.MatchSettlement; PlayerSettlements = other.PlayerSettlements; } // 刷新 Limits 移除重复 Limit public void RefreshLimits() { if (MatchLimits == null || MatchLimits.Count == 0) return; // 使用 HashSet 去重 var uniqueLimits = new HashSet(MatchLimits); MatchLimits.Clear(); MatchLimits.AddRange(uniqueLimits); } // 判断是否限制 public bool IsLimit(MatchLimitType limitType) { return MatchLimits != null && MatchLimits.Contains(limitType); } } [MemoryPackable] public partial class MemberCiv { public ulong MemberId; public uint PlayerId; public uint CivId; public uint ForceId; public bool IsReady; public int Index; public int TeamId; public bool IsAI; public bool IsCivFixed; } // 一场游戏的地图数据 [MemoryPackable] public partial class MapData { //--------------- 基础核心数据 ---------------// // 地图 ID public uint MapID; // 地图配置数据 public MapConfig MapConfig; // 地块数据 public GridMapData GridMap; // 玩家数据 public PlayerMapData PlayerMap; // 城市数据 public CityMapData CityMap; // 小兵数据 public UnitMapData UnitMap; // 网络数据 public NetData Net; // 结算数据 public MatchSettlementInfo MatchSettlement; [MemoryPackIgnore] public bool DeserializedMissingCriticalData { get; private set; } // 当前玩家 [MemoryPackIgnore] public PlayerData CurPlayer => PlayerMap?.GetPlayerData(Net?.CurPlayerId ?? 0); // 城市 -> 玩家, 将引用关系转化为ID关系 public Dictionary CityToPlayerDict; // 小兵 -> 城市, 将引用关系转化为ID关系 public Dictionary UnitToCityDict; // 小兵 -> 格子, 将引用关系转化为ID关系 public Dictionary UnitToGridDict; // 城市 -> 格子, 将引用关系转化为ID关系 public Dictionary CityToGridDict; //--------------- 核心数据派生的数据 ---------------// // 格子 -> 小兵, 缓存数据 private Dictionary _gridToUnitDict; // 格子 -> 城市, 缓存数据 private Dictionary _gridToCityDict; //--------------- 其他数据 ---------------// // 地图 ID 生成器 [MemoryPackInclude] private MapIdGenerator _idGenerator; [MemoryPackIgnore] public MapIdGenerator IdGenerator => _idGenerator; //-------------- 读写城市方法 -------------------// // 通过格子 gid 找城市 cid public bool GetCityIdByGridId(uint gid, out uint cid) { return _gridToCityDict.TryGetValue(gid, out cid); } // 通过格子 gid 找城市数据 cityData public bool GetCityDataByGid(uint gid, out CityData cityData) { if (_gridToCityDict.TryGetValue(gid, out var cid)) { return CityMap.GetCityById(_gridToCityDict[gid], out cityData); } cityData = null; return false; } // 通过小兵 ID 找城市 cid public bool GetCityIdByUnitId(uint uid, out uint cid) { return UnitToCityDict.TryGetValue(uid, out cid); } // 通过小兵 ID 找城市 data public bool GetCityDataByUnitId(uint uid, out CityData cityData) { if (UnitToCityDict.TryGetValue(uid, out var cid)) { return CityMap.GetCityById(cid, out cityData); } cityData = null; return false; } // 通过领土格子 gid 找城市数据 cityData public bool GetCityDataByTerritoryGid(uint gid, out CityData cityData) { return CityMap.GetCityDataByTerritoryGridId(gid, out cityData); } // 通过玩家 ID 找城市列表 public void GetCityDataListByPlayerId(uint pid, List cityDataList) { foreach (var kv in CityToPlayerDict) { if (kv.Value != pid) continue; if (!CityMap.GetCityById(kv.Key, out var cityData)) continue; cityDataList.Add(cityData); } } public int GetTurnTimeLimitSeconds(uint playerId) { if (MapConfig == null || !MapConfig.IsLimitTime || MapConfig.TimeLimitSeconds <= 0) return 0; var cityCount = 0; foreach (var kv in CityToPlayerDict) { if (kv.Value == playerId) cityCount++; } var unitCount = 0; foreach (var kv in UnitToCityDict) { if (!CityToPlayerDict.TryGetValue(kv.Value, out var cityPlayerId)) continue; if (cityPlayerId == playerId) unitCount++; } return MapConfig.TimeLimitSeconds + unitCount * 2 + cityCount * 5; } // 通过玩家 ID 找城市Set public void GetCityDataListByPlayerId(uint pid, HashSet cityDataList) { foreach (var kv in CityToPlayerDict) { if (kv.Value != pid) continue; if (!CityMap.GetCityById(kv.Key, out var cityData)) continue; cityDataList.Add(cityData); } } // 通过玩家 ID 找城市 Set public HashSet GetCityDataSetByPlayerId(uint pid) { var citySet = new HashSet(); foreach (var kv in CityToPlayerDict) { if (kv.Value != pid) continue; if (!CityMap.GetCityById(kv.Key, out var cityData)) continue; citySet.Add(cityData); } return citySet; } //通过玩家ID 找 领土Set public HashSet GetGridDataSetByPlayerId(uint pid) { var citySet = GetCityDataSetByPlayerId(pid); var gridSet = new HashSet(); foreach (var city in citySet) { var cityGridSet = new HashSet(); city.Territory.GetAllTerritoryArea(cityGridSet); foreach (var gid in cityGridSet) { GridMap.GetGridDataByGid(gid, out var grid); gridSet.Add(grid); } } return gridSet; } //通过玩家ID 找 领土ID Set public HashSet GetGridIdSetByPlayerId(uint pid) { var citySet = GetCityDataSetByPlayerId(pid); var gridSet = new HashSet(); foreach (var city in citySet) { var cityGridSet = new HashSet(); city.Territory.GetAllTerritoryArea(cityGridSet); gridSet.UnionWith(cityGridSet); } return gridSet; } // 通过玩家 ID 查找首都城市 cityData public bool GetCapitalCityDataByPlayerId(uint pid, out CityData cityData) { using var pooledCityList = THCollectionPool.GetListHandle(out var cityList); GetCityDataListByPlayerId(pid, cityList); foreach (var city in cityList) { if (!city.IsCapital) continue; cityData = city; return true; } cityData = null; return false; } // 改变城市所属关系,如果原城市的国家死亡,那么会处理死亡国家的所有单位死亡的视觉逻辑 public bool SetCityIdToPlayerId(uint cid, uint pid) { if (!GetPlayerDataByCityId(cid, out var oldPlayerData)) return false; if(!PlayerMap.GetPlayerDataByPlayerID(pid, out var newPlayerData)) return false; if (!GetCapitalCityDataByPlayerId(oldPlayerData.Id, out var oldPlayerCapitalCityData)) return false; if (!GetCapitalCityDataByPlayerId(pid, out var newPlayerCapitalCityData)) return false; if (!CityMap.GetCityById(cid, out var city)) return false; //playerData.RenderMark = true; //part #1 如果cid的老东家没有别的城市了,判负 if (GetCityCount(oldPlayerData.Id) == 1) { //step #1 玩家死亡(设置mark) oldPlayerData.Alive = false; oldPlayerData.DieMark = true; // Collect 调用 CollectManager.Instance.PlayerGameEndCollect(this, oldPlayerData, newPlayerData); //step #2 所有unit都销毁 using var pooledUnitDataList = THCollectionPool.GetListHandle(out var unitDataList); GetUnitDataListByCityId(cid,unitDataList); foreach (var unitData in unitDataList) unitData.Renderer(this)?.Die(); foreach (var unitData in unitDataList) { Main.UnitLogic.UnitUnnaturalDie(this,unitData); //unitData.Renderer(this) } //step #3 撤销首都 city.IsCapital = false; } //否则要将所有unit转移到老东家的capital下面去 else { //如果cid的老东家的首都就是cid,那么老东家首先要更换首都 if (oldPlayerCapitalCityData.Id == cid) { //遍历cityList,找到第一个老东家的其他城市,让他变成新的首都 foreach (var tmpCityData in CityMap.CityList) { if (tmpCityData.Id == cid) continue; if (GetPlayerIdByCityId(tmpCityData.Id, out var tmpPlayerId) && tmpPlayerId == oldPlayerData.Id) { tmpCityData.IsCapital = true; oldPlayerCapitalCityData.IsCapital = false; oldPlayerCapitalCityData = tmpCityData; break; } } } //开始将所有cid的小兵都转移到oldPlayerCapitalCityData下去 using var pooledTmpUnitDataList = THCollectionPool.GetListHandle(out var tmpUnitDataList); GetUnitDataListByCityId(cid, tmpUnitDataList); foreach(var tmpUnitData in tmpUnitDataList) SetUnitIdToCityId(tmpUnitData.Id,oldPlayerCapitalCityData.Id); oldPlayerCapitalCityData.CityInfoRenderer(this)?.InstantUpdateCityInfo(); } //如果这次这个城市是newPlayer的原始首都,那么new Player要更换首都 if (newPlayerData.CradleCityId == cid) { city.IsCapital = true; newPlayerCapitalCityData.IsCapital = false; } //正式更换城市归属 CityToPlayerDict[cid] = pid; if (CityMap.GetCityById(cid, out var cityData)) { cityData.SetCityRenderer(this); if (GetGridDataByCityId(cid, out var gridData)) gridData.Renderer(this)?.InstantUpdateCityBuilding(cityData.Level,cityData.Civ(this)); } return true; } //改变小兵的位置 public void SetUnitIdToGridId(uint uid, uint gid) { if (UnitToGridDict.TryGetValue(uid, out var oldGid)) { if (_gridToUnitDict.TryGetValue(oldGid, out var oldUid) && oldUid == uid) _gridToUnitDict.Remove(oldGid); } UnitToGridDict[uid] = gid; _gridToUnitDict[gid] = uid; /*if (UnitMap.GetUnitDataByUnitId(uid, out var unitData)) unitData.Renderer(this)?.InstantUpdateUnit();*/ } public void SetUnitDataDie(UnitData unitData) { unitData.SetDie(); // 在清除绑定前获取所属城市id和格子id UnitToCityDict.TryGetValue(unitData.Id, out var cityId); GetGridIdByUnitId(unitData.Id, out var gridId); if (gridId != 0 && _gridToUnitDict.TryGetValue(gridId, out var gridUnitId) && gridUnitId == unitData.Id) _gridToUnitDict.Remove(gridId); // 清除数据层绑定 RemoveUnitData(unitData.Id); // 刷新原来所在格子的视觉(Fire等VFX) if (gridId != 0 && GridMap.GetGridDataByGid(gridId, out var gridData)) gridData.Renderer(this)?.InstantUpdateGrid(); // 刷新所属城市的CityInfo(人口数变化) if (cityId != 0 && CityMap.GetCityById(cityId, out var cityData)) cityData.CityInfoRenderer(this)?.InstantUpdateCityInfo(); // [Fix 2026/05/15] 视野外死亡的 UnitRenderer 孤儿兜底 // 场景:AOE/溅射/远程等路径在视野外击杀单位时,调用方的 Die() 被 InMainSight 守卫跳过, // 数据层已 RemoveUnitData 但 ROUnitMap 中 UnitRenderer 残留 → 后续地块进入视野 // 时由于 GameObject.SetActive 残留为 true 而显示出"尸体"。 // 修复:只对"该格子不在玩家视野"路径兜底,视野内死亡仍走原 Fragment Die 流程,避免打断动画。 if (this == Main.MapData && gridId != 0 && PlayerMap?.SelfPlayerData?.Sight != null && !PlayerMap.SelfPlayerData.Sight.CheckIsInSight(gridId) && MapRenderer.Instance != null && MapRenderer.Instance.ROUnitMap.TryGetValue(unitData.Id, out var orphanRenderer)) { orphanRenderer.Die(); } } //改变小兵到城市的所属关系 public void SetUnitIdToCityId(uint uid, uint cid) { UnitToCityDict[uid] = cid; //if (CityMap.GetCityById(cid, out var cityData)) cityData.RenderMark = true; //if (PlayerMap.GetPlayerDataByPlayerID(pid, out var playerData)) playerData.RenderMark = true; } //获得一个玩家有多少个城市 public int GetCityCount(uint pid) { int ret = 0; foreach (var cityData in CityMap.CityList) if (CityToPlayerDict.ContainsKey(cityData.Id) && CityToPlayerDict[cityData.Id] == pid) ret++; return ret; } //获得一共城市有多少单位 public int GetUnitCount(uint cid) { int ret = 0; foreach (var unit in UnitMap.UnitList) if (UnitToCityDict.ContainsKey(unit.Id) && UnitToCityDict[unit.Id] == cid) ret+= unit.GetPopulation(); return ret; } //-------------- 读写小兵方法 -------------------// // 通过格子 gid 找小兵 uid public bool GetUnitIdByGid(uint gid, out uint uid) { return _gridToUnitDict.TryGetValue(gid, out uid); } //只能给GridData/PlayerData/CityData等调用的核心方法,外部业务不允许调用 public bool GetUnitDataByGid_Core(uint gid, out UnitData unitData) { unitData = null; if (_gridToUnitDict.TryGetValue(gid, out var uid)) { return UnitMap.GetUnitDataByUnitId(uid, out unitData); } return false; } // 通过格子 gid 找小兵 data private bool GetUnitDataByGid(uint gid, out UnitData unitData) { unitData = null; /*foreach(var tt in UnitMap.UnitList) if (GetGridDataByUnitId(tt.Id,out var gridData)) if (gridData.Id == gid) { unitData = tt; return true; } return false;*/ //TODO _gridToUnitDict这个缓存有错,和unitToGrid没有对上。 暂时用了上面的办法,之后需要修改这里的缓存 if (_gridToUnitDict.TryGetValue(gid, out var uid)) { return UnitMap.GetUnitDataByUnitId(uid, out unitData); } return false; } // 通过城市 ID 找小兵列表 public void GetUnitDataListByCityId(uint cid, List unitDataList) { foreach (var kv in UnitToCityDict) { if (kv.Value != cid) continue; if (!UnitMap.GetUnitDataByUnitId(kv.Key, out var unitData)) continue; unitDataList.Add(unitData); } } // 通过玩家 ID 找小兵列表 public void GetUnitDataListByPlayerId(uint pid, List unitDataList) { foreach (var kv in CityToPlayerDict) { if (kv.Value != pid) continue; GetUnitDataListByCityId(kv.Key, unitDataList); } } // 通过城市 ID 找小兵 Set public void GetUnitDataListByCityId(uint cid, HashSet unitDataList) { foreach (var kv in UnitToCityDict) { if (kv.Value != cid) continue; if (!UnitMap.GetUnitDataByUnitId(kv.Key, out var unitData)) continue; unitDataList.Add(unitData); } } // 通过玩家 ID 找小兵 Set public void GetUnitDataListByPlayerId(uint pid, HashSet unitDataList) { foreach (var kv in CityToPlayerDict) { if (kv.Value != pid) continue; GetUnitDataListByCityId(kv.Key, unitDataList); } } // 通过玩家 ID 找敌方小兵列表 public void GetOtherUnitDataListByPlayerId(uint pid, List unitDataList) { foreach (var kv in CityToPlayerDict) { if (kv.Value == pid) continue; GetUnitDataListByCityId(kv.Key, unitDataList); } } // 判断是否是友军 public bool IsLeagueUnitByUnit(uint uidA, uint uidB) { if (!GetPlayerIdByUnitId(uidA, out var pidA)) return false; if (!GetPlayerIdByUnitId(uidB, out var pidB)) return false; return SameUnion(pidA, pidB); } // 判断是否是友军(含刚背盟的盟友) public bool IsLeagueOrJustBreakByUnit(uint uidA, uint uidB) { if (!GetPlayerIdByUnitId(uidA, out var pidA)) return false; if (!GetPlayerIdByUnitId(uidB, out var pidB)) return false; return SameUnionOrJustBreakUnion(pidA, pidB); } // 判断格子是否为友方/刚背盟盟友的城市中心 public bool IsLeagueOrJustBreakCityCenter(uint unitId, GridData grid) { if (grid.Resource != ResourceType.CityCenter) return false; if (!GetCityDataByGid(grid.Id, out var city)) return false; if (!GetPlayerIdByCityId(city.Id, out var cityPid)) return false; if (!GetPlayerIdByUnitId(unitId, out var unitPid)) return false; return SameUnionOrJustBreakUnion(unitPid, cityPid); } // 判断是否是友军 public bool IsLeagueUnitByPlayer(uint playerId, uint uidB) { if (!GetPlayerIdByUnitId(uidB, out var pidB)) return false; return SameUnion(playerId, pidB); } // 判断是否是友军 public bool IsLeagueUnitByCity(uint cityId, uint uidB) { if (!GetPlayerIdByCityId(cityId, out var pidA)) return false; if (!GetPlayerIdByUnitId(uidB, out var pidB)) return false; return SameUnion(pidA, pidB); } public bool GetUnitByGiantType(GiantType giantType, uint unitLevel, out UnitData unitData) { foreach(var t in UnitMap.UnitList) if (t.GiantType == giantType && t.UnitLevel == unitLevel) { unitData = t; return true; } unitData = null; return false; } //-------------- 读写玩家方法 -------------------// // 通过城市找玩家 pid public bool GetPlayerIdByCityId(uint cityId, out uint playerId) { return CityToPlayerDict.TryGetValue(cityId, out playerId); } // 通过城市找玩家 data public bool GetPlayerDataByCityId(uint cityId, out PlayerData playerData) { playerData = null; if (CityToPlayerDict.TryGetValue(cityId, out var playerId)) { playerData = PlayerMap.GetPlayerData(playerId); } return playerData != null; } // 通过小兵 uid 找城市 pid public bool GetPlayerIdByUnitId(uint uid, out uint pid) { if (UnitToCityDict.TryGetValue(uid, out var cid)) { return CityToPlayerDict.TryGetValue(cid, out pid); } pid = 0; return false; } // 通过小兵 uid 找城市数据 playerData public bool GetPlayerDataByUnitId(uint uid, out PlayerData playerData) { playerData = null; if (UnitToCityDict.TryGetValue(uid, out var cid)) { if (CityToPlayerDict.TryGetValue(cid, out var pid)) { playerData = PlayerMap.GetPlayerData(pid); } } return playerData != null; } // 通过领土位置找玩家 public bool GetPlayerDataByTerritoryGridId(uint gid, out PlayerData playerData) { if (CityMap.GetCityDataByTerritoryGridId(gid, out var cityData)) { return GetPlayerDataByCityId(cityData.Id, out playerData); } playerData = null; return false; } //判断playerA和playerB是否同一个联盟 public bool SameUnion(uint pidA, uint pidB) { if (pidA == pidB) return true; if (!PlayerMap.GetPlayerDataByPlayerID(pidA, out var pA)) return false; pA.GetCountryDiplomacyInfo(pidB,out var dipInfo); return dipInfo != null && (dipInfo.IsTeammate || dipInfo.DiplomacyState == DiplomacyState.League); } public bool SameUnionByUnitId(uint uidA, uint uidB) { if (!GetPlayerDataByUnitId(uidA, out var pA)) return false; if (!GetPlayerDataByUnitId(uidB, out var pB)) return false; return SameUnion(pA.Id,pB.Id); } public bool SameUnionOrJustBreakUnion(uint pidA, uint pidB) { if (pidA == pidB) return true; if (!PlayerMap.GetPlayerDataByPlayerID(pidA, out var pA)) return false; pA.GetCountryDiplomacyInfo(pidB,out var dipInfo); return dipInfo != null && (dipInfo.IsTeammate || dipInfo.DiplomacyState is DiplomacyState.League or DiplomacyState.LeagueRupture); } //-------------- 查询方法方法 -------------------// // 通过小兵 uid 找格子 gid public bool GetGridIdByUnitId(uint uid, out uint gid) { return UnitToGridDict.TryGetValue(uid, out gid); } // 通过小兵 uid 找格子数据 data public bool GetGridDataByUnitId(uint uid, out GridData data) { if (UnitToGridDict.TryGetValue(uid, out var gid)) { return GridMap.GetGridDataByGid(gid, out data); } data = null; return false; } // 通过城市 cid 找格子 gid public bool GetGridIdByCityId(uint cid, out uint gid) { return CityToGridDict.TryGetValue(cid, out gid); } //根据曼哈顿距离返回最近的城市 public bool GetNearestCity(UnitData unit,out CityData outCity) { outCity = null; int score = -1; var unitPlayer = unit.Player(this); var unitGrid = unit.Grid(this); if (unitPlayer == null || unitGrid == null) return false; foreach (var city in CityMap.CityList) { var cityPlayer = city.Player(this); var cityGrid = city.Grid(this); if (cityPlayer == null || cityGrid == null || cityPlayer != unitPlayer) continue; if (score == -1 || GridMap.CalcManhattanDistance(cityGrid, unitGrid) < score) { outCity = city; score = GridMap.CalcManhattanDistance(cityGrid, unitGrid); } } return score != -1; } // 通过城市 cid 找格子数据 data public bool GetGridDataByCityId(uint cid, out GridData data) { if (CityToGridDict.TryGetValue(cid, out var gid)) { return GridMap.GetGridDataByGid(gid, out data); } data = null; return false; } //判断当前map是不是Main.MapData public bool IsCurrentShowMap() { return this == Main.MapData; } //------------- 计算地图信息方法 -----------------// public bool CheckIfNearByGridSamePlayer(GridData gridData, int dir) { dir--; int x = gridData.Pos.X + dir % 3 - 1, y = gridData.Pos.Y + dir / 3 - 1; if(x < 0 || x >= MapConfig.Width || y < 0 || y >= MapConfig.Height) return false; if (GridMap.GetGridDataByPos(x, y, out GridData gridData2)) { GetPlayerDataByTerritoryGridId(gridData.Id,out var p1); GetPlayerDataByTerritoryGridId(gridData2.Id,out var p2); return p1 == p2; } return false; } public bool CheckIfNearByGridSameCity(GridData gridData, int dir) { dir--; int x = gridData.Pos.X + dir % 3 - 1, y = gridData.Pos.Y + dir / 3 - 1; if(x < 0 || x >= MapConfig.Width || y < 0 || y >= MapConfig.Height) return false; if (GridMap.GetGridDataByPos(x, y, out GridData gridData2)) { GetCityDataByTerritoryGid(gridData.Id,out var c1); GetCityDataByTerritoryGid(gridData2.Id,out var c2); return c1 == c2; } return false; } //返回在gid周围1格内,dir方向的那格是否是freeland public bool CheckIfNearByGridFreeland(GridData gridData, int dir,out GridData gridData2) { gridData2 = null; dir--; int x = (int)gridData.Pos.X + dir % 3 - 1, y = (int)gridData.Pos.Y + dir / 3 - 1; if(x < 0 || x >= MapConfig.Width || y < 0 || y >= MapConfig.Height) return false; if(GridMap.GetGridDataByPos(x, y, out gridData2)) return !GetPlayerDataByTerritoryGridId(gridData2.Id,out var playerData); return false; } //判断grid的dir方向是不是道路或者海路联通的。注意要区分 陆地港口桥梁 和 港口海域 两种情况,港口是两种情况都出现的 public bool CheckIfNearByGridRoadCanConnenct(GridData gridData, int dir) { int x = gridData.Pos.X + dir % 3 - 1, y = gridData.Pos.Y + dir / 3 - 1; if(x < 0 || x >= MapConfig.Width || y < 0 || y >= MapConfig.Height) return false; if (!GridMap.GetGridDataByPos(x, y, out var gridData2)) return false; //如果有一个没有road return if (gridData.Feature != TerrainFeature.Road || gridData2.Feature != TerrainFeature.Road) return false; //判断主权情况 均有主权且不是一个同盟要return false if (GetPlayerDataByTerritoryGridId(gridData.Id, out var playerData1) && GetPlayerDataByTerritoryGridId(gridData2.Id, out var playerData2) && !SameUnion(playerData1.Id, playerData2.Id)) return false; //排除一个是非港口海域,一个是陆地的情况 if (gridData.Terrain != TerrainType.Land && gridData.Resource != ResourceType.Port && gridData2.Terrain == TerrainType.Land) { //bridge的情况特判 if (gridData.Resource == ResourceType.Bridge) return true; return false; } if (gridData2.Terrain != TerrainType.Land && gridData2.Resource != ResourceType.Port && gridData.Terrain == TerrainType.Land) { //bridge的情况特判 if (gridData2.Resource == ResourceType.Bridge) return true; return false; } //剩下情况都是true return true; } //返回在gid周围1个内,dir方向的那格是不是land public bool CheckIfNearByGridIsLand(GridData gridData, int dir,out GridData gridData2) { gridData2 = null; dir--; int x = (int)gridData.Pos.X + dir % 3 - 1, y = (int)gridData.Pos.Y + dir / 3 - 1; if(x < 0 || x >= MapConfig.Width || y < 0 || y >= MapConfig.Height) return false; GridMap.GetGridDataByPos(x, y, out gridData2); return gridData2.Terrain == TerrainType.Land; } public List GetNearby1RadiusFreelandGidList(uint gid) { var ret = new List(); GridMap.GetGridDataByGid(gid,out GridData gridData); //遍历周围一圈方向,将所有freeland加入列表 for (int dir = 1; dir <= 9; dir++) if(CheckIfNearByGridFreeland(gridData, dir, out var gridData2)) ret.Add(gridData2.Id); return ret; } //返回在gridData上是否存在建设桥梁的基础地理条件 public bool HasBridgeSideLand(GridData gridData,out bool isMirror) { isMirror = false; if (gridData.Terrain == TerrainType.Land) return false; GridData t; if (CheckIfNearByGridIsLand(gridData, 2, out t) && CheckIfNearByGridIsLand(gridData, 8, out t)) return true; isMirror = true; if (CheckIfNearByGridIsLand(gridData, 4, out t) && CheckIfNearByGridIsLand(gridData, 6, out t)) return true; return false; } // 获取玩家的所有领土格子 ID - Buffer版本 private readonly List _territoryTmpCityList = new List(); public void GetPlayerTerritoryGridIdSet(uint pid, HashSet gridSet) { _territoryTmpCityList.Clear(); GetCityDataListByPlayerId(pid, _territoryTmpCityList); foreach (var cityData in _territoryTmpCityList) { cityData.Territory.GetAllTerritoryArea(gridSet); } } // 获取玩家的所有领土格子 ID - 兼容版本(会分配新集合) public HashSet GetPlayerTerritoryGridIdSet(uint pid) { var gridSet = new HashSet(); GetPlayerTerritoryGridIdSet(pid, gridSet); return gridSet; } public bool CheckIfCityFullPopulation(CityData cityData) { if (cityData == null) { //LogSystem.LogError($"CheckIfCityFullPopulation cityData is null"); return true; } return GetUnitCount(cityData.Id) >= cityData.Level; } public uint GetMapSize() { return MapConfig.Width * MapConfig.Height; } //-------------- 新建数据的方法 -------------------// // 新建城市数据 public CityData AddCityData(uint gid, uint pid) { var player = PlayerMap.GetPlayerData(pid); uint civId = player.PlayerCivId; CityLibrary nameEnum = player.PlayerNamerData.GetSetNextCity(); CityData cityData = CityMap.AddCityData(GetNearby1RadiusFreelandGidList(gid), nameEnum, _idGenerator); CityToPlayerDict[cityData.Id] = pid; CityToGridDict[cityData.Id] = gid; _gridToCityDict[gid] = cityData.Id; //建立city会在改格子上自动建立road if (GridMap.GetGridDataByGid(gid, out var gridData)) gridData.Feature = TerrainFeature.Road; //处理rendermark var gridIdSet = GetPlayerTerritoryGridIdSet(pid); foreach(var gridId in gridIdSet) if (GridMap.GetGridDataByGid(gridId, out var gridDataTerr)) gridDataTerr.Renderer(this)?.InstantUpdateGrid(true); CityMap.CityMapRenderMark = true; cityData.SetCityRenderer(this); return cityData; } //TODO 这里要研究下,怎么迭代动画系统 让创生的动画和数据分离 // 新建小兵数据 public bool AddUnitData(uint gid, uint cid, UnitFullType unitFullType,out UnitData newUnit,float waitTime = 0f) { //Step #1 先检查是否能生成(比如在水上生成landOnly的单位就不行) newUnit = null; if (!GridMap.GetGridDataByGid(gid, out GridData grid)) return false; if(!CheckLandTypeForGrid(unitFullType, grid)) return false; if (grid.RealUnit(this, out var existingUnit)) { LogSystem.LogError( $"AddUnitData blocked: target grid occupied. gid={gid}, cid={cid}, newUnit={unitFullType.UnitType}/{unitFullType.GiantType}/{unitFullType.UnitLevel}, existingUnitId={existingUnit?.Id}, existingUnit={existingUnit?.UnitType}/{existingUnit?.GiantType}/{existingUnit?.UnitLevel}, mapId={MapID}"); return false; } //Step #2 再生成单位 newUnit = UnitMap.AddUnitData(unitFullType, _idGenerator); UnitToCityDict[newUnit.Id] = cid; UnitToGridDict[newUnit.Id] = gid; _gridToUnitDict[gid] = newUnit.Id; //Step #3 增加技能 AddUnitSkill(newUnit); foreach (var skill in newUnit.Skills.ToArray()) skill.OnSelfCreated(this, newUnit); OnAnyUnitCreate(this, newUnit); // collect 调用 CollectManager.Instance.OnAddUnitCollect(this, newUnit); if (waitTime > 0f) { var tmpUnit = newUnit; Timer.Instance.TimerRegister(this, () => { //新增renderer MapRenderer.Instance.RenderUpdateUnitMap(); //看情况是否显示,是否播放雾效 if(tmpUnit.Renderer(this)?.InstantUpdateUnit(true)??false) tmpUnit.Grid(Main.MapData)?.Renderer(Main.MapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog)); //刷新所属城市的CityInfo(人口数变化) if (CityMap.GetCityById(cid, out var cityData)) cityData.CityInfoRenderer(this)?.InstantUpdateCityInfo(); },waitTime,"REISEN ILLUSION ADDUNITDATA"); } else { //新增renderer MapRenderer.Instance.RenderUpdateUnitMap(); //看情况是否显示,是否播放雾效 if(newUnit.Renderer(this)?.InstantUpdateUnit(true)??false) newUnit.Grid(Main.MapData)?.Renderer(Main.MapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog)); //刷新所属城市的CityInfo(人口数变化) if (CityMap.GetCityById(cid, out var cityData)) cityData.CityInfoRenderer(this)?.InstantUpdateCityInfo(); } if (GetPlayerDataByUnitId(newUnit.Id, out var playerData)) { foreach (var kv in playerData.PlayerHeroData.HeroTaskDict) kv.Value.OnTrainUnit(this, playerData, newUnit); foreach (var item in playerData.MomentData.Items) item.OnTrainUnit(this, playerData, newUnit); } return true; } // public bool CheckLandTypeForGrid(UnitFullType unitFullType, GridData grid) { if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitFullType, out var info)) return false; if (grid.Terrain is TerrainType.Land) return info.LandType is LandType.Fly or LandType.LandAndWater or LandType.LandAndPort or LandType.LandOnly or LandType.WaterAndAshore; if (grid.Terrain is TerrainType.ShallowSea or TerrainType.DeepSea) { if (grid.Resource == ResourceType.Bridge) { return true; } if (grid.Resource == ResourceType.Port) { return info.LandType is LandType.Fly or LandType.WaterAndAshore or LandType.WaterOnly or LandType.LandAndWater or LandType.LandAndPort; } return info.LandType is LandType.Fly or LandType.WaterAndAshore or LandType.WaterOnly or LandType.LandAndWater; } return true; } //给Unit增加skill ,通用类, public void AddUnitSkill(UnitData unit) { if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType, unit.UnitLevel,out var unitInfo)) return; if (!GetPlayerDataByUnitId(unit.Id, out var player)) return; //var common = new List() { SkillType.WATERDEFENSE ,SkillType.WATERMOVE,SkillType.OCEANDEFENSE,SkillType.OCEANMOVE,SkillType.MOUNTAINDEFENSE,SkillType.MOUNTAINMOVE,SkillType.FORESTDEFENSE}; //Step #1先添加小兵在配表中自带的技能 foreach (var skill in unitInfo.Skills) { unit.AddInitSkill(skill, this); if (unit.GetSkill(skill, out var addedSkill)) addedSkill.OnSkillAdd(this, unit.Id); } //Step #2再添加小兵在配表中自带的技能 var t = new CommonActionId(); t.ActionType = CommonActionType.UnitSkill; //遍历所有techAtom foreach (var atom in player.TechTree.TechAtomCacheSet) { if(!Table.Instance.TechDataAssets.GetTechAtomInfo(atom,out var info))continue; if (!info.IsAddSkill) continue; if (!info.CheckCondition(unit.UnitFullType)) continue; unit.AddInitSkill(info.AddSkillType, this); if (unit.GetSkill(info.AddSkillType, out var addedSkill)) addedSkill.OnSkillAdd(this, unit.Id); } } // 移除小兵数据 public void RemoveUnitData(uint uid) { UnitMap.RemoveUnitData(uid); if (UnitToGridDict.Remove(uid, out var gid)) { if (_gridToUnitDict.TryGetValue(gid, out var gridUnitId) && gridUnitId == uid) _gridToUnitDict.Remove(gid); } UnitToCityDict.Remove(uid); } // 获取所有 ID 单位 public List GetAllIdentifierBase() { var list = new List(); foreach (var idBase in PlayerMap.PlayerDataList) list.Add(idBase); foreach (var idBase in CityMap.CityList) list.Add(idBase); foreach (var idBase in UnitMap.UnitList) list.Add(idBase); foreach (var idBase in GridMap.GridList) list.Add(idBase); return list; } // 获取 ID 单位 public IdentifierBase GetIdentifierBase(uint id) { if (PlayerMap.GetPlayerDataByPlayerID(id, out var player)) return player; if (CityMap.GetCityById(id, out var city)) return city; if (UnitMap.GetUnitDataByUnitId(id, out var unit)) return unit; if (GridMap.GetGridDataByGid(id, out var grid)) return grid; return null; } //-------------- 生命周期方法 -------------------// // 用于网络传输的默认反序列化构造 [MemoryPackConstructor] public MapData() { CityToPlayerDict = new Dictionary(); UnitToCityDict = new Dictionary(); UnitToGridDict = new Dictionary(); CityToGridDict = new Dictionary(); _gridToCityDict = new Dictionary(); _gridToUnitDict = new Dictionary(); _idGenerator = new MapIdGenerator(); } // 从地图配置初始化 public MapData(MapConfig mapCfg, NetMode netMode) { MapID = (uint)Guid.NewGuid().GetHashCode(); MapConfig = mapCfg.CreateRuntimeCopy(); _idGenerator = new MapIdGenerator(); GridMap = new GridMapData(MapConfig, _idGenerator); PlayerMap = new PlayerMapData(this, _idGenerator, netMode); CityMap = new CityMapData(); UnitMap = new UnitMapData(); Net = new NetData(); MatchSettlement = new MatchSettlementInfo(); CityToPlayerDict = new Dictionary(); UnitToCityDict = new Dictionary(); UnitToGridDict = new Dictionary(); CityToGridDict = new Dictionary(); _gridToCityDict = new Dictionary(); _gridToUnitDict = new Dictionary(); MatchSettlement.Init(this, MapConfig); } public MapData(MapData copyMap) { MapID = copyMap.MapID; MapConfig = copyMap.MapConfig; _idGenerator = copyMap._idGenerator.DeepCopy(); GridMap = new GridMapData(copyMap.GridMap); PlayerMap = new PlayerMapData(copyMap.PlayerMap); CityMap = new CityMapData(copyMap.CityMap); UnitMap = new UnitMapData(copyMap.UnitMap); Net = new NetData(copyMap.Net); MatchSettlement = new MatchSettlementInfo(copyMap.MatchSettlement); CityToPlayerDict = new Dictionary(); UnitToCityDict = new Dictionary(); UnitToGridDict = new Dictionary(); CityToGridDict = new Dictionary(); _gridToCityDict = new Dictionary(); _gridToUnitDict = new Dictionary(); foreach (var kv in copyMap.CityToPlayerDict) CityToPlayerDict[kv.Key] = kv.Value; foreach (var kv in copyMap.UnitToCityDict) UnitToCityDict[kv.Key] = kv.Value; foreach (var kv in copyMap.UnitToGridDict) UnitToGridDict[kv.Key] = kv.Value; foreach (var kv in copyMap.CityToGridDict) CityToGridDict[kv.Key] = kv.Value; foreach (var kv in copyMap._gridToCityDict) _gridToCityDict[kv.Key] = kv.Value; foreach (var kv in copyMap._gridToUnitDict) _gridToUnitDict[kv.Key] = kv.Value; } // 获取深拷贝的一份新的地图数据 public MapData GetDeepCopyMapData() { return new MapData(this); } // 深度拷贝另一封 map 数据 public void DeepCopy(MapData copyMap) { MapID = copyMap.MapID; MapConfig = copyMap.MapConfig; _idGenerator.DeepCopy(copyMap._idGenerator); GridMap.DeepCopy(copyMap.GridMap); PlayerMap.DeepCopy(copyMap.PlayerMap); CityMap.DeepCopy(copyMap.CityMap); UnitMap.DeepCopy(copyMap.UnitMap); Net.DeepCopy(copyMap.Net); MatchSettlement.DeepCopy(copyMap.MatchSettlement); CityToPlayerDict.Clear(); UnitToCityDict.Clear(); UnitToGridDict.Clear(); CityToGridDict.Clear(); _gridToCityDict.Clear(); _gridToUnitDict.Clear(); foreach (var kv in copyMap.CityToPlayerDict) CityToPlayerDict[kv.Key] = kv.Value; foreach (var kv in copyMap.UnitToCityDict) UnitToCityDict[kv.Key] = kv.Value; foreach (var kv in copyMap.UnitToGridDict) UnitToGridDict[kv.Key] = kv.Value; foreach (var kv in copyMap.CityToGridDict) CityToGridDict[kv.Key] = kv.Value; foreach (var kv in copyMap._gridToCityDict) _gridToCityDict[kv.Key] = kv.Value; foreach (var kv in copyMap._gridToUnitDict) _gridToUnitDict[kv.Key] = kv.Value; } // MemoryPack 反序列化之后的后处理 [MemoryPackOnDeserialized] public void OnAfterMemoryPackDeserialize() { // 重建缓存字典 DeserializedMissingCriticalData = MapConfig == null || GridMap == null || PlayerMap == null || CityMap == null || UnitMap == null || Net == null; Net ??= new NetData(); Net.Players ??= new Dictionary(); Net.Actions ??= new List(); if (MapConfig != null) MapConfig.MatchSettlement = MatchSettlementInfo.NormalizeType(MapConfig.MatchSettlement); if (MatchSettlement != null) MatchSettlement.SettlementType = MatchSettlementInfo.NormalizeType(MatchSettlement.SettlementType); CityToPlayerDict ??= new Dictionary(); UnitToCityDict ??= new Dictionary(); UnitToGridDict ??= new Dictionary(); CityToGridDict ??= new Dictionary(); _gridToCityDict ??= new Dictionary(); _gridToUnitDict ??= new Dictionary(); _gridToCityDict.Clear(); _gridToUnitDict.Clear(); // 从主字典重建反向索引 foreach (var kv in CityToGridDict) _gridToCityDict[kv.Value] = kv.Key; foreach (var kv in UnitToGridDict) _gridToUnitDict[kv.Value] = kv.Key; // 重绑定 GridMap?.BindMapConfig(MapConfig); // 刷新 foreach (var action in Net.Actions) { if (action?.Param == null) continue; action.Param.MapData = this; action.Param.RefreshParams(); } } // 当场上有小兵受伤前 public void BeforeUnitDamaged(SettlementInfo info) { using var unitHandle = THCollectionPool.GetListHandle(out var copyUnits); copyUnits.AddRange(UnitMap.UnitList); foreach (var unit in copyUnits) { //避免在遍历的时候,直接改到了skillsList using var skillHandle = THCollectionPool.GetListHandle(out var copy); copy.AddRange(unit.Skills); foreach (var skill in copy) skill.BeforeUnitDamaged(unit, this, info); } } // 当场上有小兵受伤时 public void OnUnitDamaged(SettlementInfo info) { using var unitHandle = THCollectionPool.GetListHandle(out var copyUnits); copyUnits.AddRange(UnitMap.UnitList); foreach (var unit in copyUnits) { //避免在遍历的时候,直接改到了skillsList using var skillHandle = THCollectionPool.GetListHandle(out var copy); copy.AddRange(unit.Skills); foreach (var skill in copy) skill.OnUnitDamaged(unit, this, info); } } // 当场上有小兵移动时 public void OnAnyUnitMove(MapData map, UnitData moveUnit, GridData target, MoveType moveType) { using var unitHandle = THCollectionPool.GetListHandle(out var copyUnits); copyUnits.AddRange(UnitMap.UnitList); foreach (var unit in copyUnits) { using var skillHandle = THCollectionPool.GetListHandle(out var copy); copy.AddRange(unit.Skills); foreach (var skill in copy) skill.OnAnyUnitMove(map, unit, moveUnit, target, moveType); } if (target?.Skills == null) return; using var gridSkillHandle = THCollectionPool.GetListHandle(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) { using var unitHandle = THCollectionPool.GetListHandle(out var unitListCopy); unitListCopy.AddRange(UnitMap.UnitList); foreach (var unit in unitListCopy) { using var skillHandle = THCollectionPool.GetListHandle(out var copy); copy.AddRange(unit.Skills); foreach (var skill in copy) skill.OnActionExecuted(logic, param, unit); } } // 当场上有小兵死亡时 public void OnAnyUnitDie(MapData map, UnitData dieUnit) { using var unitHandle = THCollectionPool.GetListHandle(out var unitListCopy); unitListCopy.AddRange(UnitMap.UnitList); foreach (var unit in unitListCopy) { //避免在遍历的时候,直接改到了skillsList using var skillHandle = THCollectionPool.GetListHandle(out var copy); copy.AddRange(unit.Skills); foreach (var skill in copy) skill.OnAnyUnitDie(map, unit, dieUnit); } } // 当场上有小兵创建时 public void OnAnyUnitCreate(MapData map, UnitData newUnit) { using var unitHandle = THCollectionPool.GetListHandle(out var unitListCopy); unitListCopy.AddRange(UnitMap.UnitList); foreach (var unit in unitListCopy) { //避免在遍历的时候,直接改到了skillsList using var skillHandle = THCollectionPool.GetListHandle(out var copy); copy.AddRange(unit.Skills); foreach (var skill in copy) skill.OnAnyUnitCreate(map, unit, newUnit); } } public static bool SaveMatchConfig(MapConfig config) { if (config == null) return false; // 改为二进制文件扩展名 string path = Application.persistentDataPath + "/../Config/match_config.dat"; int retryCount = 3; while (retryCount > 0) { try { byte[] bytes = MemoryPackSerializer.Serialize(config); if (FileTools.SafeWriteFile(path, bytes)) return true; retryCount--; if (retryCount <= 0) { LogSystem.LogError($"保存地图配置数据失败: 安全写入失败"); } } catch (Exception ex) { retryCount--; if (retryCount <= 0) { LogSystem.LogError($"保存地图配置数据失败: {ex.Message}"); } } } 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) { try { byte[] bytes = File.ReadAllBytes(path); var config = MemoryPackSerializer.Deserialize(bytes); config.ClearMultiCivs(); return config; } catch (IOException ex) { retryCount--; if (retryCount <= 0) { LogSystem.LogError($"读取地图数据失败: {ex.Message}"); return null; } } catch (MemoryPackSerializationException ex) { LogSystem.LogError($"地图数据反序列化失败,可能是版本不兼容: {ex.Message}"); return null; } catch (Exception ex) { LogSystem.LogError($"读取地图数据时发生未知错误: {ex.Message}"); return null; } } return null; } // 旧版按 MapID 扫描/读写散落存档文件的流程已经移除。 // 新版所有 begin / quick_continue / continue / end 都通过 GameArchiveManager 管理。 private static byte[] SerializeMapArchive(MapData map) { var rawBytes = MemoryPackSerializer.Serialize(map); return NetworkPayloadCodec.Encode(rawBytes); } private static MapData DeserializeMapArchive(byte[] bytes) { var rawBytes = NetworkPayloadCodec.DecodeIfNeeded(bytes); return MemoryPackSerializer.Deserialize(rawBytes); } // 给新版 GameArchiveManager 使用的 MapData 存档序列化入口。 // 新系统仍复用旧系统的 MemoryPack + NetworkPayloadCodec 格式,避免两套存档格式分裂。 public static byte[] SerializeArchiveBytes(MapData map) { return SerializeMapArchive(map); } // 给新版 GameArchiveManager 使用的 MapData 存档反序列化入口。 // 外部读新 begin/continue/end 文件时统一走这里,保证压缩/兼容解码逻辑一致。 public static MapData DeserializeArchiveBytes(byte[] bytes) { return DeserializeMapArchive(bytes); } public GameRecord ExportGameRecord() { var gameRecord = new GameRecord(); gameRecord.Mode = MapConfig.GameMode; gameRecord.AIDiff = MapConfig.AIDiff; gameRecord.Turn = PlayerMap.SelfPlayerData.Turn; // 警告:存档以 (PlayerCivId, PlayerForceId) 为玩家身份。多人允许相同 Empire 后, // 同 Empire 多个玩家在 RegenerateMap 按 (Civ,Force) 匹配时只会命中第一个 PlayerData,存档恢复存在风险,待后续以 PlayerData.Id 替代。 gameRecord.PlayerCivId = PlayerMap.SelfPlayerData.PlayerCivId; gameRecord.PlayerForceId = PlayerMap.SelfPlayerData.PlayerForceId; gameRecord.Score = PlayerMap.SelfPlayerData.PlayerScore; DateTime now = DateTime.Now; gameRecord.Time = now.ToString("yyyy.MM.dd HH:mm"); gameRecord.CityCount = (uint)GetCityCount(PlayerMap.SelfPlayerId); gameRecord.MapWidth = MapConfig.Width; gameRecord.MapHeight = MapConfig.Height; gameRecord.PlayerCount = MapConfig.PlayerCount; gameRecord.MatchSettlement = MapConfig.MatchSettlement; gameRecord.MapID = MapID; gameRecord.NetMode = Net?.Mode ?? NetMode.None; var versionInfo = ConfigManager.Instance.VersionCfg?.CurVersionInfo; gameRecord.GameVersion = versionInfo?.FullVersion ?? Application.version; gameRecord.GameVersionId = versionInfo?.VersionId ?? 0; return gameRecord; } #region --- 判断数据间关系的快捷方法 --- //判断gid是否属于pid public bool CheckIfGidBelongPid(uint gid,uint pid) { if(!GetPlayerDataByTerritoryGridId(gid, out var playerData)) return false; if (playerData.Id != pid) return false; return true; } //判断gid是否属于pid的联盟 public bool CheckIfGidBelongPidUnion(uint gid,uint pid) { if(!GetPlayerDataByTerritoryGridId(gid, out var playerData)) return false; return SameUnion(playerData.Id,pid); } public bool CheckCityIdBelongPlayerId(uint cid,uint pid) { return GetPlayerIdByCityId(cid, out var pid1) && pid1 == pid; } public bool CheckUnitIdBelongPlayerId(uint uid,uint pid) { return GetPlayerIdByUnitId(uid, out var pid1) && pid1 == pid; } //判断一个unit是否属于pid及其盟友 public bool CheckUnitIdBelongPlayerIdUnion(uint uid,uint pid) { return GetPlayerIdByUnitId(uid, out var pid1) && SameUnion(pid1,pid); } //判断一个grid是否属于pid及其盟友 public bool CheckGridIdBelongPlayerIdUnion(uint gid,uint pid) { //TODO #外交系统更新 return GetPlayerDataByTerritoryGridId(gid, out var player) && player.Id == pid; } public bool CheckUnitIdBelongCityId(uint uid,uint cid) { return GetCityIdByUnitId(uid, out var cid1) && cid1 == cid; } public bool CheckCityIdBelongSelfPlayer(uint cid) { return GetPlayerIdByCityId(cid, out var pid1) && pid1 == PlayerMap.SelfPlayerData.Id; } public bool CheckUnitIdBelongSelfPlayer(uint uid) { return GetPlayerIdByUnitId(uid, out var pid1) && pid1 == PlayerMap.SelfPlayerData.Id; } #endregion // 更新回合 public void RefreshTurn() { // 联机状态下只有房主有权限更新 if (Main.MapData.Net.Mode == NetMode.Multi && !LobbyManager.Instance.Lobby.IsLobbyOwner()) return; if (PlayerMap.PlayerDataList.Count == 0) { LogSystem.LogError($"UpdateNextPlayer Error : PlayerDataList Count = 0!!!"); return; } // 检查下超时 var turnTimeLimitSeconds = GetTurnTimeLimitSeconds(Net.CurPlayerId); if (Main.MapData.Net.Mode == NetMode.Multi && turnTimeLimitSeconds > 0 && Net.CurPlayerId != 0 && Time.time - Net.PlayerStartTime >= turnTimeLimitSeconds) Main.PlayerLogic.EndPlayerTurn(Main.MapData, Net.CurPlayerId); if (Net.CurPlayerId != 0) return; // 获取下一位 var nextPlayer = GetNextPlayer(); if (nextPlayer == null) { LogSystem.LogError($"UpdateNextPlayer Error : nextPlayer is null!!!"); return; } if (Main.MapData.Net.Mode == NetMode.Spectator) { Main.PlayerLogic.StartPlayerTurn(this, nextPlayer.Id); return; } // 新版快速存档。 // 这里已经过了“只允许房主更新联机回合”的判断,并且 Net.CurPlayerId == 0, // 所以这是每轮轮转到下一位玩家前的统一自动保存点。 // 新版快速存档:每次回合轮转覆盖 quick_continue/quick.dat,并更新唯一 Quick record。 // 不扫描本地所有存档,只维护当前这一局的快速继续入口。 GameArchiveManager.Instance.SaveQuickContinueRecord(Main.MapData); AchievementDataManager.Instance.SaveAchievementData(); // 设置当前玩家 Main.PlayerLogic.StartPlayerTurn(this, nextPlayer.Id); } // 获取下一位执行玩家 public PlayerData GetNextPlayer() { if (PlayerMap.PlayerDataList.Count == 0) { LogSystem.LogError($"UpdateNextPlayer Error : PlayerDataList Count = 0!!!"); return null; } var curTurn = uint.MaxValue; foreach (var player in PlayerMap.PlayerDataList) { if (!player.IsSurvival) continue; if (player.Turn >= curTurn) continue; curTurn = player.Turn; } foreach (var player in PlayerMap.PlayerDataList) { // 如果要做复活逻辑,需要处理复活玩家的 Turn if (!player.IsSurvival) continue; if (player.Turn > curTurn) continue; return player; } foreach (var player in PlayerMap.PlayerDataList) { if (!player.IsSurvival) continue; return player; } LogSystem.LogError($"UpdateNextPlayer Error : PlayerDataList Alive Count = 0!!!"); return PlayerMap.PlayerDataList[0]; } // 回合开始 public void OnTurnStart(uint playerId) { if (playerId == Net.CurPlayerId) return; if (!PlayerMap.GetPlayerDataByPlayerID(playerId, out var player)) return; Net.CurPlayerId = playerId; PlayerMap.OnTurnStart(this, player); CityMap.OnTurnStart(this, player); UnitMap.OnTurnStart(this, player); GridMap.OnTurnStart(this); Net.PlayerStartTime = Time.time; } public void OnAfterTurnStart(uint playerId) { if (!PlayerMap.GetPlayerDataByPlayerID(playerId, out var player)) return; UnitMap.OnAfterTurnStart(this, player); } // 回合结束 public void OnTurnEnd(uint playerId) { if (playerId != Net.CurPlayerId) return; if (!PlayerMap.GetPlayerDataByPlayerID(playerId, out var player)) return; PlayerMap.OnTurnEnd(this, player); CityMap.OnTurnEnd(this, player); UnitMap.OnTurnEnd(this, player); GridMap.OnTurnEnd(this); Net.CurPlayerId = 0; } // 游戏结束时获取胜利玩家 ID public PlayerData GetWinPlayer() { foreach (var player in PlayerMap.PlayerDataList) { if (MatchSettlement.IsWin(player.Id)) return player; } return null; } // 游戏是否结束 public bool CheckIfGameEnd(out bool isWin) { isWin = IsPlayerOrTeammateWin(PlayerMap.SelfPlayerId); return MatchSettlement.IsFinished; } public bool IsPlayerOrTeammateWin(uint playerId) { if (MatchSettlement == null) return false; if (MatchSettlement.IsWin(playerId)) return true; if (MapConfig == null || PlayerMap?.PlayerDataList == null) return false; foreach (var player in PlayerMap.PlayerDataList) { if (player == null || player.Id == playerId) continue; if (!MapConfig.ArePlayersInSameTeam(playerId, player.Id)) continue; if (MatchSettlement.IsWin(player.Id)) return true; } return false; } // // 游戏是否结束 // public bool CheckIfGameEnd(out bool isWin) // { // #if GAME_AUTO_DEBUG // if (CurPlayer != null && CurPlayer.Turn > 40) // { // uint maxId = 0; // int maxScore = 0; // foreach (var player in PlayerMap.PlayerDataList) // { // if (player.PlayerScore <= maxScore) continue; // maxScore = player.PlayerScore; // maxId = player.Id; // } // isWin = maxId == PlayerMap.SelfPlayerId; // return true; // } // #endif // // if (Net.Mode == NetMode.Multi) return CheckIfGameEndMulti(out isWin); // // var aliveCount = 0; // foreach (var player in PlayerMap.PlayerDataList) // { // if (player.Alive) aliveCount++; // } // // isWin = aliveCount <= 1 && PlayerMap.SelfPlayerData.Alive; // // #if GAME_AUTO_DEBUG // var isLose = aliveCount <= 1 && !PlayerMap.SelfPlayerData.Alive; // #else // var isLose = !PlayerMap.SelfPlayerData.Alive; // #endif // // return isWin || isLose; // } // // public bool CheckIfGameEndMulti(out bool isWin) // { // var aliveCount = 0; // foreach (var player in PlayerMap.PlayerDataList) // { // if (player.Alive) aliveCount++; // } // // var alivePlayerCount = 0; // foreach (var kv in Net.Players) // { // if (kv.Key == 0 || kv.Value == 0) continue; // if(!PlayerMap.GetPlayerDataByPlayerID(kv.Value, out var player)) continue; // if (player.Alive) alivePlayerCount++; // } // // isWin = aliveCount <= 1 && PlayerMap.SelfPlayerData.Alive; // return alivePlayerCount == 0 || aliveCount <= 1; // } public bool CheckIsRealPlayer(uint playerId) { if (Net.Mode == NetMode.Multi) { foreach (var kv in Net.Players) { if (kv.Value == playerId) return true; } return false; } return playerId == PlayerMap.SelfPlayerId; } public bool CheckIsAI(uint playerId) { if (Net.Mode == NetMode.Multi) { foreach (var kv in Net.Players) { if (kv.Value == playerId) return false; } return true; } return playerId != PlayerMap.SelfPlayerId; } // 用于使用指定地图的地图相关内容重生成 public void RegenerateMap(MapRecordData recordData) { foreach (var player in PlayerMap.PlayerDataList) { player.Alive = false; player.DieMark = false; player.IsSurrender = false; } foreach (var grid in GridMap.GridList) { if (recordData.GridMap.GetGridDataByPos(grid.Pos.X, grid.Pos.Y, out var recordGrid)) { var id = grid.Id; grid.DeepCopy(recordGrid); grid.Id = id; } else { LogSystem.LogError($"RegenerateMap Error 找不到地图 {recordData.MapName} 的格子信息 Pos=({grid.Pos.X},{grid.Pos.Y})"); } } // 警告:存档恢复以 (Civ,Force) 为主键匹配 PlayerData。多人允许相同 Empire 后, // 同 Empire 的多个玩家会全部命中同一条记录(首匹配),后续玩家恢复结果错乱,待后续改为以 PlayerData.Id 匹配。 foreach (var playerInfo in recordData.PlayerInfos) { PlayerData p = null; foreach (var player in PlayerMap.PlayerDataList) { if(player.PlayerCivId == playerInfo.Civ && player.PlayerForceId == playerInfo.Force) { p = player; break; } } if (p == null) { LogSystem.LogError($"RegenerateMap Error 找不到地图 {recordData.MapName} 的玩家文明信息 Civ={playerInfo.Civ} Force={playerInfo.Force}"); continue; } foreach (var cityInfo in playerInfo.Cities) { if (!GridMap.GetGridDataByPos(cityInfo.X, cityInfo.Y, out var cityGrid)) { LogSystem.LogError($"RegenerateMap Error 找不到地图 {recordData.MapName} 的城市格子信息 Pos=({cityInfo.X},{cityInfo.Y})"); continue; } //建设一个新城市 CityData c = AddCityData(cityGrid.Id, p.Id); c.Level = cityInfo.Level; p.Alive = true; if (cityInfo.IsCapital) { //设置该城市为首都 Main.CityLogic.SetCapital(this, c.Id); //设置该玩家的文明摇篮城市为该城市 p.CradleCityId = c.Id; } foreach (var unitInfo in cityInfo.Units) { if (!GridMap.GetGridDataByPos(unitInfo.X, unitInfo.Y, out var unitGrid)) { LogSystem.LogError($"RegenerateMap Error 找不到地图 {recordData.MapName} 的城市格子信息 Pos=({unitInfo.X},{unitInfo.Y})"); continue; } if (AddUnitData(unitGrid.Id, c.Id, unitInfo.UnitFullType, out var newUnit)) newUnit.SetFullActionPoint(); } } } foreach (var player in PlayerMap.PlayerDataList) { if (GetCityCount(player.Id) > 0) { player.Alive = true; continue; } player.Alive = false; player.DieMark = false; } } // 用于使用指定地图进游戏的清理工作 public void Clear(MapConfig config) { Net.Clear(); MatchSettlement.Init(this, config); } // 检查两个 map 的不同之处 public void CompareEqual(MapData map) { var differences = new List(); // 使用序列化比较各个组件 CompareComponent(MapConfig, map.MapConfig, "MapConfig", differences); CompareComponent(GridMap, map.GridMap, "GridMap", differences); CompareComponent(PlayerMap, map.PlayerMap, "PlayerMap", differences); CompareComponent(PlayerMap.PlayerDataList, map.PlayerMap.PlayerDataList, "PlayerMap.PlayerDataList", differences); CompareComponent(CityMap, map.CityMap, "CityMap", differences); CompareComponent(UnitMap, map.UnitMap, "UnitMap", differences); Net.CompareEqual(map.Net); // 比较字典 CompareComponent(CityToPlayerDict, map.CityToPlayerDict, "CityToPlayerDict", differences); CompareComponent(UnitToCityDict, map.UnitToCityDict, "UnitToCityDict", differences); CompareComponent(UnitToGridDict, map.UnitToGridDict, "UnitToGridDict", differences); CompareComponent(CityToGridDict, map.CityToGridDict, "CityToGridDict", differences); // 输出结果 foreach (var diff in differences) { LogSystem.LogWarning($" - {diff}"); } } // 检查两个 map 的不同之处 public List GetCompareEqual(MapData map) { var differences = new List(); // 使用序列化比较各个组件 CompareComponent(MapConfig, map.MapConfig, "MapConfig", differences); CompareComponent(GridMap, map.GridMap, "GridMap", differences); CompareComponent(PlayerMap, map.PlayerMap, "PlayerMap", differences); CompareComponent(PlayerMap.PlayerDataList, map.PlayerMap.PlayerDataList, "PlayerMap.PlayerDataList", differences); CompareComponent(CityMap, map.CityMap, "CityMap", differences); CompareComponent(UnitMap, map.UnitMap, "UnitMap", differences); Net.CompareEqual(map.Net); // 比较字典 CompareComponent(CityToPlayerDict, map.CityToPlayerDict, "CityToPlayerDict", differences); CompareComponent(UnitToCityDict, map.UnitToCityDict, "UnitToCityDict", differences); CompareComponent(UnitToGridDict, map.UnitToGridDict, "UnitToGridDict", differences); CompareComponent(CityToGridDict, map.CityToGridDict, "CityToGridDict", differences); return differences; } // 获取 player 是否在大回合内有没有行动 public bool GetPlayerHasActedInBigTurn(PlayerData player) { uint bigTurn = 0; foreach (var p in PlayerMap.PlayerDataList) { if (p.Turn >= bigTurn) bigTurn = p.Turn; } return player.Turn < bigTurn; } public static bool CompareComponent2(T obj1, T obj2) { try { var bytes1 = MemoryPackSerializer.Serialize(obj1); var bytes2 = MemoryPackSerializer.Serialize(obj2); return bytes1.SequenceEqual(bytes2); } catch (Exception ex) { LogSystem.LogError($"comparison failed: {ex.Message}"); return false; } } public static void CompareComponent(T obj1, T obj2, string name, List differences) { try { var bytes1 = MemoryPackSerializer.Serialize(obj1); var bytes2 = MemoryPackSerializer.Serialize(obj2); if (!bytes1.SequenceEqual(bytes2)) { differences.Add($"{name} differs (serialized data mismatch)"); } } catch (Exception ex) { differences.Add($"{name} comparison failed: {ex.Message}"); } } public static List FindDifferences(T obj1, T obj2, string path = "", HashSet visited = null) { var differences = new List(); visited ??= new HashSet(); if (obj1 == null && obj2 == null) return differences; if (obj1 == null || obj2 == null) { differences.Add($"{path}: one is null ({obj1?.ToString() ?? "null"} vs {obj2?.ToString() ?? "null"})"); return differences; } // 防止循环引用 if (visited.Contains(obj1)) return differences; visited.Add(obj1); // 使用运行时类型 var type1 = obj1.GetType(); var type2 = obj2.GetType(); if (type1 != type2) { differences.Add($"{path}: type mismatch ({type1.Name} vs {type2.Name})"); return differences; } var type = type1; // 处理基础类型 if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal) || type.IsEnum) { if (!obj1.Equals(obj2)) differences.Add($"{path}: {obj1} != {obj2}"); return differences; } // 处理集合类型 if (obj1 is IEnumerable enum1 && obj2 is IEnumerable enum2 && !(obj1 is string)) { var list1 = enum1.Cast().ToList(); var list2 = enum2.Cast().ToList(); if (list1.Count != list2.Count) { differences.Add($"{path}.Count: {list1.Count} != {list2.Count}"); } var minCount = Math.Min(list1.Count, list2.Count); for (int i = 0; i < minCount; i++) { var itemDiffs = FindDifferences(list1[i], list2[i], $"{path}[{i}]", new HashSet(visited)); differences.AddRange(itemDiffs); } return differences; } // 处理字典类型 if (type.GetInterfaces() .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))) { var keysProperty = type.GetProperty("Keys"); var itemProperty = type.GetProperty("Item"); if (keysProperty != null && itemProperty != null) { var keys1 = (IEnumerable)keysProperty.GetValue(obj1); var keys2 = (IEnumerable)keysProperty.GetValue(obj2); var keysList1 = keys1.Cast().ToHashSet(); var keysList2 = keys2.Cast().ToHashSet(); var allKeys = keysList1.Union(keysList2); foreach (var key in allKeys) { var hasKey1 = keysList1.Contains(key); var hasKey2 = keysList2.Contains(key); if (!hasKey1 || !hasKey2) { differences.Add($"{path}[{key}]: exists in {(hasKey1 ? "obj1" : "obj2")} only"); continue; } var value1 = itemProperty.GetValue(obj1, new[] { key }); var value2 = itemProperty.GetValue(obj2, new[] { key }); var itemDiffs = FindDifferences(value1, value2, $"{path}[{key}]", new HashSet(visited)); differences.AddRange(itemDiffs); } } return differences; } // 处理复杂对象 - 只检查 MemoryPack 序列化的成员 var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead && IsMemoryPackSerializable(p)); foreach (var prop in properties) { try { var value1 = prop.GetValue(obj1); var value2 = prop.GetValue(obj2); var propPath = string.IsNullOrEmpty(path) ? prop.Name : $"{path}.{prop.Name}"; var propDiffs = FindDifferences(value1, value2, propPath, new HashSet(visited)); differences.AddRange(propDiffs); } catch (Exception ex) { differences.Add($"{path}.{prop.Name}: reflection error - {ex.Message}"); } } var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance) .Where(f => IsMemoryPackSerializable(f)); foreach (var field in fields) { try { var value1 = field.GetValue(obj1); var value2 = field.GetValue(obj2); var fieldPath = string.IsNullOrEmpty(path) ? field.Name : $"{path}.{field.Name}"; var fieldDiffs = FindDifferences(value1, value2, fieldPath, new HashSet(visited)); differences.AddRange(fieldDiffs); } catch (Exception ex) { differences.Add($"{path}.{field.Name}: reflection error - {ex.Message}"); } } return differences; } // 更完整的 MemoryPack 序列化检测 private static bool IsMemoryPackSerializable(System.Reflection.MemberInfo member) { // 检查是否有 MemoryPackIgnore 特性 if (member.GetCustomAttribute() != null) return false; // 检查是否有 MemoryPackInclude 特性(明确包含) if (member.GetCustomAttribute() != null) return true; // 对于属性:必须有 public getter 和 setter if (member is System.Reflection.PropertyInfo prop) { return prop.CanRead && prop.CanWrite && prop.GetMethod?.IsPublic == true && prop.SetMethod?.IsPublic == true; } // 对于字段:必须是 public 且非 readonly if (member is System.Reflection.FieldInfo field) { return field.IsPublic && !field.IsInitOnly && !field.IsLiteral; } return false; } // 简化的使用方法 public static bool CompareWithDetails(T obj1, T obj2) { var differences = FindDifferences(obj1, obj2); if (differences.Count > 0) { LogSystem.LogWarning($"Found {differences.Count} differences:"); foreach (var diff in differences.Take(10)) // 只显示前10个差异 { LogSystem.LogWarning($" - {diff}"); } if (differences.Count > 10) LogSystem.LogWarning($" ... and {differences.Count - 10} more differences"); } return differences.Count == 0; } public void FindDifferences(MapData target) { if (target == null) return; var allStr = ""; var diff = GetCompareEqual(target); foreach (var str in diff) allStr += str + "\n"; diff = FindDifferences(this, target); foreach (var str in diff) allStr += str + "\n"; LogSystem.LogError($"{allStr}"); for (int i = 0; i < Net.Actions.Count; i++) { if (i >= target.Net.Actions.Count) continue; if (Net.Actions[i].MapHash == target.Net.Actions[i].MapHash) continue; if (i == 0) { LogSystem.LogError($"Map不一致前后Action : 前:空" + $"后:{Main.MapData.Net.Actions[i].ActionId.GetStringLog()}"); } else { LogSystem.LogError($"Map不一致前后Action : 前:{Main.MapData.Net.Actions[i - 1].ActionId.GetStringLog()}" + $"后:{Main.MapData.Net.Actions[i].ActionId.GetStringLog()}"); } break; } } } // 地图 ID 生成器 [MemoryPackable] public partial class MapIdGenerator { [MemoryPackInclude] private uint _idGenerator; [MemoryPackConstructor] public MapIdGenerator() { _idGenerator = 0; } public uint GeneratorId() { return ++_idGenerator; } public MapIdGenerator DeepCopy() { var copy = new MapIdGenerator(); copy._idGenerator = _idGenerator; return copy; } public void DeepCopy(MapIdGenerator copy) { _idGenerator = copy._idGenerator; } } }