2261 lines
85 KiB
C#
2261 lines
85 KiB
C#
/*
|
||
* @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.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 TH1Renderer;
|
||
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;
|
||
|
||
// 单机用
|
||
public uint selfCivId;
|
||
public uint selfForceId;
|
||
|
||
// 联机用
|
||
public List<MemberCiv> MultiCivs;
|
||
private Dictionary<ulong, MemberCiv> _memberCivs;
|
||
|
||
// 校验用
|
||
private string _hash;
|
||
|
||
// 关卡限制数据
|
||
public List<MatchLimitType> MatchLimits;
|
||
|
||
// 超时时间
|
||
public bool IsLimitTime = false;
|
||
public int TimeLimitSeconds = 180;
|
||
|
||
// 水域类型,默认为 Pangea
|
||
public Logic.MapWaterType WaterType = Logic.MapWaterType.Pangea;
|
||
|
||
[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;
|
||
selfCivId = civId;
|
||
selfForceId = forceId;
|
||
MultiCivs = new List<MemberCiv>();
|
||
_memberCivs = new Dictionary<ulong, MemberCiv>();
|
||
}
|
||
|
||
// MemoryPack 反序列化之后的后处理
|
||
[MemoryPackOnDeserialized]
|
||
public void OnAfterMemoryPackDeserialize()
|
||
{
|
||
if (TimeLimitSeconds == 0) TimeLimitSeconds = 180;
|
||
// 旧存档兼容:WaterType 字段不存在时默认为 Pangea
|
||
if (!System.Enum.IsDefined(typeof(Logic.MapWaterType), WaterType))
|
||
WaterType = Logic.MapWaterType.Pangea;
|
||
}
|
||
|
||
// 根据房间成员信息更新 mapconfig 信息
|
||
public void UpdateLobbyMember(Dictionary<ulong, MemberInfo> memberInfos)
|
||
{
|
||
RefreshMultiCivsDict();
|
||
foreach (var kv in memberInfos)
|
||
{
|
||
if (_memberCivs.ContainsKey(kv.Key)) continue;
|
||
var civ = new MemberCiv();
|
||
civ.MemberId = kv.Key;
|
||
civ.CivId = 0;
|
||
civ.ForceId = 0;
|
||
MultiCivs.Add(civ);
|
||
}
|
||
RefreshMultiCivsDict();
|
||
}
|
||
|
||
// 内部刷新
|
||
private void RefreshMultiCivsDict()
|
||
{
|
||
if (_memberCivs.Count == MultiCivs.Count) return;
|
||
_memberCivs.Clear();
|
||
foreach (var memberCiv in MultiCivs) _memberCivs[memberCiv.MemberId] = memberCiv;
|
||
}
|
||
|
||
public MemberCiv GetMemberCiv(ulong memberId)
|
||
{
|
||
foreach (var memberCiv in MultiCivs)
|
||
{
|
||
if (memberCiv.MemberId == memberId) return memberCiv;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
// 主从端一致的更新某一个成员信息
|
||
public void UpdateMemberCiv(MemberCiv civ)
|
||
{
|
||
if (LobbyManager.Instance.Lobby.IsInLobby() && !LobbyManager.Instance.Lobby.IsLobbyOwner())
|
||
{
|
||
GameNetSender.Instance.ChangeCiv(civ);
|
||
return;
|
||
}
|
||
|
||
foreach (var memberCiv in MultiCivs)
|
||
{
|
||
if (memberCiv.MemberId != civ.MemberId) continue;
|
||
memberCiv.CivId = civ.CivId;
|
||
memberCiv.ForceId = civ.ForceId;
|
||
return;
|
||
}
|
||
MultiCivs.Add(civ);
|
||
}
|
||
|
||
// 主从端一致的本地数据检测
|
||
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);
|
||
}
|
||
|
||
// 从 MapData 中重绑定 MemberCiv 的 Player 信息
|
||
public bool ReBindPlayerInfoFromMapData(MapData mapData)
|
||
{
|
||
RefreshMultiCivsDict();
|
||
if (mapData.Net.Mode == NetMode.Single)
|
||
{
|
||
foreach (var player in mapData.PlayerMap.PlayerDataList)
|
||
{
|
||
if (player.PlayerCivId != selfCivId || player.PlayerForceId != selfForceId) continue;
|
||
mapData.PlayerMap.SelfPlayerId = player.Id;
|
||
}
|
||
if (mapData.PlayerMap.SelfPlayerId == 0)
|
||
{
|
||
LogSystem.LogError($"单机模式指定地图中找不到指定的阵营信息, 启动游戏失败, " +
|
||
$"地图 ID : {Id} 文明 ID : {selfCivId} 势力 ID : {selfForceId}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (mapData.Net.Mode == NetMode.Multi)
|
||
{
|
||
foreach (var member in MultiCivs)
|
||
{
|
||
foreach (var player in mapData.PlayerMap.PlayerDataList)
|
||
{
|
||
if (player.PlayerCivId != member.CivId || player.PlayerForceId != member.ForceId) continue;
|
||
member.PlayerId = player.PlayerCivId;
|
||
}
|
||
|
||
if (member.PlayerId == 0)
|
||
{
|
||
LogSystem.LogError($"联机模式指定地图中找不到指定的阵营信息, 启动游戏失败, " +
|
||
$"地图 ID : {Id} 文明 ID : {member.CivId} 势力 ID : {member.ForceId}");
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
public void ClearMultiCivs()
|
||
{
|
||
MultiCivs?.Clear();
|
||
_memberCivs?.Clear();
|
||
}
|
||
|
||
public void ChangeByMapConfig(MapConfig other)
|
||
{
|
||
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;
|
||
}
|
||
|
||
|
||
// 一场游戏的地图数据
|
||
[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 PlayerData CurPlayer => PlayerMap?.GetPlayerData(Net.CurPlayerId);
|
||
|
||
// 城市 -> 玩家, 将引用关系转化为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=null)
|
||
{
|
||
if (cityDataList == null) cityDataList = new List<CityData>();
|
||
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=null)
|
||
{
|
||
if (cityDataList == null) cityDataList = new HashSet<CityData>();
|
||
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)
|
||
{
|
||
var cityList = new List<CityData>();
|
||
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都销毁
|
||
var unitDataList = new List<UnitData>();
|
||
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下去
|
||
var tmpUnitDataList = new List<UnitData>();
|
||
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))
|
||
{
|
||
_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,用于刷新CityInfo
|
||
UnitToCityDict.TryGetValue(unitData.Id, out var cityId);
|
||
|
||
// 清除数据层绑定
|
||
RemoveUnitData(unitData.Id);
|
||
if (GetGridIdByUnitId(unitData.Id, out var gridId))
|
||
_gridToUnitDict.Remove(gridId);
|
||
|
||
// 刷新所属城市的CityInfo(人口数变化)
|
||
if (cityId != 0 && CityMap.GetCityById(cityId, out var cityData))
|
||
cityData.CityInfoRenderer(this)?.InstantUpdateCityInfo();
|
||
}
|
||
|
||
//改变小兵到城市的所属关系
|
||
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=null)
|
||
{
|
||
if (unitDataList == null) unitDataList = new List<UnitData>();
|
||
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=null)
|
||
{
|
||
if (unitDataList == null) unitDataList = new List<UnitData>();
|
||
foreach (var kv in CityToPlayerDict)
|
||
{
|
||
if (kv.Value != pid) continue;
|
||
GetUnitDataListByCityId(kv.Key, unitDataList);
|
||
}
|
||
}
|
||
|
||
// 通过城市 ID 找小兵 Set
|
||
public void GetUnitDataListByCityId(uint cid, HashSet<UnitData> unitDataList=null)
|
||
{
|
||
if (unitDataList == null) unitDataList = new HashSet<UnitData>();
|
||
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=null)
|
||
{
|
||
if (unitDataList == null) unitDataList = new HashSet<UnitData>();
|
||
foreach (var kv in CityToPlayerDict)
|
||
{
|
||
if (kv.Value != pid) continue;
|
||
GetUnitDataListByCityId(kv.Key, unitDataList);
|
||
}
|
||
}
|
||
|
||
// 通过玩家 ID 找敌方小兵列表
|
||
public void GetOtherUnitDataListByPlayerId(uint pid, List<UnitData> unitDataList=null)
|
||
{
|
||
if (unitDataList == null) unitDataList = new List<UnitData>();
|
||
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 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.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.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
|
||
public HashSet<uint> GetPlayerTerritoryGridIdSet(uint pid)
|
||
{
|
||
var gridSet = new HashSet<uint>();
|
||
var cityList = new List<CityData>();
|
||
GetCityDataListByPlayerId(pid, cityList);
|
||
foreach (var cityData in cityList)
|
||
{
|
||
cityData.Territory.GetAllTerritoryArea(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);
|
||
|
||
//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);
|
||
}
|
||
}
|
||
|
||
// 移除小兵数据
|
||
public void RemoveUnitData(uint uid)
|
||
{
|
||
UnitMap.RemoveUnitData(uid);
|
||
if (UnitToGridDict.Remove(uid, out var gid))
|
||
{
|
||
_gridToUnitDict.Remove(gid);
|
||
}
|
||
UnitToGridDict.Remove(uid);
|
||
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()
|
||
{
|
||
// 重绑定 ID
|
||
Net.RefreshPlayerNet(this);
|
||
|
||
// 重建缓存字典
|
||
_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)
|
||
{
|
||
action.Param.MapData = this;
|
||
action.Param.RefreshParams();
|
||
}
|
||
}
|
||
|
||
// 当场上有小兵受伤前
|
||
public void BeforeUnitDamaged(SettlementInfo info)
|
||
{
|
||
foreach (var unit in UnitMap.UnitList)
|
||
{
|
||
//避免在遍历的时候,直接改到了skillsList
|
||
var copy = new List<SkillBase>(unit.Skills);
|
||
foreach (var skill in copy) skill.BeforeUnitDamaged(unit, this, info);
|
||
}
|
||
}
|
||
|
||
// 当场上有小兵受伤时
|
||
public void OnUnitDamaged(SettlementInfo info)
|
||
{
|
||
foreach (var unit in UnitMap.UnitList)
|
||
{
|
||
//避免在遍历的时候,直接改到了skillsList
|
||
var copy = new List<SkillBase>(unit.Skills);
|
||
foreach (var skill in copy) skill.OnUnitDamaged(unit, this, info);
|
||
}
|
||
}
|
||
|
||
// 当场上有小兵移动时
|
||
public void OnAnyUnitMove(MapData map, UnitData moveUnit, GridData target, MoveType moveType)
|
||
{
|
||
foreach (var unit in UnitMap.UnitList)
|
||
{
|
||
var copy = new List<SkillBase>(unit.Skills);
|
||
foreach (var skill in copy) skill.OnAnyUnitMove(map, unit, moveUnit, target, moveType);
|
||
}
|
||
var gridCopy = new List<SkillBase>(target.Skills);
|
||
foreach (var skill in gridCopy) skill.OnAnyUnitMove(map, target, moveUnit, target, moveType);
|
||
}
|
||
|
||
// 当行为执行后
|
||
public void OnActionExecuted(ActionLogicBase logic, CommonActionParams param)
|
||
{
|
||
var unitListCopy = new List<UnitData>(UnitMap.UnitList);
|
||
foreach (var unit in unitListCopy)
|
||
{
|
||
var copy = new List<SkillBase>(unit.Skills);
|
||
foreach (var skill in copy) skill.OnActionExecuted(logic, param, unit);
|
||
}
|
||
}
|
||
|
||
// 当场小兵死上有亡时
|
||
public void OnAnyUnitDie(MapData map, UnitData dieUnit)
|
||
{
|
||
var unitListCopy = new List<UnitData>(UnitMap.UnitList);
|
||
foreach (var unit in unitListCopy)
|
||
{
|
||
//避免在遍历的时候,直接改到了skillsList
|
||
var copy = new List<SkillBase>(unit.Skills);
|
||
foreach (var skill in copy) skill.OnAnyUnitDie(map, unit, dieUnit);
|
||
}
|
||
}
|
||
|
||
// 当场上有小兵创建时
|
||
public void OnAnyUnitCreate(MapData map, UnitData newUnit)
|
||
{
|
||
foreach (var unit in UnitMap.UnitList)
|
||
{
|
||
//避免在遍历的时候,直接改到了skillsList
|
||
var copy = new List<SkillBase>(unit.Skills);
|
||
foreach (var skill in copy) skill.OnAnyUnitCreate(map, unit, newUnit);
|
||
}
|
||
}
|
||
|
||
public static void SaveMatchConfig(MapConfig config)
|
||
{
|
||
if (config == null) return;
|
||
|
||
// 改为二进制文件扩展名
|
||
string path = Application.persistentDataPath + "/../Config/match_config.dat";
|
||
int retryCount = 3;
|
||
|
||
while (retryCount > 0)
|
||
{
|
||
try
|
||
{
|
||
byte[] bytes = MemoryPackSerializer.Serialize(config);
|
||
File.WriteAllBytes(path, bytes);
|
||
return;
|
||
}
|
||
catch (IOException ex)
|
||
{
|
||
retryCount--;
|
||
if (retryCount <= 0)
|
||
{
|
||
LogSystem.LogError($"保存地图配置数据失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public static MapConfig GetMatchConfig()
|
||
{
|
||
string path = Application.persistentDataPath + "/../Config/match_config.dat";
|
||
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 void SaveMapData(MapData map, bool isBegin=false, bool isEnd=false)
|
||
{
|
||
if (map == null) return;
|
||
|
||
// 改为二进制文件扩展名
|
||
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 = MemoryPackSerializer.Serialize(map);
|
||
File.WriteAllBytes(path, bytes);
|
||
return;
|
||
}
|
||
catch (IOException ex)
|
||
{
|
||
retryCount--;
|
||
if (retryCount <= 0)
|
||
{
|
||
LogSystem.LogError($"保存地图数据失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public static MapData GetMapData(bool isMulti = false, bool isBegin = false, bool isEnd = false, uint mapId = 0)
|
||
{
|
||
string directory = Application.persistentDataPath + "/../Config/";
|
||
|
||
// 构建文件名模式
|
||
string pattern = "map_archive";
|
||
if (isBegin) pattern += "_begin";
|
||
else if (isEnd) pattern += "_end";
|
||
else pattern += "_continue";
|
||
if (isMulti) pattern += "_multi";
|
||
pattern += "_*.dat";
|
||
|
||
// 获取所有匹配的文件
|
||
if (!Directory.Exists(directory)) return null;
|
||
var files = Directory.GetFiles(directory, pattern);
|
||
if (files.Length == 0) return null;
|
||
// 按文件修改时间从新到旧排序
|
||
var sortedFiles = files.OrderByDescending(File.GetLastWriteTime).ToList();
|
||
// 如果指定了 mapId,优先查找匹配的文件
|
||
string targetFile = sortedFiles[0];
|
||
if (mapId != 0) targetFile = sortedFiles.FirstOrDefault(f => f.Contains($"_{mapId}.dat"));
|
||
if (targetFile == null) return null;
|
||
|
||
int retryCount = 3;
|
||
while (retryCount > 0)
|
||
{
|
||
try
|
||
{
|
||
byte[] bytes = File.ReadAllBytes(targetFile);
|
||
var mapData = MemoryPackSerializer.Deserialize<MapData>(bytes);
|
||
|
||
// 版本校验:检查反序列化后的数据是否有效
|
||
if (mapData?.MapConfig == null || mapData.GridMap == null || mapData.PlayerMap == 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;
|
||
}
|
||
|
||
public GameRecord ExportGameRecord()
|
||
{
|
||
var gameRecord = new GameRecord();
|
||
gameRecord.Mode = MapConfig.GameMode;
|
||
gameRecord.AIDiff = MapConfig.AIDiff;
|
||
gameRecord.Turn = PlayerMap.SelfPlayerData.Turn;
|
||
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");
|
||
var cityList = new List<CityData>();
|
||
GetCityDataListByPlayerId(PlayerMap.SelfPlayerId, cityList);
|
||
gameRecord.CityCount = (uint)cityList.Count;
|
||
gameRecord.MapWidth = MapConfig.Width;
|
||
gameRecord.MapHeight = MapConfig.Height;
|
||
gameRecord.PlayerCount = MapConfig.PlayerCount;
|
||
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;
|
||
}
|
||
|
||
// 存档
|
||
SaveMapData(Main.MapData);
|
||
AchievementDataManager.Instance.SaveAchievementData();
|
||
if (Main.MapData.Net.Mode == NetMode.Single) PlayerPrefs.SetInt("Archive", 1);
|
||
if (Main.MapData.Net.Mode == NetMode.Multi) PlayerPrefs.SetInt("MultiArchive", 1);
|
||
PlayerPrefs.Save();
|
||
// 设置当前玩家
|
||
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 = MatchSettlement.IsWin(PlayerMap.SelfPlayerId);
|
||
return MatchSettlement.IsFinished;
|
||
}
|
||
|
||
// // 游戏是否结束
|
||
// 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 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})");
|
||
}
|
||
}
|
||
|
||
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}");
|
||
}
|
||
|
||
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})");
|
||
}
|
||
|
||
//建设一个新城市
|
||
CityData c = AddCityData(cityGrid.Id, p.Id);
|
||
c.Level = cityInfo.Level;
|
||
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})");
|
||
}
|
||
|
||
if (AddUnitData(unitGrid.Id, c.Id, unitInfo.UnitFullType, out var newUnit))
|
||
newUnit.SetFullActionPoint();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 用于使用指定地图进游戏的清理工作
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|