2031 lines
79 KiB
C#
2031 lines
79 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description:
|
||
* @Date: 2025年04月03日 星期四 11:04:31
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using Logic.Action;
|
||
using Logic.AI;
|
||
using Logic.Audio;
|
||
using Logic.Pool;
|
||
using MemoryPack;
|
||
using NUnit.Framework;
|
||
using TH1_Core.Events;
|
||
using TH1_Core.Managers;
|
||
using TH1_Logic.Core;
|
||
using TH1_Logic.HeroTask;
|
||
using TH1_Logic.MatchConfig;
|
||
using TH1_Presentation.Sequencer.Task;
|
||
using TH1Renderer;
|
||
using UnityEngine;
|
||
|
||
|
||
namespace RuntimeData
|
||
{
|
||
// 奇观类型枚举
|
||
public enum WonderTypeEnum
|
||
{
|
||
None,
|
||
PEACE,
|
||
KNOWLEDGE,
|
||
TRADE,
|
||
WEALTH,
|
||
POWER,
|
||
PARK,
|
||
EYE
|
||
}
|
||
|
||
|
||
// 奇观状态枚举
|
||
public enum WonderState
|
||
{
|
||
NO_HINT,
|
||
HAVE_HINT,
|
||
FINISH_NOT_BUILD,
|
||
FINISH_BUILD,
|
||
Max
|
||
}
|
||
|
||
|
||
// 外交关系枚举
|
||
public enum DiplomacyState
|
||
{
|
||
NoDiplomacy, // 未建交
|
||
Neutral, // 中立的
|
||
League, // 联盟
|
||
War, // 战争
|
||
LeagueRupture, // 联盟破裂
|
||
}
|
||
|
||
|
||
// 好感状态枚举
|
||
public enum FeelingState
|
||
{
|
||
Trust,
|
||
Appreciate,
|
||
Indifferent,
|
||
Suspicion,
|
||
Terrible,
|
||
}
|
||
|
||
|
||
// 好感策略枚举
|
||
public enum FeelingStrategy
|
||
{
|
||
Wise,
|
||
Charming,
|
||
Peaceful,
|
||
Diplomatic,
|
||
Powerful,
|
||
Brave,
|
||
Violent,
|
||
Threatening,
|
||
Weak,
|
||
Annoying,
|
||
Foolish,
|
||
Intrusive,
|
||
Dominative,
|
||
}
|
||
|
||
|
||
[MemoryPackable]
|
||
public partial class PlayerMapData
|
||
{
|
||
public List<PlayerData> PlayerDataList;
|
||
[MemoryPackIgnore]
|
||
public uint SelfPlayerId;
|
||
[MemoryPackIgnore]
|
||
public PlayerData SelfPlayerData => GetPlayerData(SelfPlayerId);
|
||
private Dictionary<uint, PlayerData> _playerDataDict;
|
||
|
||
|
||
// 无数据初始化
|
||
[MemoryPackConstructor]
|
||
public PlayerMapData()
|
||
{
|
||
PlayerDataList = new List<PlayerData>();
|
||
_playerDataDict = new Dictionary<uint, PlayerData>();
|
||
}
|
||
|
||
// 配置初始化
|
||
public PlayerMapData(MapData map, MapIdGenerator idGenerator, NetMode netMode)
|
||
{
|
||
PlayerDataList = new List<PlayerData>();
|
||
_playerDataDict = new Dictionary<uint, PlayerData>();
|
||
|
||
if (netMode == NetMode.Single)
|
||
{
|
||
map.MapConfig.EnsurePlayerSlots(NetMode.Single);
|
||
for (int i = 0; i < map.MapConfig.MultiCivs.Count; i++)
|
||
{
|
||
var slot = map.MapConfig.MultiCivs[i];
|
||
var player = new PlayerData(slot.CivId, slot.ForceId, idGenerator);
|
||
PlayerDataList.Add(player);
|
||
_playerDataDict[player.Id] = player;
|
||
slot.PlayerId = player.Id;
|
||
}
|
||
}
|
||
if (netMode == NetMode.Multi)
|
||
{
|
||
map.MapConfig.EnsurePlayerSlots(NetMode.Multi);
|
||
// 这里 civList 要包含所有 civId (civId = civEnum - 1,目前 17 个 civ → 0..16)
|
||
var civList = new List<uint>();
|
||
for (int i = 0; i < 17; i++) civList.Add((uint)i);
|
||
|
||
foreach (var slot in map.MapConfig.MultiCivs)
|
||
{
|
||
var civId = slot.CivId;
|
||
var forceId = slot.ForceId;
|
||
if (slot.MemberId == 0 && slot.IsAI && !slot.IsCivFixed)
|
||
{
|
||
var idx = civList.Count > 0 ? UnityEngine.Random.Range(0, civList.Count) : 0;
|
||
civId = civList.Count > 0 ? civList[idx] : 0u;
|
||
forceId = civId;
|
||
slot.CivId = civId;
|
||
slot.ForceId = forceId;
|
||
}
|
||
civList.Remove(civId);
|
||
var player = new PlayerData(civId, forceId, idGenerator);
|
||
PlayerDataList.Add(player);
|
||
_playerDataDict[player.Id] = player;
|
||
slot.PlayerId = player.Id;
|
||
}
|
||
}
|
||
|
||
if (PlayerDataList.Count > 0) SelfPlayerId = PlayerDataList[0].Id;
|
||
foreach (var self in PlayerDataList)
|
||
{
|
||
foreach (var target in PlayerDataList)
|
||
{
|
||
if (self.Id == target.Id) continue;
|
||
self.DiplomacyData.AddCountryDiplomacyInfo(target.Id);
|
||
}
|
||
}
|
||
}
|
||
|
||
public PlayerMapData(PlayerMapData copyData)
|
||
{
|
||
PlayerDataList = new List<PlayerData>();
|
||
_playerDataDict = new Dictionary<uint, PlayerData>();
|
||
SelfPlayerId = copyData.SelfPlayerId;
|
||
|
||
foreach (var copyPlayer in copyData.PlayerDataList)
|
||
{
|
||
var player = new PlayerData(copyPlayer);
|
||
PlayerDataList.Add(player);
|
||
_playerDataDict[player.Id] = player;
|
||
}
|
||
}
|
||
|
||
public void DeepCopy(PlayerMapData copyData)
|
||
{
|
||
_playerDataDict.Clear();
|
||
SelfPlayerId = copyData.SelfPlayerId;
|
||
|
||
for (int i = 0; i < copyData.PlayerDataList.Count; i++)
|
||
{
|
||
var copyPlayer = copyData.PlayerDataList[i];
|
||
if (i >= PlayerDataList.Count)
|
||
{
|
||
var player = new PlayerData(copyPlayer);
|
||
PlayerDataList.Add(player);
|
||
_playerDataDict[player.Id] = player;
|
||
}
|
||
else
|
||
{
|
||
PlayerDataList[i].DeepCopy(copyPlayer);
|
||
_playerDataDict[PlayerDataList[i].Id] = PlayerDataList[i];
|
||
}
|
||
}
|
||
for (int i = PlayerDataList.Count - 1; i >= copyData.PlayerDataList.Count; i--)
|
||
{
|
||
_playerDataDict.Remove(PlayerDataList[i].Id);
|
||
PlayerDataList.RemoveAt(i);
|
||
}
|
||
}
|
||
|
||
// 裁剪玩家列表到指定数量(移除末尾多余的玩家)
|
||
public void TrimPlayerCount(int count)
|
||
{
|
||
for (int i = PlayerDataList.Count - 1; i >= count; i--)
|
||
{
|
||
_playerDataDict.Remove(PlayerDataList[i].Id);
|
||
PlayerDataList.RemoveAt(i);
|
||
}
|
||
}
|
||
|
||
// MemoryPack 反序列化之后的后处理
|
||
[MemoryPackOnDeserialized]
|
||
public void OnAfterMemoryPackDeserialize()
|
||
{
|
||
_playerDataDict ??= new Dictionary<uint, PlayerData>();
|
||
_playerDataDict.Clear();
|
||
foreach (var player in PlayerDataList) _playerDataDict[player.Id] = player;
|
||
}
|
||
|
||
// 根据 pid 获取玩家数据
|
||
public bool GetPlayerDataByPlayerID(uint pid, out PlayerData playerData)
|
||
{
|
||
return _playerDataDict.TryGetValue(pid, out playerData);
|
||
}
|
||
|
||
public PlayerData GetPlayerData(uint id)
|
||
{
|
||
if (_playerDataDict.TryGetValue(id, out var playerData))
|
||
{
|
||
return playerData;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public void OnTurnStart(MapData map, PlayerData player)
|
||
{
|
||
Main.PlayerLogic.StartNextTurn(map, player);
|
||
player.OnTurnStart(map);
|
||
}
|
||
|
||
public void OnTurnEnd(MapData map, PlayerData player)
|
||
{
|
||
Main.PlayerLogic.EndThisTurn(map, player);
|
||
player.OnTurnEnd(map);
|
||
}
|
||
|
||
public int GetMaxPlayerScore()
|
||
{
|
||
int maxScore = -1;
|
||
foreach (var player in PlayerDataList)
|
||
{
|
||
if (!player.IsSurvival) continue;
|
||
if (player.PlayerScore <= maxScore) continue;
|
||
maxScore = player.PlayerScore;
|
||
}
|
||
return maxScore;
|
||
}
|
||
}
|
||
|
||
|
||
[MemoryPackable]
|
||
public partial class PlayerData : IdentifierBase
|
||
{
|
||
// [Fix 2026/05/22] RefreshDiplomacyState 视觉刷新的开关。
|
||
// true: 外交变化时把"对方单位/城市视觉刷新"入队 PresentationManager,等当前动画队列播完再执行。
|
||
// 修复 Explorer 一瞬间所有迷雾下单位提前显示的 bug。
|
||
// false: 立即同步执行(旧行为)。如果新逻辑出问题,把这一行改为 false 即可一键回退。
|
||
private const bool USE_QUEUED_DIPLOMACY_REFRESH = true;
|
||
|
||
// 文明 id, 阵营 id, 摇篮城市 id
|
||
public uint Turn;
|
||
public CivEnum CivEnum => Table.Instance.TransCivIdToCivEnum(PlayerCivId);
|
||
public ForceEnum ForceEnum => Table.Instance.TransForceIdToForceEnum(PlayerForceId);
|
||
public Empire Empire => new Empire(CivEnum, ForceEnum);
|
||
public uint PlayerCivId;
|
||
public uint PlayerForceId;
|
||
public uint CradleCityId;
|
||
|
||
// 是否存活
|
||
public bool Alive;
|
||
// 财富总额
|
||
public int PlayerCoin => _playerCoin;
|
||
public int _playerCoin;
|
||
public int PlayerTechPoint => _playerTechPoint;
|
||
public int _playerTechPoint;
|
||
|
||
//造山点(主要给守矢使用)
|
||
public int PlayerMountainPointUsed;
|
||
//分数
|
||
public int PlayerScore;
|
||
// 视野
|
||
public MapSightData Sight;
|
||
// 科技
|
||
public TechTreeData TechTree;
|
||
// 奇观
|
||
public WonderData Wonder;
|
||
// 外交
|
||
public DiplomacyData DiplomacyData;
|
||
//英雄
|
||
public PlayerHeroData PlayerHeroData;
|
||
//命名器
|
||
public PlayerNamerData PlayerNamerData;
|
||
|
||
// 用于记录已经连续几个 turn 没有进行攻击
|
||
public int TurnNoAttack;
|
||
// 用于记录累计击杀多少 unit
|
||
public int TotalKill;
|
||
// 英雄
|
||
private bool _heroDictInit;
|
||
|
||
|
||
//记录伟人的累计经验,下标直接从ChessType的Enum对应的uint转过来
|
||
public int[] giantExp = new int[6];
|
||
//记录伟人的复活冷却cd惩罚
|
||
public int[] giantPenalty = new int[6];
|
||
public List<uint> MeetPlayers;
|
||
public List<uint> CurAttackPlayers;
|
||
public List<uint> LastAttackPlayers;
|
||
|
||
//用于记录是否死亡,是否要centMessage来宣告死亡信息
|
||
public bool DieMark = false;
|
||
//用于记录当前是否正在playing,主要是视觉那边使用
|
||
public bool IsPlaying;
|
||
|
||
// Moment
|
||
public MomentData MomentData;
|
||
|
||
//PlayerCulture
|
||
public PlayerCultureInfo PlayerCultureInfo;
|
||
|
||
// 是否投降
|
||
public bool IsSurrender;
|
||
// 玩家是否存活要用这条来判断
|
||
public bool IsSurvival => !IsSurrender && Alive;
|
||
|
||
// 复用的临时集合,避免每回合外交计算GC
|
||
[MemoryPackIgnore] private HashSet<uint> _tmpOriginCityBuf;
|
||
[MemoryPackIgnore] private List<CityData> _tmpCityListBuf;
|
||
[MemoryPackIgnore] private HashSet<CityData> _tmpCitySetBuf;
|
||
[MemoryPackIgnore] private HashSet<uint> _tmpTerritoryGridIdBuf;
|
||
[MemoryPackIgnore] private HashSet<uint> _tmpSelfTerritoryInfluenceGridIdBuf;
|
||
[MemoryPackIgnore] private Dictionary<uint, float> _tmpPlayerMilitaryScoreBuf;
|
||
[MemoryPackIgnore] private Dictionary<uint, List<uint>> _tmpPlayerUnitGridIdListBuf;
|
||
[MemoryPackIgnore] private Dictionary<uint, HashSet<uint>> _tmpPlayerTerritoryGridIdSetBuf;
|
||
[MemoryPackIgnore] private Dictionary<uint, DiplomacyState> _tmpSelfToOtherDiplomacyStateBuf;
|
||
|
||
// 无参数初始化
|
||
[MemoryPackConstructor]
|
||
public PlayerData()
|
||
{
|
||
Sight = new MapSightData();
|
||
TechTree = new TechTreeData();
|
||
Wonder = new WonderData();
|
||
DiplomacyData = new DiplomacyData();
|
||
MeetPlayers = new List<uint>();
|
||
CurAttackPlayers = new List<uint>();
|
||
LastAttackPlayers = new List<uint>();
|
||
PlayerNamerData = new PlayerNamerData();
|
||
MomentData = new MomentData();
|
||
PlayerCultureInfo = new PlayerCultureInfo();
|
||
}
|
||
|
||
// 带 ID 初始化
|
||
public PlayerData(uint civId, uint forceId, MapIdGenerator idGenerator)
|
||
{
|
||
Turn = 0;
|
||
Id = idGenerator.GeneratorId();
|
||
if(Table.Instance.PlayerDataAssets.GetPlayerInfoByCivId(civId,forceId,out var info))
|
||
TechTree = new TechTreeData(this, info.TechStart);
|
||
else
|
||
TechTree = new TechTreeData();
|
||
Sight = new MapSightData();
|
||
|
||
Wonder = new WonderData();
|
||
DiplomacyData = new DiplomacyData();
|
||
PlayerHeroData = new PlayerHeroData();
|
||
CurAttackPlayers = new List<uint>();
|
||
LastAttackPlayers = new List<uint>();
|
||
PlayerNamerData = new PlayerNamerData();
|
||
MomentData = new MomentData();
|
||
MomentData.InitItems();
|
||
PlayerCultureInfo = new PlayerCultureInfo();
|
||
MeetPlayers = new List<uint>();
|
||
MeetPlayers.Add(Id);
|
||
InitData(civId,forceId);
|
||
}
|
||
|
||
public PlayerData(PlayerData copyData)
|
||
{
|
||
Turn = copyData.Turn;
|
||
Id = copyData.Id;
|
||
PlayerCivId = copyData.PlayerCivId;
|
||
PlayerForceId = copyData.PlayerForceId;
|
||
CradleCityId = copyData.CradleCityId;
|
||
Alive = copyData.Alive;
|
||
IsSurrender = copyData.IsSurrender;
|
||
_playerCoin = copyData._playerCoin;
|
||
_playerTechPoint = copyData._playerTechPoint;
|
||
PlayerMountainPointUsed = copyData.PlayerMountainPointUsed;
|
||
|
||
Sight = new MapSightData(copyData.Sight);
|
||
TechTree = new TechTreeData(copyData.TechTree);
|
||
Wonder = new WonderData(copyData.Wonder);
|
||
DiplomacyData = new DiplomacyData(copyData.DiplomacyData);
|
||
PlayerHeroData = new PlayerHeroData(copyData.PlayerHeroData);
|
||
PlayerNamerData = new PlayerNamerData(copyData.PlayerNamerData);
|
||
MomentData = new MomentData(copyData.MomentData);
|
||
PlayerCultureInfo = new PlayerCultureInfo(copyData.PlayerCultureInfo);
|
||
|
||
CurAttackPlayers = new List<uint>();
|
||
foreach (var id in copyData.CurAttackPlayers) CurAttackPlayers.Add(id);
|
||
LastAttackPlayers = new List<uint>();
|
||
foreach (var id in copyData.LastAttackPlayers) LastAttackPlayers.Add(id);
|
||
|
||
MeetPlayers = new List<uint>();
|
||
foreach (var id in copyData.MeetPlayers) MeetPlayers.Add(id);
|
||
|
||
TurnNoAttack = copyData.TurnNoAttack;
|
||
TotalKill = copyData.TotalKill;
|
||
|
||
foreach (var skill in copyData.Skills) Skills.Add(skill.GetCopySkill());
|
||
//拷贝giantExp和giantPenalty
|
||
for (int i = 0; i < 6; i++)
|
||
{
|
||
giantExp[i] = copyData.giantExp[i];
|
||
giantPenalty[i] = copyData.giantPenalty[i];
|
||
}
|
||
}
|
||
|
||
public void DeepCopy(PlayerData copyData)
|
||
{
|
||
Turn = copyData.Turn;
|
||
Id = copyData.Id;
|
||
PlayerCivId = copyData.PlayerCivId;
|
||
PlayerForceId = copyData.PlayerForceId;
|
||
CradleCityId = copyData.CradleCityId;
|
||
Alive = copyData.Alive;
|
||
IsSurrender = copyData.IsSurrender;
|
||
_playerCoin = copyData._playerCoin;
|
||
_playerTechPoint = copyData._playerTechPoint;
|
||
PlayerMountainPointUsed = copyData.PlayerMountainPointUsed;
|
||
|
||
Sight.DeepCopy(copyData.Sight);
|
||
TechTree.DeepCopy(copyData.TechTree);
|
||
Wonder.DeepCopy(copyData.Wonder);
|
||
DiplomacyData.DeepCopy(copyData.DiplomacyData);
|
||
PlayerHeroData.DeepCopy(copyData.PlayerHeroData);
|
||
PlayerNamerData.DeepCopy(copyData.PlayerNamerData);
|
||
MomentData.DeepCopy(copyData.MomentData);
|
||
PlayerCultureInfo.DeepCopy(copyData.PlayerCultureInfo);
|
||
|
||
CurAttackPlayers.Clear();
|
||
foreach (var id in copyData.CurAttackPlayers) CurAttackPlayers.Add(id);
|
||
LastAttackPlayers.Clear();
|
||
foreach (var id in copyData.LastAttackPlayers) LastAttackPlayers.Add(id);
|
||
MeetPlayers.Clear();
|
||
foreach (var id in copyData.MeetPlayers) MeetPlayers.Add(id);
|
||
|
||
TurnNoAttack = copyData.TurnNoAttack;
|
||
TotalKill = copyData.TotalKill;
|
||
|
||
Skills.Clear();
|
||
foreach (var skill in copyData.Skills) Skills.Add(skill.GetCopySkill());
|
||
for (int i = 0; i < 6; i++)
|
||
{
|
||
giantExp[i] = copyData.giantExp[i];
|
||
giantPenalty[i] = copyData.giantPenalty[i];
|
||
}
|
||
}
|
||
|
||
private void InitData(uint civId,uint forceId)
|
||
{
|
||
Alive = true;
|
||
IsSurrender = false;
|
||
_playerCoin = 5;
|
||
if (civId == 2) _playerCoin = 7;
|
||
PlayerCivId = civId;
|
||
PlayerForceId = forceId;
|
||
_playerTechPoint = 0;
|
||
PlayerMountainPointUsed = 0;
|
||
TurnNoAttack = 0;
|
||
TotalKill = 0;
|
||
PlayerHeroData.Init(Table.Instance.TransCivIdToCivEnum(civId) ,Table.Instance.TransForceIdToForceEnum(forceId));
|
||
PlayerNamerData.Init(Table.Instance.TransCivIdToCivEnum(civId));
|
||
}
|
||
|
||
// 全局通知调用
|
||
public void OnTurnStart(MapData map)
|
||
{
|
||
Turn++;
|
||
if (Turn == 1 && PlayerCultureInfo.PlayerCulture == 0)
|
||
PlayerCultureInfo.PlayerCulture = 4;
|
||
//减少1点伟人复活冷却的时间点
|
||
for (int i = 0; i < 6; i++)
|
||
if (giantPenalty[i] > 0) giantPenalty[i]--;
|
||
LastAttackPlayers.Clear();
|
||
foreach (var id in CurAttackPlayers) LastAttackPlayers.Add(id);
|
||
CurAttackPlayers.Clear();
|
||
OnSkillsTurnStart(map);
|
||
|
||
// 这里是英雄的生命周期,先放在这里
|
||
PlayerHeroData.OnTurnStart(map, this);
|
||
foreach (var item in MomentData.Items) item.OnTurnStart();
|
||
IsPlaying = true;
|
||
|
||
//更新所有单位的视觉状态 TODO 之后依赖大回合的逻辑处理
|
||
foreach (var unit in map.UnitMap.UnitList)
|
||
unit.Renderer(map)?.InstantUpdateUnit(true);
|
||
|
||
}
|
||
|
||
public void OnTurnEnd(MapData map)
|
||
{
|
||
OnSkillsTurnEnd(map);
|
||
foreach (var player in map.PlayerMap.PlayerDataList)
|
||
{
|
||
if (player.Id == Id) continue;
|
||
this.GetCountryDiplomacyInfo(player.Id, out var selfToPlayer);
|
||
if (selfToPlayer.IsTeammate)
|
||
{
|
||
selfToPlayer.DiplomacyState = DiplomacyState.League;
|
||
selfToPlayer.IsLeagueRequest = false;
|
||
selfToPlayer.IsLeagueRupture = false;
|
||
continue;
|
||
}
|
||
// 回合结束处理联盟破裂标记
|
||
if (selfToPlayer.DiplomacyState == DiplomacyState.LeagueRupture) selfToPlayer.IsLeagueRupture = true;
|
||
// 回合结束处理AI 冷静期计数
|
||
if (selfToPlayer.IsCalm > 0) selfToPlayer.IsCalm--;
|
||
}
|
||
IsPlaying = false;
|
||
}
|
||
|
||
public bool IsSelfPlayer()
|
||
{
|
||
return IsSelfPlayerOnMainMap();
|
||
}
|
||
|
||
private bool IsSelfPlayerOnMainMap()
|
||
{
|
||
return Main.MapData?.PlayerMap?.SelfPlayerData == this;
|
||
}
|
||
// 添加当前回合攻击者
|
||
public void AddAttacker(uint playerId)
|
||
{
|
||
this.AddTurnAttacker(playerId, Turn);
|
||
}
|
||
|
||
public CivEnum Civ()
|
||
{
|
||
return Table.Instance.TransCivIdToCivEnum(PlayerCivId);
|
||
}
|
||
|
||
public void AddTechPoint(int tech)
|
||
{
|
||
_playerTechPoint += tech;
|
||
//foreach (var kv in PlayerHeroData.HeroTaskDict) kv.Value.OnAddCoin((uint)coin);
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateTech });
|
||
|
||
}
|
||
|
||
public void AddCulturePoint(int culture)
|
||
{
|
||
PlayerCultureInfo.PlayerCulture += culture;
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateCulture });
|
||
}
|
||
|
||
//增加金币(目前处理了逻辑和动画 TODO 现在还是逻辑和动画没有分开,后续还是要分开
|
||
public void AddCoin(int coin, Vector3 startPos)
|
||
{
|
||
_playerCoin += coin;
|
||
foreach (var kv in PlayerHeroData.HeroTaskDict) kv.Value.OnAddCoin((uint)coin);
|
||
//如果加钱的是真人自己玩家,并且有金币来源位置,那么播动画
|
||
if (IsSelfPlayerOnMainMap())
|
||
{
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateCoin });
|
||
MapRenderer.Instance.ProjectileManager.CreateCoinProjectile(startPos,coin);
|
||
AudioManager.Instance.PlayAudio("SFX/PLAYER_coin");
|
||
//真人自己玩家,提前检查财富奇观,方便弹出提示
|
||
Main.PlayerLogic.UpdateWonder(Main.MapData,Main.MapData.PlayerMap.SelfPlayerData,WonderTypeEnum.WEALTH);
|
||
}
|
||
}
|
||
|
||
public void AddCoin_ViewOnly(int coin, Vector3 startPos)
|
||
{
|
||
if (IsSelfPlayerOnMainMap())
|
||
{
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateCoin });
|
||
MapRenderer.Instance.ProjectileManager.CreateCoinProjectile(startPos,coin);
|
||
AudioManager.Instance.PlayAudio("SFX/PLAYER_coin");
|
||
//真人自己玩家,提前检查财富奇观,方便弹出提示
|
||
Main.PlayerLogic.UpdateWonder(Main.MapData,Main.MapData.PlayerMap.SelfPlayerData,WonderTypeEnum.WEALTH);
|
||
}
|
||
}
|
||
|
||
public void AddCoin_LogicOnly(int coin)
|
||
{
|
||
_playerCoin += coin;
|
||
foreach (var kv in PlayerHeroData.HeroTaskDict) kv.Value.OnAddCoin((uint)coin);
|
||
}
|
||
|
||
|
||
public void AddCoin(int coin,GridData grid = null)
|
||
{
|
||
_playerCoin += coin;
|
||
|
||
foreach (var kv in PlayerHeroData.HeroTaskDict) kv.Value.OnAddCoin((uint)coin);
|
||
//如果加钱的是真人自己玩家,则刷新金币;如果有金币来源位置,再播放动画。暂时用legacy方案
|
||
if (IsSelfPlayerOnMainMap())
|
||
{
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateCoin });
|
||
if (grid != null)
|
||
{
|
||
var startPos = Table.Instance.GridToWorld(grid);
|
||
MapRenderer.Instance.ProjectileManager.CreateCoinProjectile(startPos,coin);
|
||
AudioManager.Instance.PlayAudio("SFX/PLAYER_coin");
|
||
}
|
||
//真人自己玩家,提前检查财富奇观,方便弹出提示
|
||
Main.PlayerLogic.UpdateWonder(Main.MapData,Main.MapData.PlayerMap.SelfPlayerData,WonderTypeEnum.WEALTH);
|
||
}
|
||
|
||
|
||
}
|
||
|
||
public void SpendCoin(int coin, GridData grid = null)
|
||
{
|
||
_playerCoin -= coin;
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateCoin });
|
||
}
|
||
|
||
public void SpendTechPoint(int tech)
|
||
{
|
||
_playerTechPoint -= tech;
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateTech });
|
||
}
|
||
|
||
//增加faith的动画目前处理了逻辑和动画 TODO 现在还是逻辑和动画没有分开,后续还是要分开
|
||
|
||
public void AddFaith(int faith,Vector3 startPos)
|
||
{
|
||
|
||
//如果加钱的是真人自己玩家,并且有金币来源位置
|
||
if (Main.MapData.PlayerMap.SelfPlayerData == this)
|
||
{
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateFaith });
|
||
MapRenderer.Instance.ProjectileManager.CreateProjectileFaith(startPos,faith);
|
||
}
|
||
}
|
||
|
||
public void AddFaith(int faith,GridData grid = null)
|
||
{
|
||
//如果加钱的是真人自己玩家,并且有金币来源位置
|
||
if (Main.MapData.PlayerMap.SelfPlayerData == this && grid != null)
|
||
{
|
||
EventManager.Publish(new UpdateUITopTopBar() { UpdateType = UpdateTopBarType.UpdateFaith });
|
||
var startPos = Table.Instance.GridToWorld(grid);
|
||
MapRenderer.Instance.ProjectileManager.CreateProjectileFaith(startPos,faith);
|
||
}
|
||
}
|
||
|
||
// 检查前几回合内是否有攻击者
|
||
public bool CheckTurnsAttacker(uint turns, uint playerId)
|
||
{
|
||
if (turns >= Turn) turns = Turn;
|
||
return this.CheckTurnAttacker(playerId, Turn - turns);
|
||
}
|
||
|
||
// 刷新对他国好感值
|
||
public void RefreshFeelingValue(MapData map)
|
||
{
|
||
var playerDataList = map.PlayerMap.PlayerDataList;
|
||
int maxScore = -1;
|
||
uint maxScorePlayer = 0;
|
||
_tmpOriginCityBuf ??= new HashSet<uint>();
|
||
_tmpOriginCityBuf.Clear();
|
||
foreach (var player in playerDataList)
|
||
{
|
||
_tmpOriginCityBuf.Add(player.CradleCityId);
|
||
if (!player.IsSurvival) continue;
|
||
if (player.PlayerScore <= maxScore) continue;
|
||
maxScore = player.PlayerScore;
|
||
maxScorePlayer = player.Id;
|
||
}
|
||
var originCityHalfCount = _tmpOriginCityBuf.Count * 0.5f;
|
||
|
||
_tmpTerritoryGridIdBuf ??= new HashSet<uint>();
|
||
_tmpSelfTerritoryInfluenceGridIdBuf ??= new HashSet<uint>();
|
||
_tmpPlayerMilitaryScoreBuf ??= new Dictionary<uint, float>();
|
||
_tmpPlayerUnitGridIdListBuf ??= new Dictionary<uint, List<uint>>();
|
||
_tmpPlayerTerritoryGridIdSetBuf ??= new Dictionary<uint, HashSet<uint>>();
|
||
_tmpSelfToOtherDiplomacyStateBuf ??= new Dictionary<uint, DiplomacyState>();
|
||
_tmpPlayerMilitaryScoreBuf.Clear();
|
||
_tmpSelfToOtherDiplomacyStateBuf.Clear();
|
||
foreach (var kv in _tmpPlayerUnitGridIdListBuf)
|
||
{
|
||
kv.Value.Clear();
|
||
}
|
||
foreach (var kv in _tmpPlayerTerritoryGridIdSetBuf)
|
||
{
|
||
kv.Value.Clear();
|
||
}
|
||
foreach (var unit in map.UnitMap.UnitList)
|
||
{
|
||
if (!map.GetPlayerIdByUnitId(unit.Id, out var ownerId)) continue;
|
||
_tmpPlayerMilitaryScoreBuf[ownerId] = _tmpPlayerMilitaryScoreBuf.GetValueOrDefault(ownerId) + unit.GetMilitary();
|
||
if (!map.GetGridDataByUnitId(unit.Id, out var unitGrid)) continue;
|
||
if (!_tmpPlayerUnitGridIdListBuf.TryGetValue(ownerId, out var unitGridIdList))
|
||
{
|
||
unitGridIdList = new List<uint>();
|
||
_tmpPlayerUnitGridIdListBuf[ownerId] = unitGridIdList;
|
||
}
|
||
unitGridIdList.Add(unitGrid.Id);
|
||
}
|
||
var selfScore = _tmpPlayerMilitaryScoreBuf.GetValueOrDefault(Id);
|
||
|
||
_tmpTerritoryGridIdBuf.Clear();
|
||
map.GetPlayerTerritoryGridIdSet(Id, _tmpTerritoryGridIdBuf);
|
||
_tmpSelfTerritoryInfluenceGridIdBuf.Clear();
|
||
foreach (var selfGridId in _tmpTerritoryGridIdBuf)
|
||
{
|
||
if (!map.GridMap.GetGridDataByGid(selfGridId, out var selfGrid)) continue;
|
||
for (int x = selfGrid.Pos.X - 1; x <= selfGrid.Pos.X + 1; x++)
|
||
{
|
||
for (int y = selfGrid.Pos.Y - 1; y <= selfGrid.Pos.Y + 1; y++)
|
||
{
|
||
if (!map.GridMap.GetGridDataByPos(x, y, out var aroundGrid)) continue;
|
||
_tmpSelfTerritoryInfluenceGridIdBuf.Add(aroundGrid.Id);
|
||
}
|
||
}
|
||
}
|
||
foreach (var player in playerDataList)
|
||
{
|
||
if (!player.IsSurvival) continue;
|
||
if (!_tmpPlayerTerritoryGridIdSetBuf.TryGetValue(player.Id, out var territorySet))
|
||
{
|
||
territorySet = new HashSet<uint>();
|
||
_tmpPlayerTerritoryGridIdSetBuf[player.Id] = territorySet;
|
||
}
|
||
|
||
if (player.Id == Id)
|
||
{
|
||
foreach (var gid in _tmpTerritoryGridIdBuf) territorySet.Add(gid);
|
||
}
|
||
else
|
||
{
|
||
map.GetPlayerTerritoryGridIdSet(player.Id, territorySet);
|
||
}
|
||
}
|
||
foreach (var player in playerDataList)
|
||
{
|
||
if (player.Id == Id) continue;
|
||
this.GetCountryDiplomacyInfo(player.Id, out var selfToOtherPlayer);
|
||
if (selfToOtherPlayer == null) continue;
|
||
_tmpSelfToOtherDiplomacyStateBuf[player.Id] = selfToOtherPlayer.DiplomacyState;
|
||
}
|
||
|
||
var aiDiff = map.MapConfig.AIDiff;
|
||
var selfIsAi = map.CheckIsAI(Id);
|
||
|
||
foreach (var player in playerDataList)
|
||
{
|
||
if (!player.IsSurvival) continue;
|
||
if (player.Id == Id) continue;
|
||
this.GetCountryDiplomacyInfo(player.Id, out var selfToPlayer);
|
||
player.GetCountryDiplomacyInfo(Id, out var playerToSelf);
|
||
selfToPlayer.FeelingStrategyList.Clear();
|
||
if (playerToSelf == null || selfToPlayer == null) continue;
|
||
var score = 50f;
|
||
|
||
// 明智的 愚蠢的
|
||
var score1 = 0f;
|
||
foreach (var otherPlayer in playerDataList)
|
||
{
|
||
if (!player.IsSurvival) continue;
|
||
if (otherPlayer.Id == Id || otherPlayer.Id == player.Id) continue;
|
||
if (!_tmpSelfToOtherDiplomacyStateBuf.TryGetValue(otherPlayer.Id, out var selfToOtherPlayerState)) continue;
|
||
if (selfToOtherPlayerState == DiplomacyState.NoDiplomacy) continue;
|
||
player.GetCountryDiplomacyInfo(otherPlayer.Id, out var playerToOtherPlayer);
|
||
if (playerToOtherPlayer.DiplomacyState == DiplomacyState.NoDiplomacy) continue;
|
||
|
||
if (selfToOtherPlayerState == playerToOtherPlayer.DiplomacyState) score1 += 5;
|
||
else score1 -= 5;
|
||
}
|
||
if (score1> 0)
|
||
{
|
||
score1 += 5;
|
||
score += Mathf.Min(20, score1);
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Wise);
|
||
}
|
||
if (score1 < 0)
|
||
{
|
||
score1 -= 5;
|
||
score += Mathf.Max(-20, score1);
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Foolish);
|
||
}
|
||
|
||
// 迷人的 恼人的
|
||
if (aiDiff == AIDifficult.EASY && map.CheckIsRealPlayer(player.Id) && selfIsAi)
|
||
{
|
||
score += 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Charming);
|
||
}
|
||
|
||
if (aiDiff == AIDifficult.LUNATIC && map.CheckIsRealPlayer(player.Id) && selfIsAi)
|
||
{
|
||
score -= 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Annoying);
|
||
}
|
||
|
||
// 和平的 暴力的
|
||
if (!CheckTurnsAttacker(3, player.Id))
|
||
{
|
||
score += 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Peaceful);
|
||
}
|
||
else
|
||
{
|
||
score -= 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Violent);
|
||
}
|
||
|
||
// 外交的
|
||
if (selfToPlayer.IsEmbassy || playerToSelf.IsEmbassy)
|
||
{
|
||
score += 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Diplomatic);
|
||
}
|
||
|
||
// A的军事分>B 强大的 A的军事分<B弱小的
|
||
var playerScore2 = _tmpPlayerMilitaryScoreBuf.GetValueOrDefault(player.Id);
|
||
if (selfScore < playerScore2)
|
||
{
|
||
score += 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Powerful);
|
||
}
|
||
if (selfScore > playerScore2)
|
||
{
|
||
score -= 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Weak);
|
||
}
|
||
|
||
// 勇敢的
|
||
if (maxScorePlayer != Id)
|
||
{
|
||
player.GetCountryDiplomacyInfo(maxScorePlayer, out var playerToMaxScorePlayer);
|
||
if (playerToMaxScorePlayer != null && playerToMaxScorePlayer.DiplomacyState == DiplomacyState.War)
|
||
{
|
||
score += 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Brave);
|
||
}
|
||
}
|
||
|
||
// 威胁的
|
||
if (_tmpPlayerUnitGridIdListBuf.TryGetValue(player.Id, out var enemyUnitGridIdList) &&
|
||
HasAnyOverlap(_tmpSelfTerritoryInfluenceGridIdBuf, enemyUnitGridIdList))
|
||
{
|
||
score -= 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Threatening);
|
||
}
|
||
|
||
// 侵略的 A和B的领土边界相邻,则持有该观点
|
||
if (_tmpPlayerTerritoryGridIdSetBuf.TryGetValue(player.Id, out var playerTerritorySet) &&
|
||
HasAnyOverlap(_tmpSelfTerritoryInfluenceGridIdBuf, playerTerritorySet))
|
||
{
|
||
score -= 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Intrusive);
|
||
}
|
||
|
||
// 主宰的
|
||
var count = 0f;
|
||
if (maxScorePlayer == player.Id)
|
||
{
|
||
_tmpCityListBuf ??= new List<CityData>();
|
||
_tmpCityListBuf.Clear();
|
||
map.GetCityDataListByPlayerId(player.Id, _tmpCityListBuf);
|
||
foreach (var city in _tmpCityListBuf)
|
||
{
|
||
if (_tmpOriginCityBuf.Contains(city.Id)) count++;
|
||
}
|
||
|
||
if (count >= originCityHalfCount)
|
||
{
|
||
score -= 15;
|
||
selfToPlayer.FeelingStrategyList.Add(FeelingStrategy.Dominative);
|
||
}
|
||
}
|
||
|
||
score = Mathf.Max(0, score);
|
||
selfToPlayer.FeelingValue = score;
|
||
if (score <= 10) selfToPlayer.FeelingState = FeelingState.Terrible;
|
||
else if (score <= 30) selfToPlayer.FeelingState = FeelingState.Suspicion;
|
||
else if (score <= 60) selfToPlayer.FeelingState = FeelingState.Indifferent;
|
||
else if (score <= 85) selfToPlayer.FeelingState = FeelingState.Appreciate;
|
||
else if (score <= 100) selfToPlayer.FeelingState = FeelingState.Trust;
|
||
}
|
||
}
|
||
|
||
private bool HasAnyOverlap(HashSet<uint> targetSet, List<uint> sourceList)
|
||
{
|
||
foreach (var id in sourceList)
|
||
{
|
||
if (targetSet.Contains(id)) return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool HasAnyOverlap(HashSet<uint> setA, HashSet<uint> setB)
|
||
{
|
||
if (setA.Count == 0 || setB.Count == 0) return false;
|
||
var source = setA.Count <= setB.Count ? setA : setB;
|
||
var target = source == setA ? setB : setA;
|
||
foreach (var gridId in source)
|
||
{
|
||
if (target.Contains(gridId)) return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 刷新对他国的外交关系,回合开始时调用
|
||
public void RefreshDiplomacyState(MapData map)
|
||
{
|
||
var isMain = map == Main.MapData;
|
||
var selfPlayerId = isMain ? map.PlayerMap.SelfPlayerId : 0u;
|
||
foreach (var player in map.PlayerMap.PlayerDataList)
|
||
{
|
||
if (!player.IsSurvival) continue;
|
||
if (player.Id == Id) continue;
|
||
this.GetCountryDiplomacyInfo(player.Id, out var selfToPlayer);
|
||
player.GetCountryDiplomacyInfo(Id, out var playerToSelf);
|
||
if (selfToPlayer.IsTeammate || playerToSelf.IsTeammate)
|
||
{
|
||
selfToPlayer.IsTeammate = true;
|
||
playerToSelf.IsTeammate = true;
|
||
selfToPlayer.DiplomacyState = DiplomacyState.League;
|
||
playerToSelf.DiplomacyState = DiplomacyState.League;
|
||
selfToPlayer.IsLeagueRequest = false;
|
||
playerToSelf.IsLeagueRequest = false;
|
||
selfToPlayer.IsLeagueRupture = false;
|
||
playerToSelf.IsLeagueRupture = false;
|
||
continue;
|
||
}
|
||
var prevSelfToPlayer = selfToPlayer.DiplomacyState;
|
||
var prevPlayerToSelf = playerToSelf.DiplomacyState;
|
||
// 如果没有外交关系,且没有见过面,则设置为无外交关系
|
||
if (!MeetPlayers.Contains(player.Id)) selfToPlayer.DiplomacyState = DiplomacyState.NoDiplomacy;
|
||
// 如果没有外交关系,且见过面,则设置为中立
|
||
else if (selfToPlayer.DiplomacyState == DiplomacyState.NoDiplomacy)
|
||
selfToPlayer.DiplomacyState = DiplomacyState.Neutral;
|
||
// 如果上一回合回合内双方未发生战斗,则该回合开始时变为中立关系
|
||
//判断有没有对方单位站在我方city上
|
||
bool otherPlayerOnSelfCity = false;
|
||
_tmpCitySetBuf ??= new HashSet<CityData>();
|
||
_tmpCitySetBuf.Clear();
|
||
map.GetCityDataListByPlayerId(Id,_tmpCitySetBuf);
|
||
foreach (var city in _tmpCitySetBuf)
|
||
{
|
||
if(!map.GetGridDataByCityId(city.Id,out var grid))continue;
|
||
if(!map.GetUnitDataByGid_Core(grid.Id,out var unit))continue;
|
||
if(!map.GetPlayerDataByUnitId(unit.Id,out var tPlayer))continue;
|
||
if (tPlayer.Id == player.Id)
|
||
{
|
||
otherPlayerOnSelfCity = true;
|
||
break;
|
||
}
|
||
}
|
||
if (selfToPlayer.DiplomacyState == DiplomacyState.War && !LastAttackPlayers.Contains(player.Id) && !otherPlayerOnSelfCity)
|
||
selfToPlayer.DiplomacyState = DiplomacyState.Neutral;
|
||
// 联盟破裂转中立
|
||
if (selfToPlayer.DiplomacyState == DiplomacyState.LeagueRupture &&
|
||
playerToSelf.DiplomacyState == DiplomacyState.LeagueRupture &&
|
||
selfToPlayer.IsLeagueRupture && playerToSelf.IsLeagueRupture)
|
||
{
|
||
selfToPlayer.DiplomacyState = DiplomacyState.Neutral;
|
||
playerToSelf.DiplomacyState = DiplomacyState.Neutral;
|
||
selfToPlayer.IsLeagueRupture = false;
|
||
playerToSelf.IsLeagueRupture = false;
|
||
}
|
||
|
||
// 外交状态实际变化、且 self 玩家参与变化时,同步刷新对方玩家的 unit/city 视觉
|
||
// 否则 UnitMono 的盟友图标/敌我背景色会停留在变更前,要等下一个 action 才被动刷新
|
||
//
|
||
// [Fix 2026/05/22] 视觉刷新走 PresentationManager 队列,避免 Explorer 等"逻辑视野
|
||
// 立即生效但视觉走队列"的 Action 在 AfterExecute 时立即把所有新见面玩家的单位全部
|
||
// SetActive(true),导致迷雾下的单位还没等雾消失动画就提前显示出来。
|
||
// 队列空时 EnqueueTask 会立即同步执行(TryProcessNext),行为与原同步实现等价;
|
||
// 队列非空时延迟到当前队列消化完,使视觉与动画时序对齐。
|
||
if (isMain &&
|
||
(selfToPlayer.DiplomacyState != prevSelfToPlayer || playerToSelf.DiplomacyState != prevPlayerToSelf))
|
||
{
|
||
uint visualPlayerId = 0u;
|
||
if (Id == selfPlayerId) visualPlayerId = player.Id;
|
||
else if (player.Id == selfPlayerId) visualPlayerId = Id;
|
||
if (visualPlayerId != 0u)
|
||
{
|
||
var capturedMap = map;
|
||
var capturedPlayerId = visualPlayerId;
|
||
System.Action refreshVisual = () =>
|
||
{
|
||
// 注意:lambda 异步执行时 capturedMap 仍引用相同 MapData 对象。
|
||
// 用独立 pool buffer 避免与 PlayerData._tmpCitySetBuf 复用冲突。
|
||
if (capturedMap?.UnitMap?.UnitList != null)
|
||
{
|
||
foreach (var tunit in capturedMap.UnitMap.UnitList)
|
||
{
|
||
if (tunit == null) continue;
|
||
if (!capturedMap.GetPlayerDataByUnitId(tunit.Id, out var uplayer)) continue;
|
||
if (uplayer == null) continue;
|
||
if (uplayer.Id == capturedPlayerId)
|
||
tunit.Renderer(capturedMap)?.InstantUpdateUnit(true);
|
||
}
|
||
}
|
||
using var pooledCities = THCollectionPool.GetHashSetHandle<CityData>(out var cityBuf);
|
||
capturedMap?.GetCityDataListByPlayerId(capturedPlayerId, cityBuf);
|
||
foreach (var city in cityBuf) city.SetCityRenderer(capturedMap);
|
||
};
|
||
|
||
if (USE_QUEUED_DIPLOMACY_REFRESH)
|
||
PresentationManager.EnqueueTask(new ActionSequencerTask(refreshVisual, "DiplomacyVisualRefresh"));
|
||
else
|
||
refreshVisual();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public int GetTechCost(TechInfo techInfo)
|
||
{
|
||
return Main.MapData.GetCityCount(Id) * techInfo.CostLevel + 4;
|
||
}
|
||
|
||
//判断该玩家能否"显示"某个资源,如metal,crop,starfish
|
||
public bool CanShowResource(ResourceType resource)
|
||
{
|
||
if (resource is ResourceType.Metal)
|
||
return TechTree.CheckIfHasTechAtom(TechAtom.UnitSkillMOUNTAINMOVE)
|
||
|| TechTree.CheckIfHasTechAtom(TechAtom.BuildMine)
|
||
|| TechTree.CheckIfHasTechAtom(TechAtom.BuildForge);
|
||
|
||
if (resource is ResourceType.Starfish)
|
||
return TechTree.CheckIfHasTechAtom(TechAtom.UnitSkillOCEANMOVE)
|
||
|| TechTree.CheckIfHasTechAtom(TechAtom.UnitActionGather);
|
||
|
||
if (resource is ResourceType.Crop)
|
||
return TechTree.CheckIfHasTechAtom(TechAtom.BuildFarm)
|
||
|| TechTree.CheckIfHasTechAtom(TechAtom.GainFruit);
|
||
return true;
|
||
}
|
||
|
||
//获取英雄相关所有消耗折扣
|
||
public float GetHeroCostDiscount(MapData mapData, out float discount)
|
||
{
|
||
discount = 1;
|
||
foreach (var card in PlayerCultureInfo.CardList)
|
||
{
|
||
var cardDiscount = card.GetHeroCostDiscount(mapData, this);
|
||
if (cardDiscount < discount) discount = cardDiscount;
|
||
}
|
||
return discount;
|
||
}
|
||
|
||
// 是否可以用文化值购买巨人
|
||
public virtual bool CanBuyBigGuyWithCulture(MapData mapData)
|
||
{
|
||
foreach (var card in PlayerCultureInfo.CardList)
|
||
{
|
||
if (card.CanBuyBigGuyWithCulture(mapData, this)) return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 是否可以用文化值让普通单位立刻升级为 Officer
|
||
public virtual bool CanCultureUpgradeUnit(MapData mapData)
|
||
{
|
||
foreach (var card in PlayerCultureInfo.CardList)
|
||
{
|
||
if (card.CanCultureUpgradeUnit(mapData, this)) return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 文化值是否能转换成金币
|
||
public virtual bool CanConvertCultureToGold(MapData mapData)
|
||
{
|
||
foreach (var card in PlayerCultureInfo.CardList)
|
||
{
|
||
if (card.CanConvertCultureToGold(mapData, this)) return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// 检查是否开启了对应 Temple Resource
|
||
public bool CheckResourceOpen(MapData mapData, ResourceType resourceType)
|
||
{
|
||
foreach (var card in PlayerCultureInfo.CardList)
|
||
{
|
||
if (card.CheckResourceOpen(mapData, this, resourceType)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 投降
|
||
public void Surrender(MapData map)
|
||
{
|
||
// 退出前解除所有盟友/刚背盟关系:League / LeagueRupture → Neutral
|
||
// LeagueRupture 自动转 Neutral 依赖双方都跑 OnTurnEnd 标 IsLeagueRupture,投降后该标记永远凑不齐 → 必须主动清
|
||
foreach (var otherPlayer in map.PlayerMap.PlayerDataList)
|
||
{
|
||
if (otherPlayer.Id == Id) continue;
|
||
if (!this.GetCountryDiplomacyInfo(otherPlayer.Id, out var selfToOther)) continue;
|
||
if (selfToOther.IsTeammate) continue;
|
||
if (selfToOther.DiplomacyState != DiplomacyState.League
|
||
&& selfToOther.DiplomacyState != DiplomacyState.LeagueRupture) continue;
|
||
|
||
Main.PlayerLogic.SetDiplomacyLeague(map, this, otherPlayer, DiplomacyState.Neutral);
|
||
selfToOther.IsEmbassy = false;
|
||
selfToOther.IsLeagueRupture = false;
|
||
if (otherPlayer.GetCountryDiplomacyInfo(Id, out var otherToSelf))
|
||
{
|
||
otherToSelf.IsEmbassy = false;
|
||
otherToSelf.IsLeagueRupture = false;
|
||
}
|
||
}
|
||
|
||
IsSurrender = true;
|
||
using var pooledSelfUnits = THCollectionPool.GetHashSetHandle<UnitData>(out var selfUnits);
|
||
map.GetUnitDataListByPlayerId(Id, selfUnits);
|
||
// 先播放Fog特效并销毁渲染层,再清除数据层
|
||
foreach (var unit in selfUnits)
|
||
{
|
||
unit.Grid(map)?.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
||
unit.Renderer(map)?.Die();
|
||
}
|
||
foreach (var unit in selfUnits) Main.UnitLogic.UnitUnnaturalDie(map, unit);
|
||
}
|
||
}
|
||
|
||
|
||
// 奇观信息
|
||
[MemoryPackable]
|
||
public partial class WonderData
|
||
{
|
||
// 所有奇观信息
|
||
public Dictionary<WonderTypeEnum, WonderState> WonderInfoDict;
|
||
public HashSet<WonderTypeEnum> WonderRenderMark;
|
||
|
||
|
||
[MemoryPackConstructor]
|
||
public WonderData()
|
||
{
|
||
WonderInfoDict = new Dictionary<WonderTypeEnum, WonderState>();
|
||
WonderRenderMark = new HashSet<WonderTypeEnum>();
|
||
foreach (WonderTypeEnum wonderType in System.Enum.GetValues(typeof(WonderTypeEnum)))
|
||
WonderInfoDict[wonderType] = WonderState.NO_HINT;
|
||
}
|
||
|
||
public WonderData(WonderData copyData)
|
||
{
|
||
WonderInfoDict = new Dictionary<WonderTypeEnum, WonderState>(copyData.WonderInfoDict);
|
||
WonderRenderMark = new HashSet<WonderTypeEnum>(copyData.WonderRenderMark);
|
||
foreach (var kv in copyData.WonderInfoDict)
|
||
{
|
||
WonderInfoDict[kv.Key] = kv.Value;
|
||
}
|
||
|
||
foreach (var typeEnum in copyData.WonderRenderMark) WonderRenderMark.Add(typeEnum);
|
||
}
|
||
|
||
public void DeepCopy(WonderData copyData)
|
||
{
|
||
WonderInfoDict.Clear();
|
||
WonderRenderMark.Clear();
|
||
foreach (var kv in copyData.WonderInfoDict) WonderInfoDict[kv.Key] = kv.Value;
|
||
foreach (var typeEnum in copyData.WonderRenderMark) WonderRenderMark.Add(typeEnum);
|
||
}
|
||
|
||
// 获取某个奇观的状态
|
||
public WonderState GetWonderState(WonderTypeEnum wonderType)
|
||
{
|
||
if (WonderInfoDict.TryGetValue(wonderType, out var state))
|
||
{
|
||
return state;
|
||
}
|
||
return WonderState.NO_HINT;
|
||
}
|
||
|
||
public void SetWonderState(WonderTypeEnum wonderType, WonderState state)
|
||
{
|
||
WonderInfoDict[wonderType] = state;
|
||
WonderRenderMark.Add(wonderType);
|
||
}
|
||
// 判断某个奇观是否变动
|
||
public bool IsWonderRenderMark(WonderTypeEnum wonderType)
|
||
{
|
||
return WonderRenderMark.Contains(wonderType);
|
||
}
|
||
|
||
public bool IsWonderLibraryBuilt(WonderLibrary wonderLibrary,PlayerData player)
|
||
{
|
||
var empire = new Empire(player.CivEnum, player.ForceEnum);
|
||
foreach (WonderTypeEnum wonderType in Enum.GetValues(typeof(WonderTypeEnum)))
|
||
if (Table.Instance.LibraryDataAssets.GetLibraryInfoByWonder(empire, wonderType, out var info)
|
||
&& info.WonderLibraryID == wonderLibrary
|
||
&& WonderInfoDict.TryGetValue(wonderType, out var v) && v == WonderState.FINISH_BUILD)
|
||
return true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
|
||
// 视野信息
|
||
[MemoryPackable]
|
||
public partial class MapSightData
|
||
{
|
||
public HashSet<uint> SightGidSet;
|
||
|
||
|
||
[MemoryPackConstructor]
|
||
public MapSightData()
|
||
{
|
||
SightGidSet = new HashSet<uint>();
|
||
}
|
||
|
||
public MapSightData(MapSightData copyData)
|
||
{
|
||
SightGidSet = new HashSet<uint>(copyData.SightGidSet);
|
||
}
|
||
|
||
public void DeepCopy(MapSightData copyData)
|
||
{
|
||
SightGidSet.Clear();
|
||
foreach (var gid in copyData.SightGidSet) SightGidSet.Add(gid);
|
||
}
|
||
|
||
// 判断是否在视野内
|
||
public bool CheckIsInSight(uint gid)
|
||
{
|
||
return SightGidSet.Contains(gid);
|
||
}
|
||
|
||
//更新unit移动路径上的视野 便捷版 unit在gridpos上 更新视野
|
||
public bool UpdateSightByPath(UnitData unit,PlayerData player,Vector2Int pos,MapData map)
|
||
{
|
||
if (!map.GridMap.GetGridDataByPos(pos.x, pos.y, out var grid))
|
||
{
|
||
Debug.Log("错误!UpdateSightByPath 时无法通过x,y获得grid");
|
||
return false;
|
||
}
|
||
var range = unit?.GetSightRange(map,grid) ?? 1;
|
||
var list = map.GridMap.GetAroundGridIdList(range,grid);
|
||
var count = Main.PlayerLogic.UpdateSight_LogicView(map, player, list);
|
||
unit.HeroTask(map)?.OnExploredGrids(map, unit, count);
|
||
foreach (var kv in player.PlayerHeroData.HeroTaskDict) kv.Value.OnAnyExploredGrids(map, unit, count);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
|
||
// 科技信息
|
||
[MemoryPackable]
|
||
public partial class TechTreeData
|
||
{
|
||
public HashSet<TechType> TechSet;
|
||
[MemoryPackIgnore] public HashSet<uint> TechAtomActionCacheSet;
|
||
[MemoryPackIgnore] public HashSet<TechAtom> TechAtomCacheSet;
|
||
|
||
|
||
[MemoryPackConstructor]
|
||
public TechTreeData()
|
||
{
|
||
TechSet = new HashSet<TechType>();
|
||
TechAtomActionCacheSet = new HashSet<uint>();
|
||
TechAtomCacheSet = new HashSet<TechAtom>();
|
||
}
|
||
|
||
public TechTreeData(PlayerData player, List<TechType> startTechList = null)
|
||
{
|
||
TechSet = new HashSet<TechType>();
|
||
TechAtomActionCacheSet = new HashSet<uint>();
|
||
TechAtomCacheSet = new HashSet<TechAtom>();
|
||
if (startTechList != null)
|
||
{
|
||
foreach(var t in startTechList) LearnTech(t, player);
|
||
}
|
||
}
|
||
|
||
public TechTreeData(TechTreeData copyData)
|
||
{
|
||
TechSet = new HashSet<TechType>(copyData.TechSet);
|
||
TechAtomActionCacheSet = new HashSet<uint>(copyData.TechAtomActionCacheSet);
|
||
TechAtomCacheSet = new HashSet<TechAtom>(copyData.TechAtomCacheSet);
|
||
}
|
||
|
||
// MemoryPack 反序列化之后的后处理
|
||
[MemoryPackOnDeserialized]
|
||
public void OnAfterMemoryPackDeserialize()
|
||
{
|
||
TechAtomActionCacheSet ??= new HashSet<uint>();
|
||
TechAtomCacheSet ??= new HashSet<TechAtom>();
|
||
foreach (var tech in TechSet)
|
||
{
|
||
var techInfo = Table.Instance.TechDataAssets.GetTechInfo(tech);
|
||
foreach (var techAtom in techInfo.TechAtomList)
|
||
{
|
||
TechAtomCacheSet.Add(techAtom);
|
||
if (!Table.Instance.TechDataAssets.GetTechAtomInfo(techAtom, out var info)) continue;
|
||
if(info.EnableAction)
|
||
foreach(var t in info.TechActions)
|
||
TechAtomActionCacheSet.Add(t.Id);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
public void DeepCopy(TechTreeData copyData)
|
||
{
|
||
TechSet.Clear();
|
||
TechAtomActionCacheSet.Clear();
|
||
foreach (var tech in copyData.TechSet) TechSet.Add(tech);
|
||
foreach (var act in copyData.TechAtomActionCacheSet) TechAtomActionCacheSet.Add(act);
|
||
foreach (var atom in copyData.TechAtomCacheSet) TechAtomCacheSet.Add(atom);
|
||
}
|
||
|
||
public void LearnTech(TechType techType, PlayerData player)
|
||
{
|
||
if (!TechSet.Add(techType)) return;
|
||
|
||
if (player.PlayerHeroData != null)
|
||
{
|
||
foreach (var kv in player.PlayerHeroData.HeroTaskDict)
|
||
kv.Value.OnTechGet(player);
|
||
}
|
||
var techInfo = Table.Instance.TechDataAssets.GetTechInfo(techType);
|
||
foreach (var techAtom in techInfo.TechAtomList)
|
||
{
|
||
TechAtomCacheSet.Add(techAtom);
|
||
if (!Table.Instance.TechDataAssets.GetTechAtomInfo(techAtom, out var info)) continue;
|
||
if(info.EnableAction)
|
||
foreach(var t in info.TechActions)
|
||
TechAtomActionCacheSet.Add(t.Id);
|
||
}
|
||
}
|
||
|
||
// 删除科技
|
||
public void RemoveTech(TechType techType)
|
||
{
|
||
TechSet.Remove(techType);
|
||
}
|
||
|
||
// 是否拥有某个科技
|
||
public bool CheckIfHasTech(TechType techType) { return TechSet.Contains(techType); }
|
||
|
||
public bool CheckIfHasTech(List<TechType> techType)
|
||
{
|
||
foreach (var tech in techType)
|
||
if (CheckIfHasTech(tech))
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
//是否拥有某个原子科技
|
||
public bool CheckIfHasTechAtom(TechAtom atom) { return TechAtomCacheSet.Contains(atom); }
|
||
|
||
public bool HasAllTech(PlayerData player)
|
||
{
|
||
bool ret = true;
|
||
Table.Instance.PlayerDataAssets.GetPlayerInfo(player, out var info);
|
||
foreach (TechType tech in info.TechPool)
|
||
{
|
||
var techInfo = Table.Instance.TechDataAssets.GetTechInfo(tech);
|
||
if (CheckIfHasTech(tech))
|
||
continue;
|
||
ret = false;
|
||
break;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
public int GetScore()
|
||
{
|
||
int ret = 0;
|
||
foreach (var tech in TechSet)
|
||
{
|
||
var techInfo = Table.Instance.TechDataAssets.GetTechInfo(tech);
|
||
ret += techInfo.CostLevel * 100;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
public bool CheckActionCan(CommonActionId actionId)
|
||
{
|
||
if (!Table.Instance.ActionDataAssets.GetActionInfo(actionId, out var info)) return false;
|
||
if (info.NoNeedTech) return true;
|
||
return TechAtomActionCacheSet.Contains(actionId.Id);
|
||
}
|
||
|
||
public bool CheckIfTechUnsee(TechType techType)
|
||
{
|
||
return !HasFatherTech(Table.Instance.TechDataAssets.GetTechInfo(techType).FatherTechList);
|
||
}
|
||
|
||
private bool HasFatherTech(List<TechType> techList)
|
||
{
|
||
foreach(var tech in techList)
|
||
if (TechSet.Contains(tech))
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
public bool CheckIfTechCanLearn(TechType techType)
|
||
{
|
||
|
||
//Debug.Log(techType);
|
||
return !TechSet.Contains(techType) && !CheckIfTechUnsee(techType);
|
||
|
||
}
|
||
}
|
||
|
||
|
||
// 外交信息
|
||
[MemoryPackable]
|
||
public partial class DiplomacyData
|
||
{
|
||
public List<CountryDiplomacyInfo> Info;
|
||
private Dictionary<uint, CountryDiplomacyInfo> _infoDict;
|
||
|
||
|
||
[MemoryPackConstructor]
|
||
public DiplomacyData()
|
||
{
|
||
Info = new List<CountryDiplomacyInfo>();
|
||
}
|
||
|
||
public DiplomacyData(DiplomacyData copyData)
|
||
{
|
||
Info = new List<CountryDiplomacyInfo>();
|
||
}
|
||
|
||
// MemoryPack 反序列化之后的后处理
|
||
[MemoryPackOnDeserialized]
|
||
public void OnAfterMemoryPackDeserialize()
|
||
{
|
||
Refresh();
|
||
}
|
||
|
||
public void DeepCopy(DiplomacyData copyData)
|
||
{
|
||
for (int i = 0; i < copyData.Info.Count; i++)
|
||
{
|
||
if (Info.Count <= i) Info.Add(new CountryDiplomacyInfo());
|
||
Info[i].DeepCopy(copyData.Info[i]);
|
||
}
|
||
}
|
||
|
||
public bool CheckTurnAttacker(uint selfId, uint targetId, uint turn)
|
||
{
|
||
GetCountryDiplomacyInfo(selfId, targetId, out var info);
|
||
if (info == null) return false;
|
||
return info.AttackTurn != 0 && info.AttackTurn >= turn;
|
||
}
|
||
|
||
public void AddTurnAttacker(uint selfId, uint targetId, uint turn)
|
||
{
|
||
GetCountryDiplomacyInfo(selfId, targetId, out var info);
|
||
if (info == null) return;
|
||
info.AttackTurn = turn;
|
||
}
|
||
|
||
public void AddCountryDiplomacyInfo(uint targetId)
|
||
{
|
||
Refresh();
|
||
if (_infoDict.ContainsKey(targetId)) return;
|
||
var info = new CountryDiplomacyInfo { PlayerId = targetId };
|
||
Info.Add(info);
|
||
}
|
||
|
||
public bool GetCountryDiplomacyInfo(uint selfId, uint targetId, out CountryDiplomacyInfo info)
|
||
{
|
||
info = null;
|
||
if (selfId == targetId) return false;
|
||
Refresh();
|
||
return _infoDict.TryGetValue(targetId, out info);
|
||
|
||
}
|
||
|
||
private void Refresh()
|
||
{
|
||
if (_infoDict == null) _infoDict = new Dictionary<uint, CountryDiplomacyInfo>();
|
||
if (_infoDict.Count == Info.Count) return;
|
||
|
||
_infoDict.Clear();
|
||
foreach (var countryDiplomacy in Info) _infoDict[countryDiplomacy.PlayerId] = countryDiplomacy;
|
||
}
|
||
}
|
||
|
||
|
||
public static class DiplomacyDataExtensions
|
||
{
|
||
public static bool GetCountryDiplomacyInfo(this PlayerData player, uint targetId, out CountryDiplomacyInfo info)
|
||
{
|
||
return player.DiplomacyData.GetCountryDiplomacyInfo(player.Id, targetId, out info);
|
||
}
|
||
|
||
public static void GetCountryDiplomacyInfo(this PlayerData player, PlayerData targetPlayer, out CountryDiplomacyInfo info)
|
||
{
|
||
player.DiplomacyData.GetCountryDiplomacyInfo(player.Id, targetPlayer.Id, out info);
|
||
}
|
||
|
||
public static void AddTurnAttacker(this PlayerData player, uint targetId, uint turn)
|
||
{
|
||
player.DiplomacyData.AddTurnAttacker(player.Id, targetId, turn);
|
||
}
|
||
|
||
public static void AddTurnAttacker(this PlayerData player, PlayerData targetPlayer, uint turn)
|
||
{
|
||
player.DiplomacyData.AddTurnAttacker(player.Id, targetPlayer.Id, turn);
|
||
}
|
||
|
||
public static bool CheckTurnAttacker(this PlayerData player, uint targetId, uint turn)
|
||
{
|
||
return player.DiplomacyData.CheckTurnAttacker(player.Id, targetId, turn);
|
||
}
|
||
|
||
public static bool CheckTurnAttacker(this PlayerData player, PlayerData targetPlayer, uint turn)
|
||
{
|
||
return player.DiplomacyData.CheckTurnAttacker(player.Id, targetPlayer.Id, turn);
|
||
}
|
||
}
|
||
|
||
|
||
// 对他国的数据
|
||
[MemoryPackable]
|
||
public partial class CountryDiplomacyInfo
|
||
{
|
||
public uint PlayerId;
|
||
// 我对国家 PlayerId 的外交关系
|
||
public DiplomacyState DiplomacyState;
|
||
// 我对国家 PlayerId 的好感值
|
||
public float FeelingValue;
|
||
// 我对国家 PlayerId 的好感态度
|
||
public FeelingState FeelingState;
|
||
// 我对国家 PlayerId 的好感策略
|
||
public List<FeelingStrategy> FeelingStrategyList;
|
||
// 国家 PlayerId 攻击我的回合记录
|
||
public uint AttackTurn;
|
||
// 国家 PlayerId 是否在我国建立了大使馆
|
||
public bool IsEmbassy;
|
||
// 联盟破裂回合结束标记
|
||
public bool IsLeagueRupture;
|
||
// 国家对我发起的结盟请求
|
||
public bool IsLeagueRequest;
|
||
// 冷静期标记
|
||
[MemoryPackIgnore]
|
||
public int IsCalm;
|
||
// PlayerId 是我的队友 (逻辑相当于同盟)
|
||
public bool IsTeammate;
|
||
|
||
|
||
[MemoryPackConstructor]
|
||
public CountryDiplomacyInfo()
|
||
{
|
||
AttackTurn = 0;
|
||
IsCalm = 0;
|
||
IsEmbassy = false;
|
||
IsLeagueRupture = false;
|
||
IsLeagueRequest = false;
|
||
IsTeammate = false;
|
||
FeelingStrategyList = new List<FeelingStrategy>();
|
||
}
|
||
|
||
public CountryDiplomacyInfo(CountryDiplomacyInfo copyData)
|
||
{
|
||
PlayerId = copyData.PlayerId;
|
||
AttackTurn = copyData.AttackTurn;
|
||
DiplomacyState = copyData.DiplomacyState;
|
||
FeelingValue = copyData.FeelingValue;
|
||
FeelingState = copyData.FeelingState;
|
||
FeelingStrategyList= new List<FeelingStrategy>();
|
||
foreach (var strategy in copyData.FeelingStrategyList)FeelingStrategyList.Add(strategy);
|
||
IsEmbassy = copyData.IsEmbassy;
|
||
IsLeagueRupture = copyData.IsLeagueRupture;
|
||
IsLeagueRequest = copyData.IsLeagueRequest;
|
||
IsCalm = copyData.IsCalm;
|
||
IsTeammate = copyData.IsTeammate;
|
||
}
|
||
|
||
public void DeepCopy(CountryDiplomacyInfo copyData)
|
||
{
|
||
PlayerId = copyData.PlayerId;
|
||
AttackTurn = copyData.AttackTurn;
|
||
DiplomacyState = copyData.DiplomacyState;
|
||
FeelingValue = copyData.FeelingValue;
|
||
FeelingState = copyData.FeelingState;
|
||
FeelingStrategyList.Clear();
|
||
foreach (var strategy in copyData.FeelingStrategyList)FeelingStrategyList.Add(strategy);
|
||
IsEmbassy = copyData.IsEmbassy;
|
||
IsLeagueRupture = copyData.IsLeagueRupture;
|
||
IsLeagueRequest = copyData.IsLeagueRequest;
|
||
IsCalm = copyData.IsCalm;
|
||
IsTeammate = copyData.IsTeammate;
|
||
}
|
||
}
|
||
|
||
// 英雄的数据
|
||
[MemoryPackable]
|
||
public partial class PlayerHeroData
|
||
{
|
||
//当前最多获取英雄数量
|
||
public int MaxHeroCount;
|
||
//当前已经拥有的英雄数量
|
||
public int HeroCount => HeroList.Count;
|
||
//当前已经有的英雄
|
||
public List<UnitFullType> HeroList;
|
||
public GiantType LeaderGiantType;
|
||
public Dictionary<UnitFullType, HeroTaskContentBase> HeroTaskDict;
|
||
[MemoryPackIgnore] private List<GridData> _tmpGridListBuf;
|
||
|
||
[MemoryPackConstructor]
|
||
public PlayerHeroData()
|
||
{
|
||
MaxHeroCount = 1;
|
||
HeroList = new List<UnitFullType>();
|
||
LeaderGiantType = GiantType.None;
|
||
HeroTaskDict = new Dictionary<UnitFullType, HeroTaskContentBase>();
|
||
}
|
||
|
||
public PlayerHeroData(PlayerHeroData copyData)
|
||
{
|
||
MaxHeroCount = copyData.MaxHeroCount;
|
||
HeroList = new List<UnitFullType>(copyData.HeroList);
|
||
LeaderGiantType = copyData.LeaderGiantType;
|
||
HeroTaskDict = new Dictionary<UnitFullType, HeroTaskContentBase>();
|
||
foreach (var kv in copyData.HeroTaskDict) HeroTaskDict.TryAdd(kv.Key,kv.Value?.GetCopyHeroTaskContent());
|
||
}
|
||
|
||
public void DeepCopy(PlayerHeroData copyData)
|
||
{
|
||
MaxHeroCount = copyData.MaxHeroCount;
|
||
HeroList = new List<UnitFullType>(copyData.HeroList);
|
||
LeaderGiantType = copyData.LeaderGiantType;
|
||
HeroTaskDict.Clear();
|
||
foreach (var kv in copyData.HeroTaskDict) HeroTaskDict.TryAdd(kv.Key,kv.Value?.GetCopyHeroTaskContent());
|
||
}
|
||
|
||
public void Init(CivEnum civEnum, ForceEnum forceEnum)
|
||
{
|
||
LeaderGiantType = civEnum switch
|
||
{
|
||
CivEnum.Egyptian => GiantType.EgyptianRemilia,
|
||
CivEnum.French => GiantType.FrenchKaguya,
|
||
CivEnum.Germany => GiantType.GermanyKanako,
|
||
CivEnum.Indian => GiantType.IndianSatori,
|
||
_ => GiantType.None
|
||
};
|
||
}
|
||
public GiantType GetLeaderGiantType()
|
||
{
|
||
return LeaderGiantType;
|
||
}
|
||
|
||
public void OnTurnStart(MapData map, PlayerData player)
|
||
{
|
||
foreach (var heroType in HeroList)
|
||
{
|
||
if (heroType.GiantType != GiantType.IndianKoishi) continue;
|
||
var count = 0;
|
||
foreach (var unit in map.UnitMap.UnitList)
|
||
{
|
||
if (unit.GiantType != GiantType.IndianKoishi) continue;
|
||
if (!map.GetPlayerIdByUnitId(unit.Id, out var ownerId)) continue;
|
||
if (ownerId != player.Id) continue;
|
||
count++;
|
||
}
|
||
if (count >= heroType.UnitLevel) continue;
|
||
map.GetCapitalCityDataByPlayerId(player.Id, out var city);
|
||
var cityGrid = city?.Grid(map);
|
||
if (cityGrid == null) continue;
|
||
_tmpGridListBuf ??= new List<GridData>();
|
||
_tmpGridListBuf.Clear();
|
||
map.GridMap.GetAroundGridData(1, 1, cityGrid, _tmpGridListBuf);
|
||
using var pooledRandomList = THCollectionPool.GetListHandle<GridData>(out var randomList);
|
||
foreach (var grid in _tmpGridListBuf)
|
||
{
|
||
if (grid == cityGrid) continue;
|
||
if (grid.RealUnit(map,out _)) continue;
|
||
if(!map.CheckLandTypeForGrid(heroType, grid))continue;
|
||
randomList.Add(grid);
|
||
}
|
||
if (randomList.Count == 0) continue;
|
||
var index = map.Net.GetRandom(map).Next(0, randomList.Count - 1); // 生成 index
|
||
if (map.AddUnitData(randomList[index].Id, city.Id, heroType, out var newUnit))
|
||
{
|
||
// 更新新单位的视野,并把揭开的格子数计入heroTask探索类任务
|
||
var exploredCount = Main.PlayerLogic.UpdateSight_LogicView(map, player,
|
||
map.GridMap.GetAroundGridIdList(newUnit.GetSightRange(map), randomList[index]));
|
||
newUnit.HeroTask(map)?.OnExploredGrids(map, newUnit, exploredCount);
|
||
foreach (var kv in player.PlayerHeroData.HeroTaskDict) kv.Value.OnAnyExploredGrids(map, newUnit, exploredCount);
|
||
}
|
||
}
|
||
}
|
||
|
||
public bool HasHero(GiantType giantType)
|
||
{
|
||
foreach (var heroType in HeroList)
|
||
if (heroType.GiantType == giantType)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
public bool AddHero(MapData map,PlayerData player,GiantType giantType)
|
||
{
|
||
var type = new UnitFullType(UnitType.Giant, giantType, 1);
|
||
//第二、第三个英雄起步为Lv2
|
||
if (HeroCount >= 1) type.UnitLevel = 2;
|
||
if (HasHero(giantType)) return false;
|
||
HeroList.Add(type);
|
||
//如果Lv3或者其他意外,就不需要填加对应task了
|
||
if (HeroTaskManager.Instance.GetHeroTaskContent(type, out var task))
|
||
{
|
||
HeroTaskDict.Add(type,task);
|
||
task.OnTaskInit(map,player);
|
||
}
|
||
// moment
|
||
foreach (var item in player.MomentData.Items) item.OnAddHero(map, player, type);
|
||
return true;
|
||
}
|
||
|
||
//发生在英雄升级的时候,更新heroList,让新的英雄type替换旧的type
|
||
public bool UpdateHero(MapData map,PlayerData player,UnitFullType newType)
|
||
{
|
||
if (!HasHero(newType.GiantType)) return false;
|
||
|
||
//通知UI界面 更新heroButton按钮
|
||
if (this == Main.MapData.PlayerMap.SelfPlayerData.PlayerHeroData)
|
||
{
|
||
EventManager.Publish(new UpdateUIBottomBottomBarHeroButtonAvatar());
|
||
//UIManager.Instance.BottomBarUI.UpdateHeroButtonSprite();
|
||
}
|
||
for(int i =0;i < HeroCount;i++)
|
||
if (HeroList[i].GiantType == newType.GiantType)
|
||
{
|
||
HeroTaskDict.Remove(HeroList[i]);
|
||
HeroList[i] = newType;
|
||
//如果不是满级,要更新新的任务
|
||
if(newType.UnitLevel < 4){
|
||
if (!HeroTaskManager.Instance.GetHeroTaskContent(newType, out var task)) return false;
|
||
HeroTaskDict.Add(newType,task);
|
||
task.OnTaskInit(map,player);
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public bool ForceFinishHeroTask(GiantType giant)
|
||
{
|
||
if (!HasHero(giant)) return false;
|
||
if(!GetHeroTask(giant,out var task))return false;
|
||
//task.Level = task.TargetLevel;
|
||
if (task.IsForceFinished) return false;
|
||
task.ForceFinish();
|
||
return true;
|
||
}
|
||
|
||
public bool GetHeroTask(GiantType giant, out HeroTaskContentBase task)
|
||
{
|
||
foreach(var t in HeroTaskDict)
|
||
if (t.Key.GiantType == giant)
|
||
{
|
||
task = t.Value;
|
||
return true;
|
||
}
|
||
task = null;
|
||
return false;
|
||
}
|
||
|
||
public void UpdateHeroMaxCount(uint unitLevel)
|
||
{
|
||
if (unitLevel > MaxHeroCount)
|
||
MaxHeroCount = Mathf.Min(3, (int)unitLevel);
|
||
}
|
||
|
||
//获取英雄的升级费用值
|
||
public int GetHeroFinishTaskCost(GiantType giant, float discount = 1f)
|
||
{
|
||
foreach (var heroType in HeroList)
|
||
if (heroType.GiantType == giant)
|
||
{
|
||
if(GetHeroTask(giant,out var task))
|
||
{
|
||
var baseCost = task.GetFinishCost() * (int)heroType.UnitLevel;
|
||
if (discount < 1f && discount > 0f)
|
||
return Mathf.CeilToInt(baseCost * discount);
|
||
return baseCost;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
public uint GetHeroLevel(GiantType giant)
|
||
{
|
||
foreach (var heroType in HeroList)
|
||
if (heroType.GiantType == giant)
|
||
return heroType.UnitLevel;
|
||
return 0;
|
||
}
|
||
|
||
public uint GetMaxHeroLevelByChessType(ChessType chessType)
|
||
{
|
||
uint ret = 0;
|
||
foreach (var heroType in HeroList)
|
||
{
|
||
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(heroType, out var info)) continue;
|
||
if (info.ChessType != chessType) continue;
|
||
if (heroType.UnitLevel > ret) ret = heroType.UnitLevel;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
//返回现在外部的herobutton应该显示谁的头像
|
||
public Sprite GetHeroButtonSprite()
|
||
{
|
||
var giant = GetLeaderGiantType();
|
||
if (HeroCount > 0)
|
||
giant = HeroList[HeroCount - 1].GiantType;
|
||
if (!Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(new UnitFullType(UnitType.Giant, giant, 1),
|
||
out var info))
|
||
return null;
|
||
if (!Table.Instance.UnitTypeDataAssets.GetUnitSpriteByInfo(info,Main.MapData.PlayerMap.SelfPlayerData,out var sprite))
|
||
return null;
|
||
return sprite;
|
||
}
|
||
|
||
//返回现在外部的herobutton是否应该显示提示符号
|
||
public bool GetHeroButtonHint()
|
||
{
|
||
return HeroCount < MaxHeroCount;
|
||
}
|
||
}
|
||
|
||
// 命名器的数据
|
||
[MemoryPackable]
|
||
public partial class PlayerNamerData
|
||
{
|
||
//City命名
|
||
[MemoryPackInclude]
|
||
private List<CityLibrary> _cityNamer;
|
||
[MemoryPackInclude]
|
||
private int _cityNamerPointer;
|
||
|
||
[MemoryPackConstructor]
|
||
public PlayerNamerData()
|
||
{
|
||
_cityNamer = new List<CityLibrary>();
|
||
_cityNamerPointer = 0;
|
||
}
|
||
|
||
public void Init(CivEnum civEnum)
|
||
{
|
||
var civId = Table.Instance.TransCivEnumToCivId(civEnum);
|
||
if (!Table.Instance.CivDataAssets.GetCivInfo(civId, out var info))
|
||
{
|
||
Debug.LogError($"PlayerNamerData.Init: CivDataAssets missing for CivEnum={civEnum}, CivId={civId}, fallback to CivId=0");
|
||
// fallback: 借用埃及的城市名,防止空列表导致崩溃
|
||
if (Table.Instance.CivDataAssets.GetCivInfo(CivEnum.Egyptian, out var fallbackInfo))
|
||
{
|
||
foreach (var t in fallbackInfo.CityInfoList)
|
||
_cityNamer.Add(t.CityNameEnum);
|
||
}
|
||
else
|
||
{
|
||
_cityNamer.Add(CityLibrary.Cairo);
|
||
}
|
||
Table.Instance.ShuffleRange(_cityNamer, 1);
|
||
return;
|
||
}
|
||
foreach (var t in info.CityInfoList)
|
||
_cityNamer.Add(t.CityNameEnum);
|
||
Table.Instance.ShuffleRange(_cityNamer, 1);
|
||
}
|
||
public PlayerNamerData(PlayerNamerData copyData)
|
||
{
|
||
_cityNamer = new List<CityLibrary>(copyData._cityNamer);
|
||
_cityNamerPointer = copyData._cityNamerPointer;
|
||
}
|
||
|
||
public void DeepCopy(PlayerNamerData copyData)
|
||
{
|
||
_cityNamer = new List<CityLibrary>(copyData._cityNamer);
|
||
_cityNamerPointer = copyData._cityNamerPointer;
|
||
}
|
||
|
||
public CityLibrary GetSetNextCity()
|
||
{
|
||
if (_cityNamer.Count == 0)
|
||
{
|
||
Debug.LogError($"PlayerNamerData: _cityNamer is empty, CivDataAssets may be missing for this player");
|
||
return CityLibrary.Cairo; // fallback to prevent crash
|
||
}
|
||
if (_cityNamerPointer >= _cityNamer.Count)
|
||
_cityNamerPointer = 1;//重复命名的时候,跳过首都
|
||
if (_cityNamerPointer >= _cityNamer.Count)
|
||
_cityNamerPointer = 0; // 如果只有一个城市,回到0
|
||
return _cityNamer[_cityNamerPointer++];
|
||
}
|
||
|
||
public bool CheckIsCityNameSelfBuild(CityLibrary cityLibrary)
|
||
{
|
||
for (int i = 0; i < _cityNamerPointer; i++)
|
||
{
|
||
if (i >= _cityNamer.Count) break;
|
||
if (_cityNamer[i] == cityLibrary) return true;
|
||
}
|
||
return false;
|
||
|
||
}
|
||
}
|
||
|
||
|
||
// 文化值系统
|
||
[MemoryPackable]
|
||
public partial class PlayerCultureInfo
|
||
{
|
||
public int PlayerCulture;
|
||
public List<CultureCardType> CultureCardList;
|
||
public List<CultureCardBase> CardList;
|
||
|
||
|
||
[MemoryPackConstructor]
|
||
public PlayerCultureInfo()
|
||
{
|
||
PlayerCulture = 0;
|
||
CultureCardList = new List<CultureCardType>();
|
||
CardList = new List<CultureCardBase>();
|
||
}
|
||
|
||
public PlayerCultureInfo(PlayerCultureInfo copyData)
|
||
{
|
||
PlayerCulture = copyData.PlayerCulture;
|
||
CultureCardList = new List<CultureCardType>();
|
||
foreach (var card in copyData.CultureCardList) CultureCardList.Add(card);
|
||
CardList = new List<CultureCardBase>();
|
||
foreach (var card in copyData.CardList)
|
||
{
|
||
CardList.Add(CultureCardFactory.GetCultureCardBase(card.GetCardType()));
|
||
}
|
||
}
|
||
|
||
public void DeepCopy(PlayerCultureInfo copyData)
|
||
{
|
||
PlayerCulture = copyData.PlayerCulture;
|
||
CultureCardList ??= new List<CultureCardType>();
|
||
CultureCardList.Clear();
|
||
foreach (var card in copyData.CultureCardList) CultureCardList.Add(card);
|
||
CardList ??= new List<CultureCardBase>();
|
||
CardList.Clear();
|
||
foreach (var card in copyData.CardList)
|
||
{
|
||
CardList.Add(CultureCardFactory.GetCultureCardBase(card.GetCardType()));
|
||
}
|
||
}
|
||
|
||
// 购买文化卡
|
||
public bool TryBuyCultureCard(MapData map, PlayerData player, CultureCardType cardType)
|
||
{
|
||
if (!Table.Instance.CultureCardDataAssets.GetCultureCardInfo(cardType, out var info)) return false;
|
||
if (!info.CheckEmpireCanUseCard(player.Empire)) return false;
|
||
if (!info.CheckPrerequisiteCardsOwned(player)) return false;
|
||
if (!info.CheckPlayerCanAfford(player)) return false;
|
||
info.SpendCost(player);
|
||
CultureCardList.Add(cardType);
|
||
var card = CultureCardFactory.GetCultureCardBase(cardType);
|
||
card.OnGetCultureCard(map, player);
|
||
CardList.Add(card);
|
||
return true;
|
||
}
|
||
|
||
// 能否购买文化卡
|
||
public bool CheckCanBuyCultureCard(MapData map, PlayerData player, CultureCardType cardType)
|
||
{
|
||
if (!Table.Instance.CultureCardDataAssets.GetCultureCardInfo(cardType, out var info)) return false;
|
||
if (!info.CheckEmpireCanUseCard(player.Empire)) return false;
|
||
if (!info.CheckPrerequisiteCardsOwned(player)) return false;
|
||
if (!info.CheckPlayerCanAfford(player)) return false;
|
||
return true;
|
||
}
|
||
|
||
public void OnTurnStart(MapData map, PlayerData player)
|
||
{
|
||
/*if (!player.IsSurvival) return;
|
||
if (map.CityMap.GetCityById(player.CradleCityId, out var cradleCity))
|
||
{
|
||
if (cradleCity.Player(map) == player) PlayerCulture += 3;
|
||
}
|
||
var cityList = new List<CityData>();
|
||
map.GetCityDataListByPlayerId(player.Id, cityList);
|
||
foreach (var city in cityList)
|
||
{
|
||
if (city == cradleCity) continue;
|
||
if (city.IsCapital) PlayerCulture += 1;
|
||
}*/
|
||
}
|
||
|
||
public void OnUnitTransform(MapData map, UnitData unit, UnitFullType origin, UnitFullType target)
|
||
{
|
||
if (origin.UnitLevel == 2 && target.UnitLevel == 3) PlayerCulture += 15;
|
||
if (origin.UnitLevel == 3 && target.UnitLevel == 4) PlayerCulture += 50;
|
||
}
|
||
|
||
public void OnWonderBuild(MapData map, PlayerData player, WonderTypeEnum wonderType)
|
||
{
|
||
PlayerCulture += 5;
|
||
}
|
||
}
|
||
}
|