2026-06-08 16:55:24 +08:00

1847 lines
88 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.

using System;
using System.Collections.Generic;
using UnityEngine;
using RuntimeData;
using Logic.Action;
using Logic.Audio;
using Logic.Config;
using Logic.CrashSight;
using Logic.Pool;
using Logic.Skill;
using TH1_Anim;
using TH1_Anim.Fragments;
using TH1_Core.Events;
using TH1_Core.Managers;
using Logic.Multilingual;
using TH1_Logic.Action;
using TH1_Logic.Collect;
using TH1_Logic.Core;
using TH1_Presentation.Sequencer.Task;
using TH1Renderer;
namespace Logic
{
public class PlayerLogic : IPlayerLogic
{
private Main _main;
// 缓存枚举值,只在类加载时获取一次
private static readonly WonderTypeEnum[] _cachedWonderTypes =
(WonderTypeEnum[])System.Enum.GetValues(typeof(WonderTypeEnum));
// 复用的临时集合
private HashSet<uint> _tmpTerritoryBuf;
private List<GridData> _tmpAroundBuf;
public PlayerLogic(Main main)
{
_main = main;
}
// 返回带后缀的 ForceName 显示文本。同 Empire (Civ+Force) 出现 ≥2 次时,
// 用该玩家在 PlayerDataList 中的全局位置 (1-based) 追加 " {idx}P"。
// 例6 名玩家中第 4、第 6 是同阵营,则显示 "斯卡雷特帝国 4P" / "斯卡雷特帝国 6P"。
public static string GetDisplayForceName(MapData mapData, PlayerData player)
{
if (player == null) return string.Empty;
if (!Table.Instance.PlayerDataAssets.GetPlayerInfo(player, out var info)) return string.Empty;
var raw = MultilingualManager.Instance.GetMultilingualTextSafe(info.ForceName);
if (mapData == null || mapData.PlayerMap == null) return raw;
var list = mapData.PlayerMap.PlayerDataList;
int sameCount = 0;
int globalIdx = -1;
for (int i = 0; i < list.Count; i++)
{
var p = list[i];
if (p.PlayerCivId != player.PlayerCivId || p.PlayerForceId != player.PlayerForceId) continue;
sameCount++;
if (p.Id == player.Id) globalIdx = i + 1;
}
if (sameCount <= 1 || globalIdx < 0) return raw;
return $"{raw} {globalIdx}P";
}
public void UpdateWonder(MapData mapData, PlayerData player,WonderTypeEnum specificWonder = WonderTypeEnum.None)
{
bool isMainSelfPlayer = mapData == Main.MapData && player == mapData.PlayerMap.SelfPlayerData;
int cachedMaxCityLevel = -1;
int cachedConnectedCityCount = -1;
bool cachedHasAllTech = false;
bool hasCachedHasAllTech = false;
bool cachedOneTowerInSight = false;
bool hasCachedOneTowerInSight = false;
bool cachedAllTowerInSight = false;
bool hasCachedAllTowerInSight = false;
foreach (WonderTypeEnum wonder in _cachedWonderTypes)
{
//如果指定了特定奇观,那么只判断对应的奇观
if (specificWonder != WonderTypeEnum.None && wonder != specificWonder) continue;
if (wonder == WonderTypeEnum.None) continue;
var wonderState = player.Wonder.GetWonderState(wonder);
//part #1 如果是未开启的奇观判断是否需要发出WonderStart提示
if (wonderState == WonderState.NO_HINT)
{
//step #1 确认是否拥有科技
if (!TryGetStartWonderTechAtom(wonder, out var startWonderTechAtom)) continue;
if (!player.TechTree.CheckIfHasTechAtom(startWonderTechAtom)) continue;
//step #2 确认EYE POWER PARK的特殊条件
bool checkSpecial = true;
switch (wonder)
{
case WonderTypeEnum.EYE:
if (!hasCachedOneTowerInSight)
{
cachedOneTowerInSight = CheckOneTowerInSight(mapData, player);
hasCachedOneTowerInSight = true;
}
checkSpecial = cachedOneTowerInSight;
break;
case WonderTypeEnum.POWER:
checkSpecial = player.TotalKill > 0;
break;
case WonderTypeEnum.PARK:
if (cachedMaxCityLevel < 0)
{
cachedMaxCityLevel = GetMaxCityLevel(mapData, player);
}
checkSpecial = cachedMaxCityLevel > 2;
break;
}
if (!checkSpecial) continue;
//step #3 设置奇观状态为已开启
player.Wonder.SetWonderState(wonder, WonderState.HAVE_HINT);
wonderState = WonderState.HAVE_HINT;
//如果是和平奇怪还要把技术调整为0
if (wonder == WonderTypeEnum.PEACE)
player.TurnNoAttack = 0;
//step #4 如果是player是真人玩家发出界面通知事件
if (isMainSelfPlayer)
{
var announcement = new ShowUIAnnounceMajorEvent
{
EventType = UIAnnounceMajorEventType.WonderStart,
Param1 = (int)wonder
};
EventManager.Publish(announcement);
}
}
//part #2 如果是已经wonderStart的奇观判断是否需要发出WonderComplete提示
if (wonderState == WonderState.HAVE_HINT)
{
//step #1 确认奇观有没有完成
bool checkWonderDone;
switch (wonder)
{
case WonderTypeEnum.PEACE:
checkWonderDone = player.TurnNoAttack >= 5;
break;
case WonderTypeEnum.KNOWLEDGE:
if (!hasCachedHasAllTech)
{
cachedHasAllTech = player.TechTree.HasAllTech(player);
hasCachedHasAllTech = true;
}
checkWonderDone = cachedHasAllTech;
break;
case WonderTypeEnum.TRADE:
if (cachedConnectedCityCount < 0)
{
cachedConnectedCityCount = GetConnectedCityCount(mapData, player);
}
checkWonderDone = cachedConnectedCityCount >= 5;
break;
case WonderTypeEnum.WEALTH:
checkWonderDone = player.PlayerCoin >= 100;
break;
case WonderTypeEnum.EYE:
if (!hasCachedAllTowerInSight)
{
cachedAllTowerInSight = CheckAllTowerInSight(mapData, player);
hasCachedAllTowerInSight = true;
}
checkWonderDone = cachedAllTowerInSight;
break;
case WonderTypeEnum.POWER:
checkWonderDone = player.TotalKill >= 10;
break;
case WonderTypeEnum.PARK:
if (cachedMaxCityLevel < 0)
{
cachedMaxCityLevel = GetMaxCityLevel(mapData, player);
}
checkWonderDone = cachedMaxCityLevel > 5;
break;
default:
checkWonderDone = false;
break;
}
if (!checkWonderDone) continue;
//step #2 设置状态
player.Wonder.SetWonderState(wonder, WonderState.FINISH_NOT_BUILD);
//step #3 如果是player是真人玩家发出界面通知事件
if (isMainSelfPlayer)
{
var announcement = new ShowUIAnnounceMajorEvent
{
EventType = UIAnnounceMajorEventType.WonderEnd,
Param1 = (int)wonder
};
EventManager.Publish(announcement);
}
}
}
}
public static bool TryGetStartWonderTechAtom(WonderTypeEnum wonder, out TechAtom techAtom)
{
switch (wonder)
{
case WonderTypeEnum.PEACE:
techAtom = TechAtom.StartWonderPEACE;
return true;
case WonderTypeEnum.KNOWLEDGE:
techAtom = TechAtom.StartWonderKNOWLEDGE;
return true;
case WonderTypeEnum.TRADE:
techAtom = TechAtom.StartWonderTRADE;
return true;
case WonderTypeEnum.WEALTH:
techAtom = TechAtom.StartWonderWEALTH;
return true;
case WonderTypeEnum.POWER:
techAtom = TechAtom.StartWonderPOWER;
return true;
case WonderTypeEnum.PARK:
techAtom = TechAtom.StartWonderPARK;
return true;
case WonderTypeEnum.EYE:
techAtom = TechAtom.StartWonderEYE;
return true;
default:
techAtom = TechAtom.None;
return false;
}
}
public void Update(MapData mapData)
{
if (mapData == null)
return;
//TODO 放到GameLogic
//每帧判断每个玩家的奇观达成情况
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
//单次调用内即可处理同一帧开启+完成奇观
UpdateWonder(mapData, player);
}
//Part #2 判断当前这一帧正在行动的玩家meetlist的更新情况
var curPlayer = mapData.CurPlayer;
if (curPlayer == null) return;
//遍历所有unit有没有玩家新看到的unit
foreach (var unit in mapData.UnitMap.UnitList)
{
if (!mapData.GetGridDataByUnitId(unit.Id, out var grid)) continue;
if (!mapData.GetPlayerDataByUnitId(unit.Id, out var player1)) continue;
if (curPlayer.Sight.CheckIsInSight(grid.Id) && !curPlayer.MeetPlayers.Contains(player1.Id))
AddMeetPlayer(mapData,curPlayer,player1);
}
//遍历所有city有没有玩家新看到的city
foreach (var city in mapData.CityMap.CityList)
{
if (!mapData.GetGridDataByCityId(city.Id, out var grid)) continue;
if (!mapData.GetPlayerDataByCityId(city.Id, out var player1)) continue;
if (curPlayer.Sight.CheckIsInSight(grid.Id) && !curPlayer.MeetPlayers.Contains(player1.Id))
AddMeetPlayer(mapData,curPlayer,player1);
}
//Part #3 判断当前有没有玩家出局
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
if (player.DieMark && !player.IsSurvival)
{
player.DieMark = false;
var announcement = new ShowUIAnnounceMajorEvent { EventType = UIAnnounceMajorEventType.CivLose,Param1 = (int)player.Id};
EventManager.Publish(announcement);
//SetCenterMessageShow(UICenterMessageID.ForcesFallen,player);
}
}
//判断玩家的胜负情况
//var selfp = mapData.PlayerMap.SelfPlayerData;
//if (mapData.GetCityCount(selfp.Id) == 0)
//selfp.Alive = false;
}
// 回合开始
public void StartPlayerTurn(MapData map, uint playerId)
{
if (!map.PlayerMap.GetPlayerDataByPlayerID(playerId, out var player)) return;
var actionId = new CommonActionId { ActionType = CommonActionType.TurnStart };
var action = new PlayerTurnStartAction(actionId);
var param = new CommonActionParams(Main.MapData, playerData:player);
param.RefreshParams();
action.CompleteExecute(param);
//action.ExecuteWithoutFullActionPeriod(param);
if ((map.Net.Mode == NetMode.Multi || map.Net.Mode == NetMode.Spectator)
&& map.Net.Players.ContainsValue(playerId))
return;
if (!map.CheckIsAI(playerId)) return;
AIAddMoney(map, playerId);
}
// 回合结束
public void EndPlayerTurn(MapData map, uint playerId)
{
if (!map.PlayerMap.GetPlayerDataByPlayerID(playerId, out var player)) return;
var actionId = new CommonActionId { ActionType = CommonActionType.TurnEnd };
var action = new PlayerTurnEndAction(actionId);
var param = new CommonActionParams(Main.MapData, playerData:player);
param.RefreshParams();
action.CompleteExecute(param);
//action.ExecuteWithoutFullActionPeriod(param);
}
// 投降
public void Surrender(MapData map, uint playerId)
{
if (!map.PlayerMap.GetPlayerDataByPlayerID(playerId, out var player)) return;
var actionId = new CommonActionId { ActionType = CommonActionType.PlayerSurrender };
var action = ActionLogicFactory.GetActionLogic(actionId);
var param = new CommonActionParams(Main.MapData, playerData:player);
param.RefreshParams();
if (!action.CheckCan(param)) return;
action.CompleteExecute(param);
}
// AI 发钱
public void AIAddMoney(MapData map, uint playerId)
{
if (!map.PlayerMap.GetPlayerDataByPlayerID(playerId, out var player)) return;
var actionId = new CommonActionId { ActionType = CommonActionType.AIParamControl , AIParamType = AIParamControlType.AIMoney};
var action = new AIParamControlAction(actionId);
var param = new CommonActionParams(Main.MapData, playerData:player);
param.RefreshParams();
action.CompleteExecute(param);
//action.ExecuteWithoutFullActionPeriod(param);
}
//更新pid下所有领土的所有格子的buildinglevel
public void UpdateTerritoryAllBuildingLevel(MapData mapData, uint pid)
{
var ter = mapData.GetPlayerTerritoryGridIdSet(pid);
mapData.PlayerMap.GetPlayerDataByPlayerID(pid, out var player);
//step #1 更新我国所有领土的特殊建筑 (除了学院和市场)
foreach (var gid in ter)
{
//step #1 获得gid的resource
if (!mapData.GridMap.GetGridDataByGid(gid, out var grid)) continue;
if (grid.Resource == ResourceType.None) continue;
//TODO 将来要对resource管理分类
if (grid.Resource is ResourceType.Sawmill or ResourceType.Windmill or ResourceType.Forge
or ResourceType.Preserve or ResourceType.KaguyaFrenchYard or ResourceType.EgyptianIrrigation)
{
Main.CityLogic.UpdateGridBuildingData_LogicView(mapData, grid);
//Main.CityLogic.UpdateGrid_ViewOnly(mapData,grid);
if (player != null)
{
foreach (var item in player.MomentData.Items)
{
item.OnNewResourceGet(mapData, player, grid.Resource, grid.buildingLevel);
}
}
}
}
//Step #2 更新那我国所有领土的学院和市场(必须再Step#1 全部做完再做,这里要明确分先后)
foreach (var gid in ter)
{
//step #1 获得gid的resource
if (!mapData.GridMap.GetGridDataByGid(gid, out var grid)) continue;
if (grid.Resource == ResourceType.None) continue;
//TODO 将来要对resource管理分类
if (grid.Resource is ResourceType.Academy or ResourceType.Market)
{
Main.CityLogic.UpdateGridBuildingData_LogicView(mapData, grid);
//Main.CityLogic.UpdateGrid_ViewOnly(mapData,grid);
if (player != null)
{
foreach (var item in player.MomentData.Items)
{
item.OnNewResourceGet(mapData, player, grid.Resource, grid.buildingLevel);
}
}
}
}
}
// unit 占领了位于 grid 的村庄返回一个CityData
public CityData OccupyVillage_LogicOnly(MapData mapData, UnitData unitData, GridData gridData)
{
// 先拿到小兵所属的玩家
if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var playerData)) return null;
//新建城市
var cityData = mapData.AddCityData(gridData.Id, playerData.Id);
//转移小兵的编制
if (!mapData.GetCityDataByUnitId(unitData.Id, out var oldCityData))
{
LogSystem.LogError("UnitHasNoCityData");
return cityData;
}
mapData.SetUnitIdToCityId(unitData.Id, cityData.Id);
oldCityData.SetCityRenderer(mapData);
if (mapData.IsCurrentShowMap())
MapRenderer.Instance?.RenderUpdateCityMap();
cityData.SetCityRenderer(mapData);
//更新小兵的状态(可能会有城防)
unitData.Renderer(mapData)?.InstantUpdateUnit(true);
//更新该国家的所有level建筑
UpdateTerritoryAllBuildingLevel(mapData,playerData.Id);
//更新所有国家的联通情况
UpdateAllPlayerConnected(mapData);
//更新特殊占领技能
if(playerData.TechTree.CheckIfHasTechAtom(TechAtom.KaguyaFrenchNapoleonicCode))
Main.PlayerLogic.SetCityTerritoryGridSp(mapData,cityData,GridSpType.KaguyaGrid);
return cityData;
}
// unit 占领了city
public void OccupyCity_LogicView(MapData mapData, UnitData unitData, CityData cityData)
{
var gridData = cityData.Grid(mapData);
if (gridData == null) return;
bool momentBeOccupied = false;
bool momentBeOccupiedCaptain = false;
bool momentOccupyLv5 = false;
bool momentOccupyLv6 = false;
bool momentOccupyCaptain = false;
bool momentOccupyOurCaptain = false;
bool momentOccupyOurCity = false;
//Step #1 处理logic, 先拿到小兵所属的玩家
if (!mapData.GetPlayerDataByUnitId(unitData.Id, out var playerData)) return;
if (!mapData.GetPlayerDataByCityId(cityData.Id, out var playerData2)) return;
//记录oldplayer和newplayer
var oldPid = playerData2.Id;
var newPid = playerData.Id;
if (mapData == Main.MapData && oldPid == mapData.PlayerMap.SelfPlayerId)
{
momentBeOccupied = true;
if(playerData2.CradleCityId == cityData.Id)
momentBeOccupiedCaptain = true;
}
if (mapData == Main.MapData && newPid == mapData.PlayerMap.SelfPlayerId)
{
if(cityData.Level >= 6)
momentOccupyLv6 = true;
else
momentOccupyLv5 = true;
if(playerData2.CradleCityId == cityData.Id)
momentOccupyCaptain = true;
if(playerData.PlayerNamerData.CheckIsCityNameSelfBuild(cityData.Name))
momentOccupyOurCity = true;
if (playerData.CradleCityId == cityData.Id)
momentOccupyOurCaptain = true;
}
bool lostEmbassy = Main.CityLogic.CheckCradleCapital(mapData, playerData2, cityData);
// 修改城市和玩家的所属关系
if (!mapData.SetCityIdToPlayerId(cityData.Id, playerData.Id))
{
LogSystem.LogError("占领失败!");
return;
}
//Step #3 播放grid的fog特效关闭fire特效更新城市的造型
gridData.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
gridData.Renderer(mapData)?.InstantUpdateGrid();
gridData.Renderer(mapData)?.InstantUpdateCityBuilding(cityData.Level,cityData.Civ(mapData));
//Step #3.5 如果玩家有视野,播放音效
if(gridData.InMainSight())
AudioManager.Instance.PlayAudio("SFX/UNIT_capture");
//Step #4 转移小兵的编制,要在处理完城市和玩家所属关系后才能转移小兵编制,不然出局的玩家清理本城市小兵的时候会出错
if (!mapData.GetCityDataByUnitId(unitData.Id, out var oldCityData))
{
LogSystem.LogError("UnitHasNoCityData");
return;
}
mapData.SetUnitIdToCityId(unitData.Id, cityData.Id);
oldCityData.SetCityRenderer(mapData);
oldCityData.SetCityRenderer(Main.MapData);
cityData.SetCityRenderer(mapData);
//更新小兵的状态(可能会有城防)
unitData.Renderer(mapData)?.InstantUpdateUnit(true);
//更新城市的cityinfo
//gridData.CityOnGrid(mapData)?.CityInfoRenderer(mapData)?.InstantUpdateCityInfo();
//Step #5 撤销所有国家在 player2 建立的大使馆
if (lostEmbassy)
{
foreach (var info in playerData2.DiplomacyData.Info) info.IsEmbassy = false;
}
//Step #6 处理占领城市的特效(所有领土会升起一面旗帜
using var pooledGridList = THCollectionPool.GetHashSetHandle<uint>(out var gridList);
cityData.Territory.GetAllTerritoryArea(gridList);
foreach (var gd in gridList)
{
if (mapData.GridMap.GetGridDataByGid(gd, out var gdd))
{
//设置gdd格子的VFXrendermark
var t = new GridVFXParams(GridVFXType.Flag);
t.CivId = playerData.PlayerCivId;
gdd.Renderer(mapData)?.PlayVFXInSight(t);
}
}
//Step #7 更新该城镇3范围内的所有单元格(包括可交互物体的glow以及边界的更新)
if (!mapData.GetGridDataByCityId(cityData.Id, out var grid))
return;
_tmpAroundBuf ??= new List<GridData>();
_tmpAroundBuf.Clear();
mapData.GridMap.GetAroundGridData(3, 3, grid, _tmpAroundBuf);
foreach (var tg in _tmpAroundBuf)
tg.Renderer(mapData)?.InstantUpdateGrid(true);
using var pooledTerritoryList = THCollectionPool.GetListHandle<uint>(out var list);
list.AddRange(cityData.Territory.TerritoryArea);
//更新player的视野
Main.PlayerLogic.UpdateSight_LogicView(mapData, playerData, list);
//Step #8 更新两个国家的所有levelbuilding情况修正城市经验
UpdateTerritoryAllBuildingLevel(mapData,oldPid);
UpdateTerritoryAllBuildingLevel(mapData,newPid);
//Step #10 更新所有国家的联通情况,修正城市经验
UpdateAllPlayerConnected(mapData);
//Step #11 更新特殊占领技能
if(playerData.TechTree.CheckIfHasTechAtom(TechAtom.KaguyaFrenchNapoleonicCode))
Main.PlayerLogic.SetCityTerritoryGridSp(mapData,cityData,GridSpType.KaguyaGrid);
else
Main.PlayerLogic.ClearCityTerritoryGridSp(mapData,cityData,GridSpType.KaguyaGrid);
//Step #12 处理Moment
if (momentOccupyOurCaptain)
{
EventManager.Publish(new ShowUINotifyMoment()
{
MomentSubType = MomentSubType.BattleOccupyOurCaptain,
Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire,
});
}
else if (momentOccupyCaptain)
{
EventManager.Publish(new ShowUINotifyMoment()
{
MomentSubType = MomentSubType.BattleOccupyCaptain,
Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire,
});
}
else if (momentOccupyOurCity)
{
EventManager.Publish(new ShowUINotifyMoment()
{
MomentSubType = MomentSubType.BattleOccupyOurCity,
Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire,
});
}
else if (momentOccupyLv6)
{
EventManager.Publish(new ShowUINotifyMoment()
{
MomentSubType = MomentSubType.BattleOccupyBigCity,
Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire,
});
}
else if (momentOccupyLv5)
{
EventManager.Publish(new ShowUINotifyMoment()
{
MomentSubType = MomentSubType.BattleOccupyCity,
Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire,
});
}
else if (momentBeOccupiedCaptain)
{
EventManager.Publish(new ShowUINotifyMoment()
{
MomentSubType = MomentSubType.BattleCaptainLost,
Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire,
});
}
else if (momentBeOccupied)
{
EventManager.Publish(new ShowUINotifyMoment()
{
MomentSubType = MomentSubType.BattleCityLost,
Empire = Main.MapData.PlayerMap.SelfPlayerData.Empire,
});
}
}
public int GetMapUnitTypeCount(MapData mapData, UnitType unitType, GiantType giantType)
{
int ret = 0;
foreach (var unit in mapData.UnitMap.UnitList)
{
if ((unit.UnitType == unitType && unit.GiantType == giantType)
|| (unit.CarryUnitType == unitType && unit.CarryGiantType == giantType))
ret++;
}
return ret;
}
public int GetMapUnitTypeCount(MapData mapData, UnitType unitType, GiantType giantType, uint playerId)
{
int ret = 0;
foreach (var unit in mapData.UnitMap.UnitList)
{
if (!mapData.CheckUnitIdBelongPlayerId(unit.Id, playerId)) continue;
if ((unit.UnitType == unitType && unit.GiantType == giantType)
|| (unit.CarryUnitType == unitType && unit.CarryGiantType == giantType))
ret++;
}
return ret;
}
// 获取玩家在某个格子周围 1 单位的拥有某个资源且属于该玩家领土的grid的全列表
public List<GridData> GetPlayerResourceNearbyList(MapData mapData, PlayerData playerData, GridData gridData, ResourceType resourceType)
{
var aroundGridList = new List<GridData>();
_tmpAroundBuf ??= new List<GridData>();
_tmpAroundBuf.Clear();
mapData.GridMap.GetAroundGridDataSet_NOCENTER(1, 1, gridData, _tmpAroundBuf);
//var territoryGidSet = mapData.GetPlayerTerritoryGridIdSet(playerData.Id);
foreach (var grid in _tmpAroundBuf)
{
//如果不是玩家的领土
if(mapData.GetPlayerDataByTerritoryGridId(grid.Id, out var player)
&& player.Id != playerData.Id)
continue;
if (grid.Resource != resourceType) continue;
aroundGridList.Add(grid);
}
return aroundGridList;
}
// 获取玩家在某个格子周围 1 单位的某个地形的列表
public List<GridData> GetPlayerFeatureNearbyList(MapData mapData, PlayerData playerData, GridData gridData, TerrainFeature feature)
{
var aroundGridList = new List<GridData>();
_tmpAroundBuf ??= new List<GridData>();
_tmpAroundBuf.Clear();
mapData.GridMap.GetAroundGridDataSet_NOCENTER(1, 1, gridData, _tmpAroundBuf);
var territoryGidSet = mapData.GetPlayerTerritoryGridIdSet(playerData.Id);
foreach (var grid in _tmpAroundBuf)
{
if (!territoryGidSet.Contains(grid.Id)) continue;
if (grid.Feature != feature) continue;
aroundGridList.Add(grid);
}
return aroundGridList;
}
//---------------------------------------- city相关操作-------
// 获取firstMeet金钱数额
public int GetFirstMeetCoin(MapData map, PlayerData player)
{
return player.PlayerScore / 1000 * 2 + 3;
}
//获取player目前拥有城市的最高等级
public int GetMaxCityLevel(MapData map, PlayerData player)
{
int citylv = 2;
foreach (var city in map.CityMap.CityList)
if (map.GetPlayerDataByCityId(city.Id, out var p1) && p1 == player && city.Level > citylv)
citylv = city.Level;
return citylv;
}
//获取player目前平均等级
public int GetAverageCityLevel(MapData map, PlayerData player)
{
int cityCount = 0;
int cityLv = 0;
foreach (var city in map.CityMap.CityList)
{
if (map.GetPlayerDataByCityId(city.Id, out var p1) && p1 == player)
{
cityCount++;
cityLv += city.Level;
}
}
if (cityCount == 0) return 0;
return cityLv / cityCount;
}
//获取player目前和首都联通的城市数量
public int GetConnectedCityCount(MapData map, PlayerData player)
{
int c = 0;
foreach (var city in map.CityMap.CityList)
if (map.GetPlayerDataByCityId(city.Id, out var p2) && p2 == player && city.IsConnectedCapital)
c++;
return c;
}
//更新所有玩家的connected情况
public void UpdateAllPlayerConnected(MapData mapData)
{
foreach(var player in mapData.PlayerMap.PlayerDataList)
UpdateCityConnect(mapData,player);
}
// 这里的数据缓存到 GridMap 里面去 TODO 这个方法读的不是很懂
public void UpdateCityConnect(MapData mapData, PlayerData playerData)
{
//step #1 初始化将联通的城市存于Hashset中
if (playerData == null) return;
if (!mapData.GetCapitalCityDataByPlayerId(playerData.Id, out var capitalCity)) return;
using var pooledCityList = THCollectionPool.GetListHandle<CityData>(out var cityList);
mapData.GetCityDataListByPlayerId(playerData.Id, cityList);
using var pooledConnectList = THCollectionPool.GetHashSetHandle<uint>(out var connectList);
//Step #2 如果玩家还有原始首都,计算联通情况,否则所有城市都是不联通
if (playerData.CradleCityId == capitalCity.Id)
{
//算法总述
//注意一些事实:
//首先只有同一个playerId的港口之间才会通过[公海、同盟海域]计算一个5格内的bfs最短路然后标记在格子上
//其次每个player的上述每一对港口之间都会计算最短路 然后同时显示在玩家的渲染中
//再次每一个player在计算的时候都会受到sight的影响如果同盟的港口之间是联通的但是我方没有视野就是视为不联通
//于是,算法如下(如果更新某个playerId的首都联通情况)
//先整理出所有可计算港口连通性的格子:视野内,公海+己方领土+盟军领土
//先清除所有公海+己方领土+盟军领土的海域(要去掉桥梁和港口)的waterroad标记 gridData.WaterRoadMark[playerId] = false
//遍历每个港口将港口5格内bfs辐射连通性所有抵达其他港口的最短路要做如下标记
//gridData.WaterRoadMark[playerId] = true
// 然后更新所有这次的公海+己方领土+盟军领土的海域
//如果是港口桥梁 或者有任意一个waterroadMark[x] = true 那么gridData.Feature = road。这里主要是因为 你的标记即使置为false了也有可能别人用公海建立了海陆还是要渲染的
//否则置为那么gridData.Feature = none
//#1,记录港口之间的两两最短路
//#1-1首先处理初始变量,获取playerId的所有city和grid
var citySet = mapData.GetCityDataSetByPlayerId(playerData.Id);
var gridSet = mapData.GetGridDataSetByPlayerId(playerData.Id);
//获取所有视野内的grid然后去除非盟友的有主海域去除所有土地这是所有港口可以计算最短路的格子用来计算通路用的
using var pooledWaterRoadGidSet = THCollectionPool.GetHashSetHandle<uint>(out var waterRoadGidSet);
foreach (var gid in playerData.Sight.SightGidSet)
if (mapData.GridMap.GetGridDataByGid(gid, out var grid) && grid.Terrain != TerrainType.Land)
{
//如果友军或者盟军海域
if(mapData.CheckGridIdBelongPlayerIdUnion(gid, playerData.Id))
waterRoadGidSet.Add(gid);
//如果是公海
if(!mapData.GetPlayerDataByTerritoryGridId(gid,out var _))
waterRoadGidSet.Add(gid);
}
//#1-2清空所有waterRoadGidSet中的waterroad标记(仅关于playerforceId的那个标记)(除了port和bridge)
foreach (var gid in waterRoadGidSet)
{
if (!mapData.GridMap.GetGridDataByGid(gid, out var grid)) continue;
if (grid.Terrain == TerrainType.Land || grid.Resource == ResourceType.Port ||
grid.Resource == ResourceType.Bridge) continue;
if (playerData.PlayerForceId < grid.WaterRoadForceId.Length)
grid.WaterRoadForceId[playerData.PlayerForceId] = false;
}
//按照dis = 1 到5 依次处理每一对port首先将所有距离=1的port pair连接了然后是2然后3...
for (int dis = 1; dis <= 5; dis++)
{
//#1-3每个己方领土内的港口进行floodfill 标记抵达其他港口的最短路
foreach (var portCandidate in gridSet)
{
if (portCandidate.Resource != ResourceType.Port) continue;
//Debug.Log($"candidatePort is {portCandidate.Id}");
// 记录前驱节点
using var pooledPrev = THCollectionPool.GetDictionaryHandle<uint, uint>(out var prev);
using var pooledDist = THCollectionPool.GetDictionaryHandle<uint, int>(out var dist);
using var pooledVisited = THCollectionPool.GetHashSetHandle<uint>(out var visited);
// 初始港口格子
uint startId = portCandidate.Id;
dist[startId] = 0;
visited.Add(startId);
// BFS 队列
Queue<uint> queue = new Queue<uint>();
queue.Enqueue(startId);
// 计算从该港口出发能辐射的5个距离内的bfs情况
while (queue.Count > 0)
{
uint currentId = queue.Dequeue();
mapData.GridMap.GetGridDataByGid(currentId, out var curGrid);
int currentDist = dist[currentId];
// 最长只扩展到距离为dis的格子
if (currentDist >= dis)
continue;
// 获取相邻格子
_tmpAroundBuf ??= new List<GridData>();
_tmpAroundBuf.Clear();
mapData.GridMap.GetAroundGridDataSetByOrder(1, 1, curGrid, _tmpAroundBuf);
foreach (var neighbor in _tmpAroundBuf)
{
uint nextId = neighbor.Id;
// 条件限制:
// 1. 在roadGidSet里可以用Id判断
// 2. 未访问
// 3. Terrain != Land
if (!waterRoadGidSet.Contains(neighbor.Id)) continue;
if (visited.Contains(nextId)) continue;
if (neighbor.Terrain == TerrainType.Land) continue;
// 合法蔓延
dist[nextId] = currentDist + 1;
prev[nextId] = currentId;
visited.Add(nextId);
queue.Enqueue(nextId);
}
}
foreach (var targetPort in gridSet)
{
if (targetPort.Resource != ResourceType.Port) continue;
//先把目标港口的watermark设置为true
if (playerData.PlayerForceId < targetPort.WaterRoadForceId.Length)
targetPort.WaterRoadForceId[playerData.PlayerForceId] = true;
//只处理最短距离为dis的port pair
if (!dist.TryGetValue(targetPort.Id, out var disValue) || disValue != dis) continue;
//如果step步以内已经可达targetPort那么也不需要新增最短路
int step = 6;
using var pooledVisitedSet = THCollectionPool.GetHashSetHandle<uint>(out var visitedSet);
using var pooledStartSet = THCollectionPool.GetHashSetHandle<uint>(out var startSet);
using var pooledNextSet = THCollectionPool.GetHashSetHandle<uint>(out var nextSet);
startSet.Add(portCandidate.Id);
while(step > 0)
{
foreach (var tmpStartId in startSet)
visitedSet.Add(tmpStartId);
foreach (var tmpStartId in startSet)
{
mapData.GridMap.GetGridDataByGid(tmpStartId, out var tmpStartGrid);
_tmpAroundBuf.Clear();
mapData.GridMap.GetAroundGridData(1, 1, tmpStartGrid, _tmpAroundBuf);
foreach(var tmpAroundGrid in _tmpAroundBuf)
if (playerData.PlayerForceId < tmpAroundGrid.WaterRoadForceId.Length
&& tmpAroundGrid.WaterRoadForceId[playerData.PlayerForceId]
&& !visitedSet.Contains(tmpAroundGrid.Id))
nextSet.Add(tmpAroundGrid.Id);
}
var swap = startSet;
startSet = nextSet;
nextSet = swap;
nextSet.Clear();
step--;
}
//如果仅仅依靠当前的waterRoadMark 就能在5格内访问到targetPort那么跳出
if (visitedSet.Contains(targetPort.Id)) continue;
//Debug.Log($"Start Shortest Path !!!! From {targetPort.Id} to {portCandidate.Id}");
uint traceId = targetPort.Id;
//避免死循环的保险
int safe = 0;
while (traceId != portCandidate.Id && safe < 10)
{
//Debug.Log($"Path Node #{safe + 1} is {traceId}");
if (mapData.GridMap.GetGridDataByGid(traceId, out var traceGrid))
if(playerData.PlayerForceId < traceGrid.WaterRoadForceId.Length)
traceGrid.WaterRoadForceId[playerData.PlayerForceId] = true;
// 回溯前驱节点
traceId = prev[traceId];
safe++;
}
if(playerData.PlayerForceId < portCandidate.WaterRoadForceId.Length)
portCandidate.WaterRoadForceId[playerData.PlayerForceId] = true;
}
}
}
//#1-4 根据所有WaterMark 对所有可使用海域(公海 己方海域 盟军海域) 算一遍terrainFeature
foreach (var gid in waterRoadGidSet)
{
bool isRoad = false;
if (!mapData.GridMap.GetGridDataByGid(gid, out var grid)) continue;
for (int i = 0; i < grid.WaterRoadForceId.Length; i++)
if (grid.WaterRoadForceId[i])
{
//Debug.Log($"grid{grid.Id} is road because forceid{i}");
isRoad = true;
break;
}
//保险补一句bridge和port一定是road
if (grid.Resource == ResourceType.Bridge || grid.Resource == ResourceType.Port)
isRoad = true;
//如果road情况发生变化做调整
if (isRoad != (grid.Feature == TerrainFeature.Road))
{
grid.Feature = isRoad ? TerrainFeature.Road : TerrainFeature.None;
grid.Renderer(mapData)?.InstantUpdateGrid(true);
_tmpAroundBuf.Clear();
mapData.GridMap.GetAroundGridDataSetByOrder(1, 1, grid, _tmpAroundBuf);
foreach (var nearbyGrid in _tmpAroundBuf)
nearbyGrid.Renderer(mapData)?.InstantUpdateGrid(true);
}
}
//#1-5 从首都出发进行floodfill计算哪些城市可以被抵达。是否联通只需要判断两个格子是否都具有road属性即可(不过有些文明要特殊判断)
using var pooledCn = THCollectionPool.GetHashSetHandle<uint>(out var cn);
//floodfill算一遍从首都出发的连通性
if (!mapData.GetCapitalCityDataByPlayerId(playerData.Id, out var capital)) return;
if (!mapData.GetGridDataByCityId(capital.Id, out var capitalGrid)) return;
cn.Add(capitalGrid.Id);
Queue<uint> li = new Queue<uint>();
li.Enqueue(capitalGrid.Id);
while (li.Count > 0)
{
var head = li.Dequeue();
if (!mapData.GridMap.GetGridDataByGid(head, out var headGrid)) continue;
_tmpAroundBuf ??= new List<GridData>();
_tmpAroundBuf.Clear();
mapData.GridMap.GetAroundGridDataSet_NOCENTER(1, 1, headGrid, _tmpAroundBuf);
foreach (var aroundGrid in _tmpAroundBuf)
{
//Step #1如果不是我方领土或者中立领土不行
if (mapData.GetPlayerDataByTerritoryGridId(aroundGrid.Id, out var _) &&
!mapData.CheckGridIdBelongPlayerIdUnion(aroundGrid.Id, playerData.Id))
continue;
//Step #2联通检测。这里要处理特殊情况
//Step #2-1 先处理kaguya 的联通判断bamboomove的情况
if (playerData.TechTree.CheckIfHasTechAtom(TechAtom.KaguyaFrenchBambooMove))
{
if (aroundGrid.Feature != TerrainFeature.Road && aroundGrid.Vegetation != Vegetation.Trees)
continue;
}
// Step #2-2 先处理moriya 的联通判断moriyaroad的情况
else if (playerData.TechTree.CheckIfHasTechAtom(TechAtom.MoriyaRoad))
{
if (aroundGrid.Feature != TerrainFeature.Road &&
(aroundGrid.Feature != TerrainFeature.Mountain || !aroundGrid.HasBuilding())) continue;
}
//Step #2-3 再处理其他常规的联通判断
else
{
if (aroundGrid.Feature != TerrainFeature.Road)
{
continue;
}
}
if (cn.Contains(aroundGrid.Id))continue;
cn.Add(aroundGrid.Id);
li.Enqueue(aroundGrid.Id);
}
}
// 最终确定哪些城市联通加入hashset
foreach (var city in cityList)
{
if (city.IsCapital) continue;
if (!mapData.GetGridDataByCityId(city.Id, out var gridData))continue;
if (!cn.Contains(gridData.Id)) continue;
connectList.Add(city.Id);
}
}
//Step3 处理城市的联通变化
foreach (var city in cityList)
{
//Step #1 先计算经验值的差值
int expDelta = 0;
//Step #2 处理首都的经验差值
if (city.IsCapital)
{
//先减去这之前联通城市的数量(值非0说明之前是原始首都)
expDelta = - city.ConnectExp;
//如果之前有联通属性(说明是其他国家的小弟)要把这1点经验减去
if (city.IsConnectedCapital) expDelta--;
//如果是原始首都,再加上这次联通的城市数量
if (playerData.CradleCityId == city.Id) expDelta += connectList.Count;
}
//Step #3 处理常规城市的经验差值
else
{
//减去之前的联通城市带来的经验(值非0说明之前是原始首都)
expDelta -= city.ConnectExp;
//减去之前联通的经验(撤销联通)
expDelta -= city.IsConnectedCapital ? 1: 0;
//加上现在联通的经验(重新联通)
expDelta += connectList.TryGetValue(city.Id,out var _)? 1 : 0;
}
//Step #4 设置城市的新数值(只有capital=cradle才有联通的概念)
city.IsConnectedCapital = connectList.TryGetValue(city.Id, out var _);
city.ConnectExp = Main.CityLogic.CheckCradleCapital(mapData,playerData,city) ? connectList.Count : 0;
//Step #5 更新city的deltaExp
//Main.CityLogic.CityUpdateExp(mapData, city, expDelta);
Main.CityLogic.GridGiveCityExp_LogicView(mapData,playerData,city.Grid(mapData),city,expDelta);
//Step #6 更新联通动画
if((city.Grid(mapData)?.IsMainMap() ?? false) && (city.Player(mapData)?.IsSelfPlayer()??false)){
var gridRenderer = city.GridRenderer(mapData);
var cityInfoRenderer = city.CityInfoRenderer(mapData);
if (gridRenderer == null || cityInfoRenderer == null)
{
city.SetCityRenderer(mapData);
continue;
}
if (expDelta > 0)
{
for (int i = 0; i < expDelta; i++)
{
var dataExpUp = FragmentDataFactory.Create(FragmentType.CityConnectExpUp,
gridRenderer, cityInfoRenderer,city.Level,city.LevelExp);
PresentationManager.EnqueueTask(new FragmentSequencerTask(FragmentFactory.Create(FragmentType.CityConnectExpUp,dataExpUp)));
}
}
else
{
for (int i = 0; i > expDelta; i--)
{
var dataExpDown = FragmentDataFactory.Create(FragmentType.CityConnectExpDown,
gridRenderer, cityInfoRenderer,city.Level,city.LevelExp);
PresentationManager.EnqueueTask(new FragmentSequencerTask(FragmentFactory.Create(FragmentType.CityConnectExpDown,dataExpDown)));
}
}
}
}
}
// 目前是给kaguya_french专用的用来刷新一个城市领土所有的土地变为kaguya tile
public void SetCityTerritoryGridSp(MapData mapData, CityData cityData,GridSpType spType)
{
var gidList = cityData.Territory.TerritoryArea;
foreach (var gid in gidList)
{
if(!mapData.GridMap.GetGridDataByGid(gid, out var grid))continue;
if (grid.AddSpType(spType,mapData,null))
grid.Renderer(mapData)?.InstantUpdateGrid(true);
}
}
// 目前是给kaguya_french专用的用来刷新一个城市领土所有的土地清除kaguya 的特殊gridSpType
public void ClearCityTerritoryGridSp(MapData mapData, CityData cityData,GridSpType spType)
{
var gidList = cityData.Territory.TerritoryArea;
foreach (var gid in gidList)
{
if(!mapData.GridMap.GetGridDataByGid(gid, out var grid))continue;
if (grid.RemoveSpType(spType,mapData))
grid.Renderer(mapData)?.InstantUpdateGrid(true);
}
}
//--------------------------------------------- Grid相关操作 -------------------------------------------
//这个函数希望接管所有SetGridResource的接口但是暂时没有用上因为目前所有操作结束会更新所有格子的buildinglevel
public bool SetGridResource(MapData map, PlayerData player, GridData gridData, ResourceType resourceType)
{
return true;
}
//-----------------------------------------------------外交相关---------------------------------------------------
//计算p1和p2互相建立大使馆给p1玩家多少钱
public int GetEmbassyCoin(MapData map,PlayerData p1, PlayerData p2)
{
if(!p1.IsSurvival || !p2.IsSurvival) return 0;
p1.GetCountryDiplomacyInfo(p2.Id,out var dip);
if (dip == null) return 0;
//如果是战争 直接return 0
if (dip.DiplomacyState == DiplomacyState.War) return 0;
//如果被敌人占领首都直接return 0
if (Main.PlayerLogic.CheckEnemyOnCaptain(map, p2)) return 0;
p2.GetCountryDiplomacyInfo(p1.Id,out var dip2);
var coin = 0;
if (dip.IsEmbassy) coin += 2;
if (dip2.IsEmbassy) coin += 2;
if (dip.DiplomacyState == DiplomacyState.League) coin *= 2;
return coin;
}
public void TryAddMeetPlayer(MapData map, PlayerData p1, GridData p2)
{
//var unit = p2.Unit(map);
var city = p2.CityOnGrid(map);
if(p2.VisibleUnit(map,p1,out var unit) && unit.Player(map,out var unitPlayer) && !p1.MeetPlayers.Contains(unitPlayer.Id))
AddMeetPlayer(map,p1,unit.Player(map));
if(city != null && city.Player(map) != null && !p1.MeetPlayers.Contains(city.Player(map).Id))
AddMeetPlayer(map,p1,city.Player(map));
}
public void AddMeetPlayer(MapData map, PlayerData p1, PlayerData p2)
{
if (p1.MeetPlayers.Contains(p2.Id)) return;
//已死/已投降的对手不算首遇:避免遇到死者残留城市/单位时弹首遇MajorEvent
if (!p2.IsSurvival) return;
//step #1 meetlist增加
p1.MeetPlayers.Add(p2.Id);
foreach (var kv in p1.PlayerHeroData.HeroTaskDict) kv.Value.OnMeetOtherPlayers(map, p1);
//step #2 加钱
var coin = GetFirstMeetCoin(map,p2);
p1.AddCoin_LogicOnly(coin);
//step #3 如果我方拥有大使馆科技,获得对方原始首都的视野
if (p1.TechTree.CheckIfHasTech(TechType.Diplomacy))
{
if(map.GetGridIdByCityId(p2.CradleCityId, out var cradleGid))
{
using var pooledGidList = THCollectionPool.GetListHandle<uint>(out var gidList);
gidList.Add(cradleGid);
UpdateSight_LogicView(map, p1, gidList);
}
}
//step #5
//如果是玩家出发ui提示。加钱的操作要在ui提示关闭的时候由ui来出发
//只在真实 Main.MapData 上弹,避免 AI 预测/模拟的虚假 MapData 误触发
if (map == Main.MapData && p1 == map.PlayerMap.SelfPlayerData)
{
var announcement = new ShowUIAnnounceMajorEvent { EventType = UIAnnounceMajorEventType.FirstMeet,Param1 = (int)p2.Id,Param2 = coin};
EventManager.Publish(announcement);
//UIManager.Instance.CenterMessageUI.SetCenterMessageShow(UICenterMessageID.MeetNewPlayer,player1,grid);
}
}
//-------- 查询 --------//
public bool HasRoadForUnitOnGrid(MapData mapData, GridData gridData, UnitData unitData)
{
if (mapData == null || gridData == null || unitData == null) return false;
bool hasRoad = gridData.Feature == TerrainFeature.Road;
//水面格(浅海/深海只有Bridge才算有road加成避免LandAndWater单位在带road属性的水格上错误吃到加成
if (gridData.Terrain is TerrainType.ShallowSea or TerrainType.DeepSea
&& gridData.Resource != ResourceType.Bridge)
hasRoad = false;
//这里要特殊处理永远亭的bamboo move
if (unitData.GetSkill(SkillType.BAMBOOMOVE, out var _) && gridData.Vegetation == Vegetation.Trees)
hasRoad = true;
//这里要特殊处理Moriya 的moriyamove
if (unitData.GetSkill(SkillType.MORIYAROAD, out var _)
&& gridData.Feature == TerrainFeature.Mountain
&& gridData.HasBuilding())
hasRoad = true;
return hasRoad;
}
public bool HasRoadForUnit(MapData mapData, GridData gridDataA,GridData gridDataB ,UnitData unitData)
{
if (gridDataA.Resource == ResourceType.Port || gridDataB.Resource == ResourceType.Port) return false;
mapData.GetPlayerIdByUnitId(unitData.Id, out var unitPlayerId);
bool GridAHasPlayer = mapData.GetPlayerDataByTerritoryGridId(gridDataA.Id, out var gridAPlayerData);
bool GridBHasPlayer = mapData.GetPlayerDataByTerritoryGridId(gridDataB.Id, out var gridBPlayerData);
if (GridAHasPlayer && !mapData.SameUnion(gridAPlayerData.Id , unitPlayerId))
return false;
if (GridBHasPlayer && !mapData.SameUnion(gridBPlayerData.Id , unitPlayerId))
return false;
var OriginHasRoad = HasRoadForUnitOnGrid(mapData, gridDataA, unitData);
var TargetHasRoad = HasRoadForUnitOnGrid(mapData, gridDataB, unitData);
return OriginHasRoad && TargetHasRoad;
}
public void ExamineTreasure(MapData mapData, int pid, int uid, Vector2Int tpos) { }
//player[pid]命令unit[uid]挖掘位于grid[tpos]的宝藏
public void Disband(MapData mapData, int pid, int uid) { }
//判断player的首都是否被敌人占领
public bool CheckEnemyOnCaptain(MapData map,PlayerData player)
{
if (!map.GetCapitalCityDataByPlayerId(player.Id, out var city)) return false;
if (!map.GetGridDataByCityId(city.Id, out var grid)) return false;
if (!grid.RealUnit(map, out var unit)) return false;
if (!map.GetPlayerDataByUnitId(unit.Id, out var player2)) return false;
if (player2.Id == player.Id) return false;
return true;
}
//---------------------------------------------- tech相关操作 ------------------------------------
public void ResearchTech(MapData mapData, PlayerData playerData, TechType techType, int cost)
{
//如果已经有了这个科技,直接退出
if (playerData.TechTree.CheckIfHasTech(techType)) return;
if (playerData.PlayerTechPoint >= cost)
{
playerData.SpendTechPoint(cost);
//playerData.PlayerTechPoint -= cost;
}
else
{
playerData.SpendCoin(cost - playerData.PlayerTechPoint);
playerData.SpendTechPoint(playerData.PlayerTechPoint);
//playerData.PlayerTechPoint = 0;
}
playerData.TechTree.LearnTech(techType, playerData);
// Collect 调用
CollectManager.Instance.LearnTechsCollect(mapData, playerData, techType);
//diplomacy科技特殊处理外交情况
if (techType == TechType.Diplomacy)
UpdateDiplomacySight(mapData, playerData);
foreach (var girdData in mapData.GridMap.GridList)
if (mapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(girdData.Id))
girdData.Renderer(mapData)?.InstantUpdateGrid(true);
//竹林驿站科技特殊处理城市联通情况 TODO 临时写法
if (techType == TechType.KaguyaRoad || techType == TechType.KanakoRoads)
Main.PlayerLogic.UpdateAllPlayerConnected(mapData);
var techInfo = Table.Instance.TechDataAssets.GetTechInfo(techType);
foreach (var techAtom in techInfo.TechAtomList)
{
if (!Table.Instance.TechDataAssets.GetTechAtomInfo(techAtom, out var atomInfo)) continue;
if (!atomInfo.IsAddSkill) continue;
//是否给全员增加,还是有条件增加
bool forAll = atomInfo.AddSkillCondition.Count == 0;
using var pooledCondition = THCollectionPool.GetHashSetHandle<UnitFullType>(out var condition);
if (!forAll)
{
condition.UnionWith(atomInfo.AddSkillCondition);
}
foreach (var unit in mapData.UnitMap.UnitList)
{
if (!mapData.CheckUnitIdBelongPlayerId(unit.Id, playerData.Id)) continue;
if (!forAll && !condition.Contains(unit.UnitFullType)) continue;
unit.AddSkill_Legacy(atomInfo.AddSkillType, mapData, true, -1, false, -1, false,
SpecialAddSkillType.Force, 0);
//TODO 森林防御刷新视觉的临时写法
if (atomInfo.AddSkillType is SkillType.FORESTDEFENSE or SkillType.WATERDEFENSE
or SkillType.OCEANDEFENSE or SkillType.MOUNTAINDEFENSE)
unit.Renderer(mapData)?.RenderUpdateUnitDefense();
}
}
}
//player[pid]学习了tid科技
//-------基础操作-------
public void UpdatePlayerStarsPerTurn(MapData mapData, int pid) { }
//playerData点击了EndTurn
public void EndThisTurn(MapData mapData, PlayerData playerData)
{
if(playerData.Id == mapData.PlayerMap.SelfPlayerId)
MapRenderer.Instance.InGameBubbleManager.CloseAllBubble();
playerData.TurnNoAttack++;
}
public int GetPlayerTechPointPerTurn(MapData mapData, uint playerId)
{
int ret = 0;
mapData.PlayerMap.GetPlayerDataByPlayerID(playerId, out var player);
foreach(var cityData in mapData.CityMap.CityList)
if (mapData.CityToPlayerDict[cityData.Id] == playerId
&& !Main.CityLogic.IsCityHeldByEnemyUnit(mapData, cityData))
ret += Main.CityLogic.GetCityTechPointPerTurn(mapData,cityData);
//处理kanako的特殊能力
if (player != null && player.PlayerHeroData.HasHero(GiantType.GermanyKanako))
ret += (int)player.PlayerHeroData.GetHeroLevel(GiantType.GermanyKanako);
return ret;
}
public int GetPlayerCulturePointPerTurn(MapData mapData, uint playerId)
{
int ret = 0;
foreach(var cityData in mapData.CityMap.CityList)
if (mapData.CityToPlayerDict[cityData.Id] == playerId
&& !Main.CityLogic.IsCityHeldByEnemyUnit(mapData, cityData))
ret += Main.CityLogic.GetCityCulturePointPerTurn(mapData,cityData);
return ret;
}
public int GetPlayerMountainPoint(MapData mapData, uint playerId)
{
int ret = 0;
if (!mapData.PlayerMap.GetPlayerDataByPlayerID(playerId, out var player)) return ret;
if (!player.TechTree.CheckIfHasTechAtom(TechAtom.CreateMountain)) return ret;
ret += 1;
int aca = 0;
int pro = 0;
foreach (var g in mapData.GridMap.GridList)
{
if (g.Player(mapData) != player) continue;
if (g.Resource == ResourceType.Academy) aca++;
if (g.Feature == TerrainFeature.Mountain) pro++;
}
if (player.TechTree.CheckIfHasTechAtom(TechAtom.AcademyCreateMountain)) ret += aca;
if(player.TechTree.CheckIfHasTechAtom(TechAtom.CreateMountainPro)) ret += pro / 3;
//处理kanako的特殊能力
if (player.PlayerHeroData.HasHero(GiantType.GermanyKanako))
ret += (int)player.PlayerHeroData.GetHeroLevel(GiantType.GermanyKanako) + 1;
return ret;
}
public int GetPlayerCoinPerTurn(MapData mapData, uint playerId)
{
int ret = 0;
foreach(var cityData in mapData.CityMap.CityList)
if (mapData.CityToPlayerDict[cityData.Id] == playerId
&& !Main.CityLogic.IsCityHeldByEnemyUnit(mapData, cityData))
ret += Main.CityLogic.GetCityCoinPerTurn(mapData,cityData);
if (!mapData.PlayerMap.GetPlayerDataByPlayerID(playerId, out var player))
{
LogSystem.LogError("PlayerLogic Get PlayerStarsPerTurn can't find player by playerId");
return ret;
}
//产业科技带来的techpoint 转化为金币的能力
if (player.TechTree.CheckIfHasTechAtom(TechAtom.TechIndustry))
ret += GetPlayerTechPointPerTurn(mapData, playerId) / 2;
//计算外交收入
foreach (var t in player.MeetPlayers)
{
if (!mapData.PlayerMap.GetPlayerDataByPlayerID(t, out var tplayer)) continue;
if (!tplayer.IsSurvival) continue;
ret += GetEmbassyCoin(mapData,player, tplayer);
}
//文化值转化为金币
if (player.CanConvertCultureToGold(mapData))
ret += GetPlayerCulturePointPerTurn(mapData, playerId);
ret += GetPlayerKingHeroCoinPerTurn(mapData, player);
return ret;
}
public int GetPlayerKingHeroCoinPerTurn(MapData mapData, PlayerData player)
{
if (mapData == null || player == null) return 0;
var kingLevel = player.PlayerHeroData.GetMaxHeroLevelByChessType(ChessType.King);
if (kingLevel == 0) return 0;
return GetPlayerKingCoinSourceCount(mapData, player) * (int)kingLevel;
}
public int GetPlayerKingCoinSourceCount(MapData mapData, PlayerData player)
{
if (mapData == null || player == null) return 0;
int count = 0;
if (mapData.GetCapitalCityDataByPlayerId(player.Id, out var capitalCity)
&& mapData.GetPlayerDataByCityId(capitalCity.Id, out var capitalOwner)
&& capitalOwner.Id == player.Id)
count++;
foreach (var otherPlayer in mapData.PlayerMap.PlayerDataList)
{
if (otherPlayer.Id == player.Id) continue;
if (!mapData.CityMap.GetCityById(otherPlayer.CradleCityId, out var city)) continue;
if (!mapData.GetPlayerDataByCityId(city.Id, out var owner)) continue;
if (owner.Id == player.Id) count++;
}
return count;
}
//进入回合结算 #ono #nextturn #start #startturn
public void StartNextTurn(MapData mapData, PlayerData playerData)
{
//Debug.Log($"[Bubble] StartNextTurn called for player {playerData.Id}, Turn={playerData.Turn}");
if (mapData != Main.MapData)
{
LogSystem.LogError("PlayerLogic StartNextTurn can't start next turn,mapData is not Real MAPDATA !!!");
return;
}
//每回合提示
//Debug.Log($"[Bubble] Checking SelfPlayerId: {mapData.PlayerMap.SelfPlayerId}, playerId: {playerData.Id}, match={playerData.Id == mapData.PlayerMap.SelfPlayerId}");
if (playerData.Id == mapData.PlayerMap.SelfPlayerId)
{
//Debug.Log("[Bubble] Calling TurnStartSetBubble...");
MapRenderer.Instance.InGameBubbleManager.TurnStartSetBubble(mapData,playerData);
//如果不是第0回合要显示回合开始提示
if (playerData.Turn != 0)
{
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateTurn });
}
//UIManager.Instance.TopBarUI.SetShowTurnHint((int)playerData.Turn + 1);
}
//加钱/科技点/文化值的模块
//如果是0回合不用放动画 不用加钱 ,直接return
if (playerData.Turn != 0) {
//暂时在这里触发金币动画。实际上不应该的TODO 迭代这里的动画触发逻辑
//增加科技点
playerData.AddTechPoint(GetPlayerTechPointPerTurn(mapData, playerData.Id));
//增加文化点
playerData.AddCulturePoint(GetPlayerCulturePointPerTurn(mapData, playerData.Id));
//增加英雄exp
var allGrid = mapData.GetPlayerTerritoryGridIdSet(playerData.Id);
foreach (var gid in allGrid)
{
if (!mapData.GridMap.GetGridDataByGid(gid, out var grid))
{
LogSystem.LogError("Can't Find grid by gid in PlayerLogic StartNextTurn");
continue;
}
if (!Table.Instance.GridAndResourceDataAssets.GetResourceInfo(grid.Resource, out var info))
{
LogSystem.LogError("Can't Find Resource Info for Resource Type in PlayerLogic StartNextTurn");
continue;
}
if(info.ChessType != ChessType.None)
{
uint x = (uint)info.ChessType;
playerData.giantExp[x] += grid.buildingLevel;
}
// Temple每2回合自动升1级最高5级
if (grid.Resource is ResourceType.Temple or ResourceType.KingTemple
or ResourceType.ForestTemple or ResourceType.WaterTemple
or ResourceType.MountainTemple)
{
if (grid.buildingLevel < 5
&& playerData.Turn >= grid.BuildTime + 2
&& (playerData.Turn - grid.BuildTime) % 2 == 0)
{
grid.buildingLevel++;
Main.CityLogic.UpdateGrid_ViewOnly(mapData, grid);
}
}
}
var addCoin = GetPlayerCoinPerTurn(mapData, playerData.Id);
//Cloak 偷金币:本回合开始时,扫全图所有城市的 CITYSTOLEN 标记,
//偷家是当前玩家则把 StolenAmount 累加到 addCoin
foreach (var city in mapData.CityMap.CityList)
{
if (!city.GetSkill(SkillType.CITYSTOLEN, out var stolenSkill)) continue;
if (stolenSkill is not CityStolenSkill cs) continue;
if (cs.StealerPlayerId != playerData.Id) continue;
addCoin += cs.StolenAmount;
}
playerData.AddCoin_LogicOnly(addCoin);
//如果是真人玩家,要播放动画
if (playerData.IsSelfPlayer())
{
mapData.GetCapitalCityDataByPlayerId(playerData.Id, out var captain);
//否则一边播放动画一边1块钱1块钱的增加wealth
foreach (var city in mapData.CityMap.CityList)
{
if (!mapData.GetPlayerDataByCityId(city.Id, out var tmpPlayer)) continue;
//要么是我方城市产金币,要么是对方的原始首都给的外交金币
if (tmpPlayer.Id != playerData.Id && !Main.CityLogic.CheckCradleCapital(mapData,tmpPlayer,city))continue;
if (!mapData.GetGridDataByCityId(city.Id,out var grid)) continue;
var startPos = Table.Instance.GridToWorld(grid);
//计算该城市获得的金币数量
var money = 0;
if (tmpPlayer.Id == playerData.Id)
{
//被敌方单位站住的城市本回合不产金币,动画跳过
if (Main.CityLogic.IsCityHeldByEnemyUnit(mapData, city)) continue;
money = Main.CityLogic.GetCityCoinPerTurn(mapData, city);
}
else
money = GetEmbassyCoin(mapData, playerData, tmpPlayer);
if(money <= 0)continue;
MapRenderer.Instance.ProjectileManager.CreateCoinProjectile(startPos,money);
}
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateCoin });
}
}
//处理收到的外交请求,必须是真人玩家
if (playerData.Id == mapData.PlayerMap.SelfPlayerId)
{
foreach (var player2Id in playerData.MeetPlayers)
if(mapData.PlayerMap.GetPlayerDataByPlayerID(player2Id, out var player2))
{
if(player2.Id == playerData.Id)continue;
if (!player2.IsSurvival) continue;
playerData.GetCountryDiplomacyInfo(player2.Id, out var dipInfo);
//找到一个联盟请求弹出ui窗口
if (dipInfo.IsLeagueRequest)
{
EventManager.Publish(new ShowUIInteractionDiplomacyOfferAlly(){PlayerId = player2.Id});
}
}
}
}
//-------------------------------- 视野相关 ---------------------------------------
//查询视野
public bool CheckOneTowerInSight(MapData mapData, PlayerData player)
{
return mapData.GridMap.GetGridDataByPos(0, 0, out var g1) && player.Sight.CheckIsInSight(g1.Id)
|| mapData.GridMap.GetGridDataByPos(0, (int)mapData.MapConfig.Height - 1, out var g2) && player.Sight.CheckIsInSight(g2.Id)
|| mapData.GridMap.GetGridDataByPos((int)mapData.MapConfig.Width - 1, 0, out var g3) && player.Sight.CheckIsInSight(g3.Id)
|| mapData.GridMap.GetGridDataByPos((int)mapData.MapConfig.Width - 1, (int)mapData.MapConfig.Height - 1, out var g4) && player.Sight.CheckIsInSight(g4.Id);
}
public bool CheckAllTowerInSight(MapData mapData, PlayerData player)
{
return mapData.GridMap.GetGridDataByPos(0, 0, out var g1) && player.Sight.CheckIsInSight(g1.Id)
&& mapData.GridMap.GetGridDataByPos(0, (int)mapData.MapConfig.Height - 1, out var g2) && player.Sight.CheckIsInSight(g2.Id)
&& mapData.GridMap.GetGridDataByPos((int)mapData.MapConfig.Width - 1, 0, out var g3) && player.Sight.CheckIsInSight(g3.Id)
&& mapData.GridMap.GetGridDataByPos((int)mapData.MapConfig.Width - 1, (int)mapData.MapConfig.Height - 1, out var g4) && player.Sight.CheckIsInSight(g4.Id);
}
public bool GetAllSight()
{
var selfp = Main.MapData.PlayerMap.SelfPlayerData;
foreach (var t in Main.MapData.GridMap.GridList)
{
if (selfp.Sight.SightGidSet.Add(t.Id))
{
t.Renderer(Main.MapData)?.FirstShowGrid();
/*t.Renderer(Main.MapData)?.SetUpdateGrid();
if(t.CityOnGrid(Main.MapData,out var city))
t.Renderer(Main.MapData)?.SetUpdateCityBuilding(city.Level,
Table.Instance.TransCivIdToCivEnum(city.Player(Main.MapData).PlayerCivId));*/
}
}
return true;
}
//TODO 目前UpdateSight在MapRenderer初始化之前就使用了。要处理
// 更新视野 ,viewNextFrame表示这次的动画是否排在下一帧才注册dur表示更新雾效的时候间隔时间是0.01s(开局用)还是0.05f(默认)
public int UpdateSight_LogicView(MapData mapData,PlayerData playerData, List<uint> gidList,bool viewNextFrame = false,float dur = 0.05f)
{
if (playerData == null)
{
Debug.Log("playerData Null !!!!");
return 0;
}
var count = 0;
foreach (var id in gidList)
{
if (!playerData.Sight.SightGidSet.Add(id)) continue;
count++;
if (!mapData.GridMap.GetGridDataByGid(id, out var gridData)) continue;
if (!mapData.GetCapitalCityDataByPlayerId(playerData.Id, out var city)) continue;
//如果是真人玩家,加入地块被新显示的播放队列
if (Main.MapData == mapData && playerData.IsSelfPlayer())
{
var data = FragmentDataFactory.Create(FragmentType.GridUpdate, GridUpdateType.FogDisappear, gridData.Renderer(mapData),dur);
PresentationManager.EnqueueTask(new FragmentSequencerTask(FragmentFactory.Create(FragmentType.GridUpdate, data)),viewNextFrame);
}
//尝试判断gridData有没有带来新的meetplayerList
TryAddMeetPlayer(mapData, playerData, gridData);
//处理发现tower的情况
if (gridData.Resource == ResourceType.Tower)
{
Main.CityLogic.GridGiveCityExp_LogicView(mapData,playerData,gridData,city,1);
//更新奇观情况,可能弹出奇观通知
UpdateWonder(mapData,playerData);
}
}
//更新视野后,要更新联通情况
Main.PlayerLogic.UpdateCityConnect(mapData,playerData);
return count;
}
//ViewNextFrame表示View相关的内容要放在下一帧执行
public int UpdateSightByRadius_LogicView(MapData mapData, PlayerData playerData, GridData gridData, int radius,bool ViewNextFrame = false)
{
return UpdateSight_LogicView(mapData,playerData,mapData.GridMap.GetAroundGridIdList(radius,gridData,true),ViewNextFrame);
}
public void UpdateAllTeammateCapitalSight(MapData mapData)
{
if (mapData?.PlayerMap?.PlayerDataList == null) return;
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
if (player == null || !player.IsSurvival) continue;
using var pooledGidList = THCollectionPool.GetListHandle<uint>(out var gidList);
foreach (var teammate in mapData.PlayerMap.PlayerDataList)
{
if (teammate == null || !teammate.IsSurvival || teammate.Id == player.Id) continue;
if (!CheckIsTeammate(mapData, player, teammate)) continue;
if (!mapData.GetCapitalCityDataByPlayerId(teammate.Id, out var capital)) continue;
if (!mapData.GetGridIdByCityId(capital.Id, out var capitalGid)) continue;
if (player.Sight.CheckIsInSight(capitalGid)) continue;
gidList.Add(capitalGid);
}
if (gidList.Count > 0)
UpdateSight_LogicView(mapData, player, gidList);
}
}
private static bool CheckIsTeammate(MapData mapData, PlayerData player, PlayerData targetPlayer)
{
if (mapData?.MapConfig != null && mapData.MapConfig.ArePlayersInSameTeam(player.Id, targetPlayer.Id))
return true;
if (player.GetCountryDiplomacyInfo(targetPlayer.Id, out var selfToTarget) && selfToTarget.IsTeammate)
return true;
return targetPlayer.GetCountryDiplomacyInfo(player.Id, out var targetToSelf) && targetToSelf.IsTeammate;
}
public void DebugGetAllSight(MapData mapData, PlayerData playerData)
{
using var pooledGidList = THCollectionPool.GetListHandle<uint>(out var gidList);
foreach(var gridData in mapData.GridMap.GridList)
gidList.Add(gridData.Id);
UpdateSight_LogicView(mapData,playerData,gidList);
}
//如果我方拥有大使馆科技更新所有meetlist的cradlecity位置
public void UpdateDiplomacySight(MapData map, PlayerData player)
{
while (true)
{
var count = player.MeetPlayers.Count;
for (int i = 0; i < count; i++)
{
var playerId = player.MeetPlayers[i];
if (player.Id == playerId) continue;
if (!map.PlayerMap.GetPlayerDataByPlayerID(playerId, out var player2)) continue;
if (!map.GetGridDataByCityId(player2.CradleCityId, out var grid)) continue;
using var pooledGidList = THCollectionPool.GetListHandle<uint>(out var gidList);
gidList.Add(grid.Id);
UpdateSight_LogicView(map, player, gidList);
}
if (count != player.MeetPlayers.Count) continue;
break;
}
}
//返回某个玩家学习某个科技的真实价格
public int GetTechCost(MapData mapData,PlayerData playerData,TechType techType)
{
var techInfo = Table.Instance.TechDataAssets.GetTechInfo(techType);
return techInfo.CostLevel * mapData.GetCityCount(playerData.Id) + 4;
}
//更新一个mapData里所有Player的Score
public void CalcAllPlayerScore(MapData mapData)
{
if (mapData == null)
return;
// 先清空
foreach (var player in mapData.PlayerMap.PlayerDataList) player.PlayerScore = 0;
//记录格子得分(视野)
foreach (var player in mapData.PlayerMap.PlayerDataList) player.PlayerScore += player.Sight.SightGidSet.Count * 5;
// 记录格子得分(领土)
foreach (var city in mapData.CityMap.CityList)
{
if (!mapData.GetPlayerDataByCityId(city.Id, out var player)) continue;
player.PlayerScore += city.Territory.TerritoryArea.Count * 20;
player.PlayerScore += city.Level * 50 + city.LevelExp * 5;
foreach(var gid in city.Territory.TerritoryArea)
if (mapData.GridMap.GetGridDataByGid(gid, out var grid))
{
if (grid.Resource is ResourceType.Temple or ResourceType.ForestTemple or ResourceType.MountainTemple or ResourceType.WaterTemple)
player.PlayerScore += 50 + 50 * grid.buildingLevel;
}
}
// 小兵得分Cost*5
foreach (var unit in mapData.UnitMap.UnitList)
{
if (!unit.IsAlive()) continue;
if (!mapData.GetPlayerDataByUnitId(unit.Id, out var player)) continue;
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unit.UnitType, unit.GiantType,unit.UnitLevel, out var info)) continue;
player.PlayerScore += info.Cost * 5;
}
// 奇观得分(每完成一个+400
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
foreach (var kv in player.Wonder.WonderInfoDict)
{
if (kv.Value == WonderState.FINISH_BUILD)
player.PlayerScore += 400;
}
// 科技得分100 * 等级)
player.PlayerScore += player.TechTree.GetScore();
}
}
// 设置外交关系
public void SetDiplomacyLeague(MapData map, PlayerData originPlayer, PlayerData targetPlayer, DiplomacyState state)
{
if (!originPlayer.GetCountryDiplomacyInfo(targetPlayer.Id, out var player1ToPlayer2)) return;
if (!targetPlayer.GetCountryDiplomacyInfo(originPlayer.Id, out var player2ToPlayer1)) return;
if ((player1ToPlayer2.IsTeammate || player2ToPlayer1.IsTeammate) && state != DiplomacyState.League)
{
player1ToPlayer2.IsTeammate = true;
player2ToPlayer1.IsTeammate = true;
player1ToPlayer2.DiplomacyState = DiplomacyState.League;
player2ToPlayer1.DiplomacyState = DiplomacyState.League;
player1ToPlayer2.IsLeagueRequest = false;
player2ToPlayer1.IsLeagueRequest = false;
player1ToPlayer2.IsLeagueRupture = false;
player2ToPlayer1.IsLeagueRupture = false;
return;
}
player1ToPlayer2.DiplomacyState = state;
player2ToPlayer1.DiplomacyState = state;
if (state == DiplomacyState.War)
{
if (player1ToPlayer2.IsEmbassy)
{
player1ToPlayer2.IsEmbassy = false;
//更新player2的首都cityinfo
if (map.GetCapitalCityDataByPlayerId(targetPlayer.Id, out var city2))
city2.SetCityRenderer(map);
//如果有一方是真人玩家要发布UI通知
if (map == Main.MapData
&& (originPlayer.Id == Main.MapData.PlayerMap.SelfPlayerId ||
targetPlayer.Id == Main.MapData.PlayerMap.SelfPlayerId))
{
var announce = new ShowUIAnnounceDiplomacy(){PlayerActionType = PlayerActionType.BreakEmbassy,PlayerId = originPlayer.Id,TargetPlayerId = targetPlayer.Id};
EventManager.Publish(announce);
}
}
if (player2ToPlayer1.IsEmbassy)
{
player2ToPlayer1.IsEmbassy = false;
//更新player1的首都cityinfo
if (map.GetCapitalCityDataByPlayerId(originPlayer.Id, out var city1))
city1.SetCityRenderer(map);
//如果有一方是真人玩家要发布UI通知
if (map == Main.MapData
&& (originPlayer.Id == Main.MapData.PlayerMap.SelfPlayerId ||
targetPlayer.Id == Main.MapData.PlayerMap.SelfPlayerId))
{
var announce = new ShowUIAnnounceDiplomacy(){PlayerActionType = PlayerActionType.BreakEmbassy,PlayerId = targetPlayer.Id,TargetPlayerId = originPlayer.Id};
EventManager.Publish(announce);
}
}
}
}
}
}