2026-05-23 21:03:20 +08:00

3287 lines
126 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @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.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<PlayerSettlementInfo> PlayerSettlements;
// 旧存档兼容字段。新逻辑统一从 MultiCivs 读取单机/联机玩家槽位。
public uint selfCivId;
public uint selfForceId;
// 玩家位置槽位。旧名保留用于兼容现有序列化/联机消息。
public List<MemberCiv> MultiCivs;
private Dictionary<ulong, MemberCiv> _memberCivs;
// 校验用
[MemoryPackIgnore]
private string _hash;
// 关卡限制数据
public List<MatchLimitType> MatchLimits;
// 超时时间
public bool IsLimitTime = false;
public int TimeLimitSeconds = 180;
// 水域类型,默认为 Pangea
public Logic.MapWaterType WaterType = Logic.MapWaterType.Pangea;
public const int NoTeamId = 0;
[MemoryPackConstructor]
public MapConfig()
{
MultiCivs = new List<MemberCiv>();
PlayerSettlements = new List<PlayerSettlementInfo>();
_memberCivs = new Dictionary<ulong, MemberCiv>();
MatchLimits = new List<MatchLimitType>();
}
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<MemberCiv>();
_memberCivs = new Dictionary<ulong, MemberCiv>();
PlayerSettlements = new List<PlayerSettlementInfo>();
MatchLimits = new List<MatchLimitType>();
SetSinglePlayerCiv(civId, forceId);
}
// MemoryPack 反序列化之后的后处理
[MemoryPackOnDeserialized]
public void OnAfterMemoryPackDeserialize()
{
if (TimeLimitSeconds == 0) TimeLimitSeconds = 180;
// 旧存档兼容WaterType 字段不存在时默认为 Pangea
if (!System.Enum.IsDefined(typeof(Logic.MapWaterType), WaterType))
WaterType = Logic.MapWaterType.Pangea;
MultiCivs ??= new List<MemberCiv>();
PlayerSettlements ??= new List<PlayerSettlementInfo>();
MatchLimits ??= new List<MatchLimitType>();
EnsurePlayerSlots(NetMode.Multi);
RefreshMultiCivsDict();
}
public void EnsurePlayerSlots(NetMode netMode = NetMode.Multi)
{
MultiCivs ??= new List<MemberCiv>();
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<uint> { 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<uint> 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;
}
// 根据房间成员信息更新 mapconfig 信息
public bool UpdateLobbyMember(Dictionary<ulong, MemberInfo> 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<ulong, MemberCiv>();
MultiCivs ??= new List<MemberCiv>();
_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<MemberCiv>();
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;
EnsurePlayerSlots(mapData.Net?.Mode ?? NetMode.Multi);
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<MemberCiv>();
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;
}
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<ulong, MemberInfo> memberInfos)
{
if (memberInfos == null) return false;
EnsurePlayerSlots(NetMode.Multi);
var seen = new HashSet<ulong>();
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<MatchLimitType>(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<uint, uint> CityToPlayerDict;
// 小兵 -> 城市, 将引用关系转化为ID关系
public Dictionary<uint, uint> UnitToCityDict;
// 小兵 -> 格子, 将引用关系转化为ID关系
public Dictionary<uint, uint> UnitToGridDict;
// 城市 -> 格子, 将引用关系转化为ID关系
public Dictionary<uint, uint> CityToGridDict;
//--------------- 核心数据派生的数据 ---------------//
// 格子 -> 小兵, 缓存数据
private Dictionary<uint, uint> _gridToUnitDict;
// 格子 -> 城市, 缓存数据
private Dictionary<uint, uint> _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<CityData> 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 void GetCityDataListByPlayerId(uint pid, HashSet<CityData> 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<CityData> GetCityDataSetByPlayerId(uint pid)
{
var citySet = new HashSet<CityData>();
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<GridData> GetGridDataSetByPlayerId(uint pid)
{
var citySet = GetCityDataSetByPlayerId(pid);
var gridSet = new HashSet<GridData>();
foreach (var city in citySet)
{
var cityGridSet = new HashSet<uint>();
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<uint> GetGridIdSetByPlayerId(uint pid)
{
var citySet = GetCityDataSetByPlayerId(pid);
var gridSet = new HashSet<uint>();
foreach (var city in citySet)
{
var cityGridSet = new HashSet<uint>();
city.Territory.GetAllTerritoryArea(cityGridSet);
gridSet.UnionWith(cityGridSet);
}
return gridSet;
}
// 通过玩家 ID 查找首都城市 cityData
public bool GetCapitalCityDataByPlayerId(uint pid, out CityData cityData)
{
using var pooledCityList = THCollectionPool.GetListHandle<CityData>(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<UnitData>(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<UnitData>(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<UnitData> 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<UnitData> unitDataList)
{
foreach (var kv in CityToPlayerDict)
{
if (kv.Value != pid) continue;
GetUnitDataListByCityId(kv.Key, unitDataList);
}
}
// 通过城市 ID 找小兵 Set
public void GetUnitDataListByCityId(uint cid, HashSet<UnitData> 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<UnitData> unitDataList)
{
foreach (var kv in CityToPlayerDict)
{
if (kv.Value != pid) continue;
GetUnitDataListByCityId(kv.Key, unitDataList);
}
}
// 通过玩家 ID 找敌方小兵列表
public void GetOtherUnitDataListByPlayerId(uint pid, List<UnitData> 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<uint> GetNearby1RadiusFreelandGidList(uint gid)
{
var ret = new List<uint>();
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<CityData> _territoryTmpCityList = new List<CityData>();
public void GetPlayerTerritoryGridIdSet(uint pid, HashSet<uint> gridSet)
{
_territoryTmpCityList.Clear();
GetCityDataListByPlayerId(pid, _territoryTmpCityList);
foreach (var cityData in _territoryTmpCityList)
{
cityData.Territory.GetAllTerritoryArea(gridSet);
}
}
// 获取玩家的所有领土格子 ID - 兼容版本(会分配新集合)
public HashSet<uint> GetPlayerTerritoryGridIdSet(uint pid)
{
var gridSet = new HashSet<uint>();
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;
//Step #2 再生成单位
newUnit = UnitMap.AddUnitData(unitFullType, _idGenerator);
UnitToCityDict[newUnit.Id] = cid;
UnitToGridDict[newUnit.Id] = gid;
_gridToUnitDict[gid] = newUnit.Id;
//Step #3 增加技能
AddUnitSkill(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>() { 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<IdentifierBase> GetAllIdentifierBase()
{
var list = new List<IdentifierBase>();
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<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>();
_idGenerator = new MapIdGenerator();
}
// 从地图配置初始化
public MapData(MapConfig mapCfg, NetMode netMode)
{
MapID = (uint)Guid.NewGuid().GetHashCode();
MapConfig = mapCfg;
_idGenerator = new MapIdGenerator();
GridMap = new GridMapData(mapCfg, _idGenerator);
PlayerMap = new PlayerMapData(this, _idGenerator, netMode);
CityMap = new CityMapData();
UnitMap = new UnitMapData();
Net = new NetData();
MatchSettlement = new MatchSettlementInfo();
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>();
MatchSettlement.Init(this, mapCfg);
}
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<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>();
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<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();
_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();
}
MapConfig?.ApplyTeamDiplomacy(this);
}
// 当场上有小兵受伤前
public void BeforeUnitDamaged(SettlementInfo info)
{
using var unitHandle = THCollectionPool.GetListHandle<UnitData>(out var copyUnits);
copyUnits.AddRange(UnitMap.UnitList);
foreach (var unit in copyUnits)
{
//避免在遍历的时候直接改到了skillsList
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(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<UnitData>(out var copyUnits);
copyUnits.AddRange(UnitMap.UnitList);
foreach (var unit in copyUnits)
{
//避免在遍历的时候直接改到了skillsList
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(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<UnitData>(out var copyUnits);
copyUnits.AddRange(UnitMap.UnitList);
foreach (var unit in copyUnits)
{
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);
}
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)
{
using var unitHandle = THCollectionPool.GetListHandle<UnitData>(out var unitListCopy);
unitListCopy.AddRange(UnitMap.UnitList);
foreach (var unit in unitListCopy)
{
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(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<UnitData>(out var unitListCopy);
unitListCopy.AddRange(UnitMap.UnitList);
foreach (var unit in unitListCopy)
{
//避免在遍历的时候直接改到了skillsList
using var skillHandle = THCollectionPool.GetListHandle<SkillBase>(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<UnitData>(out var unitListCopy);
unitListCopy.AddRange(UnitMap.UnitList);
foreach (var unit in unitListCopy)
{
//避免在遍历的时候直接改到了skillsList
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 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<MapConfig>(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;
}
public static bool SaveMapData(MapData map, bool isBegin=false, bool isEnd=false)
{
if (map == null) return false;
if (ShouldSkipMapArchive(map)) return false;
// 改为二进制文件扩展名
string path = Application.persistentDataPath + "/../Config/map_archive";
if (isBegin) path += "_begin";
else if (isEnd) path += "_end";
else path += "_continue";
if (map.Net.Mode == NetMode.Multi) path += "_multi";
path += $"_{map.MapID}.dat";
int retryCount = 3;
while (retryCount > 0)
{
try
{
byte[] bytes = SerializeMapArchive(map);
if (FileTools.SafeWriteFile(path, bytes))
{
InvalidateMapArchiveAvailabilityCache();
return true;
}
retryCount--;
if (retryCount <= 0)
{
LogSystem.LogError($"保存地图数据失败: 安全写入失败");
}
}
catch (Exception ex)
{
retryCount--;
if (retryCount <= 0)
{
LogSystem.LogError($"保存地图数据失败: {ex.Message}");
}
}
}
return false;
}
private enum MapArchiveKind
{
Begin,
Continue,
End
}
private static readonly TimeSpan MapArchiveAvailabilityCacheRefreshInterval = TimeSpan.FromSeconds(1);
private static readonly Dictionary<bool, MapArchiveAvailabilityCache> MapArchiveAvailabilityCaches =
new Dictionary<bool, MapArchiveAvailabilityCache>();
private sealed class MapArchiveAvailabilityCache
{
public DateTime CheckedAtUtc;
public string Fingerprint;
public bool HasArchive;
}
public static bool HasMapArchive(bool isMulti = false)
{
var now = DateTime.UtcNow;
if (MapArchiveAvailabilityCaches.TryGetValue(isMulti, out var cache)
&& now - cache.CheckedAtUtc < MapArchiveAvailabilityCacheRefreshInterval)
{
return cache.HasArchive;
}
var fingerprint = GetMapArchiveAvailabilityFingerprint(isMulti);
if (cache != null && cache.Fingerprint == fingerprint)
{
cache.CheckedAtUtc = now;
return cache.HasArchive;
}
var hasArchive = GetLatestReadableMapArchive(isMulti, MapArchiveKind.Continue, 0) != null;
MapArchiveAvailabilityCaches[isMulti] = new MapArchiveAvailabilityCache
{
CheckedAtUtc = now,
Fingerprint = fingerprint,
HasArchive = hasArchive
};
return hasArchive;
}
public static MapData GetMapData(bool isMulti = false, bool isBegin = false, bool isEnd = false, uint mapId = 0)
{
var kind = GetMapArchiveKind(isBegin, isEnd);
return GetLatestReadableMapArchive(isMulti, kind, mapId);
}
private static MapArchiveKind GetMapArchiveKind(bool isBegin, bool isEnd)
{
if (isBegin) return MapArchiveKind.Begin;
if (isEnd) return MapArchiveKind.End;
return MapArchiveKind.Continue;
}
private static MapData GetLatestReadableMapArchive(bool isMulti, MapArchiveKind kind, uint mapId)
{
var files = GetMapArchiveCandidates(isMulti, kind, mapId);
var targetFile = files.FirstOrDefault();
if (targetFile == null) return null;
if (!TryParseMapArchiveFileName(targetFile, out _, out _, out var archiveMapId)) return null;
var endArchiveTimes = kind == MapArchiveKind.Continue
? GetLatestEndMapArchiveTimes(isMulti)
: null;
if (endArchiveTimes != null
&& endArchiveTimes.TryGetValue(archiveMapId, out var endWriteTime)
&& endWriteTime >= GetMapArchiveLastWriteTime(targetFile))
{
return null;
}
var mapData = ReadMapDataWithBackup(targetFile);
if (mapData == null) return null;
if (ShouldSkipMapArchive(mapData)) return null;
var expectedMode = isMulti ? NetMode.Multi : NetMode.Single;
if (mapData.Net.Mode != expectedMode)
{
LogSystem.LogWarning($"存档模式与文件名不一致,跳过: {targetFile}");
return null;
}
if (kind == MapArchiveKind.Continue && mapData.PlayerMap.SelfPlayerData?.IsSurrender == true)
return null;
return mapData;
}
private static bool ShouldSkipMapArchive(MapData map)
{
return map?.Net?.Mode == NetMode.Spectator
|| map?.MapConfig?.MatchSettlement == MatchSettlementType.Story;
}
private static void InvalidateMapArchiveAvailabilityCache()
{
MapArchiveAvailabilityCaches.Clear();
}
private static string GetMapArchiveAvailabilityFingerprint(bool isMulti)
{
string directory = Application.persistentDataPath + "/../Config/";
if (!Directory.Exists(directory)) return string.Empty;
unchecked
{
long hash = 17;
int count = 0;
AddMapArchiveFingerprint(directory, "map_archive_continue*.dat", isMulti, ref hash, ref count);
AddMapArchiveFingerprint(directory, "map_archive_continue*.dat.bak", isMulti, ref hash, ref count);
AddMapArchiveFingerprint(directory, "map_archive_end*.dat", isMulti, ref hash, ref count);
AddMapArchiveFingerprint(directory, "map_archive_end*.dat.bak", isMulti, ref hash, ref count);
return $"{count}:{hash}";
}
}
private static void AddMapArchiveFingerprint(string directory, string pattern, bool isMulti,
ref long hash, ref int count)
{
try
{
foreach (var path in Directory.EnumerateFiles(directory, pattern))
{
if (!TryParseMapArchiveFileName(path, out var kind, out var fileIsMulti, out _)) continue;
if (fileIsMulti != isMulti) continue;
if (kind != MapArchiveKind.Continue && kind != MapArchiveKind.End) continue;
count++;
hash = hash * 31 + Path.GetFileName(path).GetHashCode();
hash = hash * 31 + GetMapArchiveLastWriteTime(path).Ticks;
hash = hash * 31 + GetMapArchiveFileLength(path);
}
}
catch (Exception ex)
{
LogSystem.LogError($"读取存档目录失败: {ex.Message}");
}
}
private static long GetMapArchiveFileLength(string path)
{
try
{
return new FileInfo(path).Length;
}
catch
{
return 0;
}
}
private static List<string> GetMapArchiveCandidates(bool isMulti, MapArchiveKind kind, uint mapId)
{
string directory = Application.persistentDataPath + "/../Config/";
if (!Directory.Exists(directory)) return new List<string>();
try
{
return Directory.GetFiles(directory, "map_archive_*.dat")
.Concat(Directory.GetFiles(directory, "map_archive_*.dat.bak"))
.Where(path => IsMatchingMapArchive(path, isMulti, kind, mapId))
.OrderByDescending(GetMapArchiveLastWriteTime)
.ToList();
}
catch (Exception ex)
{
LogSystem.LogError($"读取存档目录失败: {ex.Message}");
return new List<string>();
}
}
private static bool IsMatchingMapArchive(string path, bool isMulti, MapArchiveKind kind, uint mapId)
{
if (!TryParseMapArchiveFileName(path, out var fileKind, out var fileIsMulti, out var fileMapId)) return false;
if (fileKind != kind || fileIsMulti != isMulti) return false;
return mapId == 0 || fileMapId == mapId;
}
private static bool TryParseMapArchiveFileName(string path, out MapArchiveKind kind, out bool isMulti, out uint mapId)
{
kind = MapArchiveKind.Continue;
isMulti = false;
mapId = 0;
var fileName = Path.GetFileName(path);
if (string.IsNullOrEmpty(fileName)) return false;
if (fileName.EndsWith(".bak", StringComparison.OrdinalIgnoreCase))
fileName = fileName.Substring(0, fileName.Length - ".bak".Length);
if (!fileName.EndsWith(".dat", StringComparison.OrdinalIgnoreCase)) return false;
var stem = fileName.Substring(0, fileName.Length - ".dat".Length);
const string prefix = "map_archive_";
if (!stem.StartsWith(prefix, StringComparison.Ordinal)) return false;
var rest = stem.Substring(prefix.Length);
if (rest.StartsWith("begin_", StringComparison.Ordinal))
{
kind = MapArchiveKind.Begin;
rest = rest.Substring("begin_".Length);
}
else if (rest.StartsWith("continue_", StringComparison.Ordinal))
{
kind = MapArchiveKind.Continue;
rest = rest.Substring("continue_".Length);
}
else if (rest.StartsWith("end_", StringComparison.Ordinal))
{
kind = MapArchiveKind.End;
rest = rest.Substring("end_".Length);
}
else
{
return false;
}
if (rest.StartsWith("multi_", StringComparison.Ordinal))
{
isMulti = true;
rest = rest.Substring("multi_".Length);
}
return uint.TryParse(rest, out mapId);
}
private static Dictionary<uint, DateTime> GetLatestEndMapArchiveTimes(bool isMulti)
{
var latestTimes = new Dictionary<uint, DateTime>();
var endFiles = GetMapArchiveCandidates(isMulti, MapArchiveKind.End, 0);
foreach (var endFile in endFiles)
{
if (!TryParseMapArchiveFileName(endFile, out _, out _, out var mapId)) continue;
var writeTime = GetMapArchiveLastWriteTime(endFile);
if (!latestTimes.TryGetValue(mapId, out var existingTime) || writeTime > existingTime)
latestTimes[mapId] = writeTime;
}
return latestTimes;
}
private static DateTime GetMapArchiveLastWriteTime(string path)
{
try
{
return File.GetLastWriteTime(path);
}
catch
{
return DateTime.MinValue;
}
}
private static MapData ReadMapDataWithBackup(string targetFile)
{
if (targetFile.EndsWith(".bak", StringComparison.OrdinalIgnoreCase))
return ReadMapDataFile(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)
{
if (!File.Exists(targetFile)) return null;
int retryCount = 3;
while (retryCount > 0)
{
try
{
byte[] bytes = File.ReadAllBytes(targetFile);
var mapData = DeserializeMapArchive(bytes);
// 版本校验:检查反序列化后的数据是否有效
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;
}
return mapData;
}
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;
}
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<MapData>(rawBytes);
}
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;
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;
}
// 检查下超时
if (Main.MapData.Net.Mode == NetMode.Multi && Main.MapData.MapConfig.IsLimitTime &&
Net.CurPlayerId != 0 && Time.time - Net.PlayerStartTime >= Main.MapData.MapConfig.TimeLimitSeconds)
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;
}
// 存档
var saveSucceeded = SaveMapData(Main.MapData);
AchievementDataManager.Instance.SaveAchievementData();
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);
}
// 获取下一位执行玩家
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<string>();
// 使用序列化比较各个组件
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<string> GetCompareEqual(MapData map)
{
var differences = new List<string>();
// 使用序列化比较各个组件
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>(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>(T obj1, T obj2, string name, List<string> 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<string> FindDifferences<T>(T obj1, T obj2, string path = "", HashSet<object> visited = null)
{
var differences = new List<string>();
visited ??= new HashSet<object>();
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<object>().ToList();
var list2 = enum2.Cast<object>().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<object>(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<object>().ToHashSet();
var keysList2 = keys2.Cast<object>().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<object>(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<object>(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<object>(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<MemoryPackIgnoreAttribute>() != null)
return false;
// 检查是否有 MemoryPackInclude 特性(明确包含)
if (member.GetCustomAttribute<MemoryPackIncludeAttribute>() != 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>(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;
}
}
}