TH1/Unity/Assets/Scripts/TH1_UI/View/Info/UIInfoGridInfoView.cs
2026-06-27 02:30:03 +08:00

1105 lines
41 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 Animancer;
using Logic;
using Logic.Action;
using Logic.Audio;
using Logic.CrashSight;
using Logic.Multilingual;
using Logic.Skill;
using RuntimeData;
using TH1_Core.Events;
using TH1_Core.Managers;
using TH1_Logic.Config;
using TH1_Logic.Core;
using TH1_Logic.MatchConfig;
using TH1_Presentation.Sequencer.Task;
using TH1_UI.Components;
using TH1_UI.HintUI;
using TH1Renderer;
using TH1Resource;
using TMPro;
using UI.HintUI;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
namespace TH1_UI.View.Info
{
public class UIInfoGridInfoView : Base.View
{
public Button closeButton;
public ViDelegateAssisstant.Dele OnBtnCloseClick;
[Header("Color相关")]
public Image BGUnder;
public Image BGSurface;
public Image BG2Under;
public Image BG2Surface;
public Image AvatarTitleBG;
[Header("GridInfo相关")]
public GameObject GridInfoArea;
public GameObject GridPreview;
public GameObject GridCamera;
public TextMeshProUGUI GridInfoTitle;
public TextMeshProUGUI GridInfoDesc;
public TextMeshProUGUI GridInfoTerrainTxt;
public TextMeshProUGUI GridInfoResourceTxt;
public TextMeshProUGUI GridInfoBuildingTxt;
public HintTrigger GridInfoHint;
[Header("CityInfo相关")]
public GameObject CityInfoArea;
public GameObject CityPreview;
public GameObject CityCamera;
public GameObject CityInfoDataArea;
public TextMeshProUGUI CityInfoTitle;
public TextMeshProUGUI CityInfoDesc;
public TextMeshProUGUI CityInfoCoinTxt;
public TextMeshProUGUI CityInfoTechTxt;
public TextMeshProUGUI CityInfoCultureTxt;
public TextMeshProUGUI CityInfoDiamondTxt;
public TextMeshProUGUI CityInfoPopulationTxt;
public TextMeshProUGUI CityInfoConnectTxt;
[Header("UnitInfo相关")]
public GameObject UnitInfoArea;
public Image UnitPreview;
public Image HeroBG;
public GameObject SkillArea;
public TextMeshProUGUI UnitInfoTitle;
public TextMeshProUGUI UnitInfoDesc;
public TextMeshProUGUI UnitInfoAttackTxt;
public TextMeshProUGUI UnitInfoDefendTxt;
public TextMeshProUGUI UnitInfoHealthTxt;
public TextMeshProUGUI UnitInfoMoveTxt;
public TextMeshProUGUI UnitInfoRangeTxt;
public TextMeshProUGUI UnitInfoSightTxt;
public TextMeshProUGUI UnitInfoMobilityTxt;
public HintTrigger UnitInfoHint;
public GameObject UnitCity;
public Button UnitCityBtn;
public TextMeshProUGUI UnitCityText;
[Header("TaskInfo相关")]
public GameObject TaskArea;
public TextMeshProUGUI TaskDesc;
[Header("CommonInfo相关")]
public GameObject AvatarArea;
public GameObject AvatarHint;
public GameObject Avatar;
public GameObject AvatarTitleArea;
public TextMeshProUGUI AvatarTitle;
public Image AvatarImg;
public Button AvatarBtn;
public GameObject ActionArea;
public TextMeshProUGUI ActionHint;
public RectTransform VerticalGroup;
public GameObject ActionCirclePrefab;
public GameObject SkillCirclePrefab;
[Header("ActionPoint相关")]
public GameObject APBox;
public Transform APContainer;
public GameObject ActionPointPrefab;
private List<UIInfoCommonBaseActionCircleMono> ActionCircleMonoList = null;
private List<UIInfoCommonBaseSkillCircleMono> SkillCircleMonoList = null;
private List<UIInfoGridInfoActionPointMono> ActionPointMonoList = null;
private int _actionCount;
protected override void OnInit()
{
base.OnInit();
ActionCircleMonoList = new List<UIInfoCommonBaseActionCircleMono>();
SkillCircleMonoList = new List<UIInfoCommonBaseSkillCircleMono>();
ActionPointMonoList = new List<UIInfoGridInfoActionPointMono>();
ActionAreaInit();
SkillAreaInit();
APBoxInit();
BindUnitInfoItems();
closeButton.onClick.RemoveAllListeners();
closeButton.onClick.AddListener(() => { OnBtnCloseClick?.Invoke(); });
//CheckPanel.InitStart(RefreshStatus);
}
private void BindUnitInfoItems()
{
if (UnitInfoSightTxt != null || UnitInfoArea == null) return;
foreach (var child in UnitInfoArea.GetComponentsInChildren<Transform>(true))
{
if (child.name != "SightInfoItem") continue;
var itemInfo = child.GetComponent<UIInfoGridInfoItemInfoMono>();
if (itemInfo != null && itemInfo.ValueText != null)
{
UnitInfoSightTxt = itemInfo.ValueText;
return;
}
UnitInfoSightTxt = child.Find("ValueText")?.GetComponent<TextMeshProUGUI>();
return;
}
}
//清除所有actioncircle,重新开始生成
private void ActionAreaInit()
{
if (gameObject == null) return;
for (int i = ActionArea.transform.childCount - 1; i >= 0; i--)
{
Transform child = ActionArea.transform.GetChild(i);
// 判断组件是否存在
if (child.GetComponent<UIInfoCommonBaseActionCircleMono>() != null)
{
// 正确调用 Destroy 方法,销毁子对象的 GameObject
Destroy(child.gameObject);
}
}
ActionCircleMonoList.Clear();
ActionCircleMonoList = new List<UIInfoCommonBaseActionCircleMono>();
}
private void SkillAreaInit()
{
if (gameObject == null) return;
for (int i = SkillArea.transform.childCount - 1; i >= 0; i--)
{
Transform child = SkillArea.transform.GetChild(i);
// 判断组件是否存在
if (child.GetComponent<UIInfoCommonBaseSkillCircleMono>() != null)
{
// 正确调用 Destroy 方法,销毁子对象的 GameObject
Destroy(child.gameObject);
}
}
SkillCircleMonoList.Clear();
SkillCircleMonoList = new List<UIInfoCommonBaseSkillCircleMono>();
}
public void SetContent(ShowUIInfoGridInfo evt)
{
TryMarkCityBorderForSelection(evt);
SetContentAvatar(evt);
SetContentGrid(evt);
SetContentUnit(evt);
SetContentCity(evt);
SetContentSkill(evt);
SetContentAction(evt);
SetContentAPBox(evt);
SetContentTask(evt);
LayoutRebuilder.ForceRebuildLayoutImmediate(VerticalGroup);
}
// "始终显示选中地块所属城市边界"设置打开时,把宿主格所属城市的整片领土 mark 起来。
// 仅在选中地块 (GridInfo) 时生效;选中单位不触发——单位选中不应连带亮起城市边界。
// 关闭时不做事,仍由 SetContentCity 处理"点城市中心格才亮"的旧行为。
// CancelAllHighlight 已经在每次点击前清零全图 mark所以这里只管置 true。
private void TryMarkCityBorderForSelection(ShowUIInfoGridInfo evt)
{
if (!ConfigManager.Instance.Config.AlwaysShowCityBorder) return;
if (evt.type != UIGridInfoType.grid) return;
if (!Main.MapData.GetCityDataByTerritoryGid(evt.id, out var city)) return;
foreach (var gid in city.Territory.TerritoryArea)
if (Main.MapData.GridMap.GetGridDataByGid(gid, out var gridData))
gridData.CityBorderRenderMark = true;
}
private void SetContentAvatar(ShowUIInfoGridInfo evt)
{
AvatarArea.SetActive(true);
PlayerData player = null;
switch (evt.type)
{
case UIGridInfoType.grid: // City is on a grid, so its ID is a GID
Main.MapData.GetPlayerDataByTerritoryGridId(evt.id, out player);
break;
case UIGridInfoType.unit:
Main.MapData.GetPlayerDataByUnitId(evt.id, out player);
break;
}
if (player == null || !Table.Instance.PlayerDataAssets.GetPlayerInfo(player, out var info))
{
AvatarTitleArea.SetActive(false);
AvatarHint.SetActive(false);
Avatar.SetActive(false);
//设置中立领土颜色
SetColor(Table.Instance.PlayerDataAssets.CommonColor);
return;
}
AvatarTitleArea.SetActive(true);
AvatarHint.SetActive(true);
Avatar.SetActive(true);
Table.Instance.PlayerDataAssets.GetPlayerInfo(player, out var playerInfo);
SetColor(playerInfo?.Color ?? Color.black);
if(!Main.MapData.PlayerMap.SelfPlayerData.MeetPlayers.Contains(player.Id))
{
AvatarBtn.onClick.RemoveAllListeners();
AvatarImg.sprite = Table.Instance.PlayerDataAssets.CommonPlayerAvatar;
AvatarTitle.text = "???";
LayoutRebuilder.ForceRebuildLayoutImmediate(AvatarTitleArea.GetComponent<RectTransform>());
return;
}
AvatarImg.sprite = info.LeaderAvatar;
// 如果当前显示的是unit且该unit被视为英雄用英雄头像替换
if (evt.type == UIGridInfoType.unit
&& Main.MapData.UnitMap.GetUnitDataByUnitId(evt.id, out var heroUnit)
&& heroUnit.TreatedAsHero(Main.MapData, null, out var heroGiantType)
&& heroGiantType != GiantType.None
&& Table.Instance.HeroDataAssets.GetHeroInfo(heroGiantType, out var heroInfoData)
&& heroInfoData.HeroAvatar != null)
{
AvatarImg.sprite = heroInfoData.HeroAvatar;
}
AvatarTitle.text = Logic.PlayerLogic.GetDisplayForceName(Main.MapData, player);
LayoutRebuilder.ForceRebuildLayoutImmediate(AvatarTitleArea.GetComponent<RectTransform>());
AvatarBtn.onClick.RemoveAllListeners();
AvatarBtn.onClick.AddListener(() =>
{
EventManager.Publish(new ShowUIInfoDiplomacy() { Pid = player.Id });
//因为gridinfo和diplomacyinfo 同属infoUI打开diploInfo会自动关闭gridinfo这里如果再主动关闭自身会重复关闭导致错误
//OnBtnCloseClick?.Invoke();
});
}
private void SetColor(Color color)
{
var colorUnder = color;
colorUnder.a = 0.4f;
BGUnder.color = colorUnder;
BGSurface.color = color;
BG2Under.color = colorUnder;
BG2Surface.color = color;
AvatarTitleBG.color = color;
}
private void SetContentGrid(ShowUIInfoGridInfo evt)
{
if (evt.type != UIGridInfoType.grid || !Main.MapData.GridMap.GetGridDataByGid(evt.id, out var grid) || Main.MapData.GetCityDataByGid(evt.id, out var _))
{
GridInfoArea.SetActive(false);
return;
}
GridInfoArea.SetActive(true);
//Step #1 GridPreview
GameObject clone =
Instantiate(MapRenderer.Instance.ROGridMap[evt.id].GetROGrid() as GameObject);
for (int i = GridPreview.transform.childCount - 1; i >= 0; i--)
Destroy(GridPreview.transform.GetChild(i).gameObject);
clone.transform.SetParent(GridPreview.transform);
clone.transform.localPosition = new Vector3(0,15,0);
clone.transform.Find("SelectHighlight").gameObject.SetActive(false);
SetLayerRecursively(clone, LayerMask.NameToLayer("Preview"));
GridCamera.SetActive(true);
Timer.Instance.TimerRegister(GridCamera, () => { GridCamera.SetActive(false); }, 0.001f,"HideCamera");
//Step #2 Title & Desc
//title 逻辑 -> building > resource > forest > mountain > plain
string title_DECODE = "";
string desc_DECODE = "";
Empire empire = new Empire(grid.CivEnum,grid.Player(Main.MapData)?.ForceEnum??ForceEnum.Common);
var spType = grid.GetTopGridSpType();
//如果是奇观
if (grid.Resource == ResourceType.Wonder)
{
// Name / Desc 走 LibraryDataAssets图鉴文案其他奇观信息仍由 WonderInfo 提供
if (Table.Instance.LibraryDataAssets.GetLibraryInfoByWonder(grid.Wonder, out var libraryData))
{
title_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(libraryData.Name);
desc_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(libraryData.Desc);
}
}
//如果显示建筑或者资源
else if (grid.Resource != ResourceType.None && Main.MapData.PlayerMap.SelfPlayerData.CanShowResource(grid.Resource))
{
title_DECODE = Table.Instance.GridAndResourceDataAssets.GetResourceNameWithLevel_DECODE(grid,empire,spType);
//var spr = Table.Instance.GridAndResourceDataAssets.GetResourceSprite(grid.Resource, grid.Player(Main.MapData), grid);
Table.Instance.GridAndResourceDataAssets.GetResourceInfo(grid.Resource, out var resourceInfo);
desc_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(resourceInfo.GetResourceDesc(empire,spType));
}
//如果显示树木
else if(grid.Vegetation == Vegetation.Trees)
{
Table.Instance.GridAndResourceDataAssets.GetVegetationInfo(out var vegetationInfo);
title_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(vegetationInfo.GetResourceName(empire,spType));
desc_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(vegetationInfo.GetResourceDesc(empire,spType));
}
//如果显示山脉
else if(grid.Feature == TerrainFeature.Mountain)
{
Table.Instance.GridAndResourceDataAssets.GetMountainInfo(out var mountainInfo);
title_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(mountainInfo.ResourceName);
desc_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(mountainInfo.ResourceDesc);
}
//如果显示地形
else
{
Table.Instance.GridAndResourceDataAssets.GetTerrainInfo(grid.Terrain, out var terrainInfo);
title_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(terrainInfo.ResourceName);
desc_DECODE = MultilingualManager.Instance.GetMultilingualTextSafe(terrainInfo.ResourceDesc);
}
GridInfoTitle.text = title_DECODE;
GridInfoDesc.text = desc_DECODE;
//Step #3 设置详细info
//resource / building
if (grid.Resource != ResourceType.None && Table.Instance.GridAndResourceDataAssets.GetResourceInfo(grid.Resource, out var resourceInfo2))
{
if (resourceInfo2.ResourceSubType == ResourceSubType.Building)
{
GridInfoBuildingTxt.text = Table.Instance.GridAndResourceDataAssets.GetResourceNameWithLevel_DECODE(grid,empire,spType);
//处理盖在建筑下面的资源
if (grid.ResourceUnderBuilding != ResourceType.None &&
Table.Instance.GridAndResourceDataAssets.GetResourceInfo(grid.ResourceUnderBuilding,
out var resourceInfo3) && resourceInfo3.ResourceSubType == ResourceSubType.Resource)
MultilingualManager.Instance.SetUIText(GridInfoResourceTxt,
Table.Instance.GridAndResourceDataAssets.GetResourceName(grid.ResourceUnderBuilding,empire,spType));
else GridInfoResourceTxt.text = "-";
}
else
{
GridInfoBuildingTxt.text = "-";
if(Main.MapData.PlayerMap.SelfPlayerData.CanShowResource(grid.Resource))
MultilingualManager.Instance.SetUIText(GridInfoResourceTxt, Table.Instance.GridAndResourceDataAssets.GetResourceName(grid.Resource,empire,spType));
else GridInfoResourceTxt.text = "-";
}
}
else
{
GridInfoBuildingTxt.text = "-";
GridInfoResourceTxt.text = "-";
}
//terrain
if (grid.Feature == TerrainFeature.Mountain)
{
Table.Instance.GridAndResourceDataAssets.GetMountainInfo(out var mountainInfo);
MultilingualManager.Instance.SetUIText(GridInfoTerrainTxt, mountainInfo.ResourceName);
}
else if (grid.Vegetation == Vegetation.Trees)
{
Table.Instance.GridAndResourceDataAssets.GetVegetationInfo(out var vegetationInfo);
MultilingualManager.Instance.SetUIText(GridInfoTerrainTxt, vegetationInfo.ResourceName);
}
else
{
Table.Instance.GridAndResourceDataAssets.GetTerrainInfo(grid.Terrain,out var terrainInfo);
MultilingualManager.Instance.SetUIText(GridInfoTerrainTxt, terrainInfo.ResourceName);
}
//处理GeoHintInfo
//TODO 不能在这里修改mapData这是错误的 必须要调用action 来修改mapData
grid.UpdateGeoInfo(Main.MapData);
if (grid.GeoIdList?.Count > 0)
{
GridInfoHint.gameObject.SetActive(true);
GridInfoHint.DataProvider.HintDataType = HintDataType.GeoData;
GridInfoHint.DataProvider.GeoIdList = grid.GetRealTimeGeoIdList();
}
else
{
GridInfoHint.gameObject.SetActive(false);
}
}
private void SetContentUnit(ShowUIInfoGridInfo evt)
{
if (evt.type != UIGridInfoType.unit || !Main.MapData.UnitMap.GetUnitDataByUnitId(evt.id, out var unitData))
{
UnitInfoArea.SetActive(false);
if (HeroBG != null) HeroBG.gameObject.SetActive(false);
return;
}
UnitInfoArea.SetActive(true);
// 获取关联数据
Main.MapData.GetPlayerDataByUnitId(unitData.Id, out var playerData);
Table.Instance.UnitTypeDataAssets.GetUnitTypeInfo(unitData.UnitFullType, out var unitTypeInfo);
// GiantJuggernaut 是 Giant 上船形态,真实英雄信息在 CarryUnitFullType
var heroFullType = unitData.UnitFullType.UnitType == UnitType.GiantJuggernaut
? unitData.CarryUnitFullType
: unitData.UnitFullType;
// Step #1: Unit Preview
if (Table.Instance.UnitTypeDataAssets.GetUnitSprite(Main.MapData, unitData, out var spr))
UnitPreview.sprite = spr;
// HeroBG: Giant单位且在Library中有Illustration时显示
if (HeroBG != null)
{
LibraryGiantData libData = null;
bool showHeroBG = unitData.UnitType is UnitType.Giant or UnitType.GiantJuggernaut
&& heroFullType.GiantType != GiantType.None
&& Table.Instance.LibraryDataAssets.GetLibraryInfoByGiant(heroFullType.GiantType, out libData)
&& libData.Illust != null;
HeroBG.gameObject.SetActive(showHeroBG);
if (showHeroBG)
HeroBG.sprite = libData.Illust;
}
// Step #2: Title & Description
// 逻辑参考 BottomInfoUI.cs -> UpdateInfoUnit
string title = MultilingualManager.Instance.GetMultilingualTextSafe(unitTypeInfo.Name);
//处理Officer的情况
if (unitData.IsOfficer()) title += "("+ MultilingualManager.Instance.GetMultilingualText(Table.Instance.TextDataAssets.UnitOfficerName)+")"; // 示例
else if (unitData.IsPrepareOfficer()) title += $"({unitData.GetKillCount()}/3)";
UnitInfoTitle.text = title;
MultilingualManager.Instance.SetUIText(UnitInfoDesc, unitTypeInfo.Desc);
// Step #3: Core Stats
UnitInfoHealthTxt.text = $"{unitData.Health}/{unitData.GetMaxHealth()}";
UnitInfoAttackTxt.text = unitData.GetAttackShowString(Main.MapData);
UnitInfoDefendTxt.text = unitData.GetDefenseShowString(Main.MapData);
UnitInfoMoveTxt.text = unitData.GetMoveRangeShowString(Main.MapData);
UnitInfoRangeTxt.text = unitData.GetAttackRangeShowString(Main.MapData);
if (UnitInfoSightTxt != null) UnitInfoSightTxt.text = unitData.GetSightRange(Main.MapData).ToString();
UnitInfoMobilityTxt.text = Table.Instance.MobilityDataAssets.GetLandTypeText_DECODE(unitTypeInfo.LandType);
//Step #4 处理Giant Hint
bool isgiant = unitData.UnitFullType.UnitType is UnitType.Giant or UnitType.GiantJuggernaut;
UnitInfoHint.gameObject.SetActive(isgiant);
if (isgiant)
{
UnitInfoHint.DataProvider.HintDataType = HintDataType.TextDataGiantUpgrate;
UnitInfoHint.DataProvider.UnitFullType = heroFullType;
}
//Step #5 处理UnitCity - 仅我方单位显示所属城市
var selfPlayer = Main.MapData.PlayerMap.SelfPlayerData;
bool showCity = playerData != null && playerData.Id == selfPlayer.Id;
CityData unitCity = null;
if (showCity)
{
unitCity = unitData.City(Main.MapData);
showCity = unitCity != null;
}
// 处理UnitCity显示/隐藏
if (UnitCity != null)
{
UnitCity.SetActive(showCity);
if (showCity && UnitCityText != null && unitCity != null)
{
UnitCityText.gameObject.SetActive(true);
if (Table.Instance.CivDataAssets.GetCityInfo(unitCity.Name, out var cityInfo))
{
UnitCityText.text = MultilingualManager.Instance.GetMultilingualTextSafe(cityInfo.CityName);
}
}
}
// 处理按钮点击
if (UnitCityBtn != null)
{
UnitCityBtn.onClick.RemoveAllListeners();
if (showCity && unitCity != null)
{
// 使用局部变量捕获避免闭包问题
var capturedCity = unitCity;
UnitCityBtn.onClick.AddListener(() =>
{
var grid = capturedCity.Grid(Main.MapData);
if (grid != null)
{
// 先关闭当前面板,再显示新内容
OnBtnCloseClick?.Invoke();
EventManager.Publish(new ShowUIInfoGridInfo()
{
type = UIGridInfoType.grid,
id = grid.Id
});
}
});
}
}
}
private void SetContentCity(ShowUIInfoGridInfo evt)
{
if (evt.type != UIGridInfoType.grid || !Main.MapData.GetCityDataByGid(evt.id,out var city)
|| !Main.MapData.GetPlayerDataByCityId(city.Id, out var player))
{
CityInfoArea.SetActive(false);
return;
}
//TODO 这里临时去现实city的border
foreach (var gid in city.Territory.TerritoryArea)
if(Main.MapData.GridMap.GetGridDataByGid(gid,out var gridData))
gridData.CityBorderRenderMark = true;
CityInfoArea.SetActive(true);
// Step 1: Preview (Placeholder)
GameObject clone =
Instantiate(MapRenderer.Instance.ROGridMap[evt.id].GetROGrid() as GameObject);
for (int i = CityPreview.transform.childCount - 1; i >= 0; i--)
Destroy(CityPreview.transform.GetChild(i).gameObject);
clone.transform.SetParent(CityPreview.transform);
clone.transform.localPosition = new Vector3(0,15,0);
clone.transform.Find("SelectHighlight").gameObject.SetActive(false);
SetLayerRecursively(clone, LayerMask.NameToLayer("Preview"));
CityCamera.SetActive(true);
Timer.Instance.TimerRegister(CityCamera, () => { CityCamera.SetActive(false); }, 0.001f,"HideCamera");
// Step 2: Title & Description
Table.Instance.CivDataAssets.GetCityInfo(city.Name, out var cityInfo);
CityInfoTitle.text = MultilingualManager.Instance.GetMultilingualTextSafe(cityInfo.CityName) + " Lv." + city.Level;
MultilingualManager.Instance.SetUIText(CityInfoDesc, cityInfo.CityDescription);
// Step 3: Detailed Info (differs for self vs. others)
if (player.Id == Main.MapData.PlayerMap.SelfPlayerData.Id)
{
CityInfoDataArea.SetActive(true);
var tb = Table.Instance.TextDataAssets;
//被敌方单位站住的城市本回合不产出,UI显示0
var heldByEnemy = Main.CityLogic.IsCityHeldByEnemyUnit(Main.MapData, city);
var coinPerTurn = heldByEnemy ? 0 : Main.CityLogic.GetCityCoinPerTurn(Main.MapData, city);
var techPerTurn = heldByEnemy ? 0 : Main.CityLogic.GetCityTechPointPerTurn(Main.MapData, city);
CityInfoCoinTxt.text = "+" + coinPerTurn + MultilingualManager.Instance.GetMultilingualTextSafe(tb.GridInfoText_PerTurn);
CityInfoTechTxt.text = "+" + techPerTurn + MultilingualManager.Instance.GetMultilingualTextSafe(tb.GridInfoText_PerTurn);
//var culturePerTurn = Main.CityLogic.GetCityCulturePointPerTurn(Main.MapData, city);
//CityInfoCultureTxt.text = "+" + culturePerTurn + MultilingualManager.Instance.GetMultilingualTextSafe(tb.GridInfoText_PerTurn);
//CityInfoCultureTxt.gameObject.SetActive(culturePerTurn > 0);
CityInfoPopulationTxt.text = MultilingualManager.Instance.GetMultilingualTextSafe(tb.GridInfoText_Population) + Main.MapData.GetUnitCount(city.Id) + "/" + city.Level;
CityInfoDiamondTxt.text = city.ParkCount + MultilingualManager.Instance.GetMultilingualTextSafe(tb.GridInfoText_Diamond);
CityInfoConnectTxt.text = city.IsCapital ? MultilingualManager.Instance.GetMultilingualTextSafe(tb.GridInfoText_Capital) : (city.IsConnectedCapital ? MultilingualManager.Instance.GetMultilingualTextSafe(tb.GridInfoText_Connected) :
MultilingualManager.Instance.GetMultilingualTextSafe(tb.GridInfoText_Unconnected));
}
else
CityInfoDataArea.SetActive(false);
}
private void SetContentSkill(ShowUIInfoGridInfo evt)
{
if (evt.type == UIGridInfoType.grid)
{
SetContentGridStatusSkill(evt);
return;
}
if (evt.type != UIGridInfoType.unit)
{
SkillArea.SetActive(false);
return;
}
if (!Main.MapData.UnitMap.GetUnitDataByUnitId(evt.id, out var unit)) return;
var skillList = new List<SkillBase>();
foreach (var skill in unit.Skills)
{
var skillType = skill.GetSkillType();
if(!Table.Instance.SkillDataAssets.GetSkillInfo(skillType, out var info)
|| !SkillDataAssets.ShouldShowInUnitSkillList(skillType, info) || !skill.ShowSkill)continue;
skillList.Add(skill);
}
if (skillList.Count == 0)
{
SkillArea.SetActive(false);
return;
}
SkillArea.SetActive(true);
//Step #2 确定格位够不够
while (SkillCircleMonoList.Count < skillList.Count)
{
GameObject actionCircle = Instantiate(SkillCirclePrefab, SkillArea.transform, true);
actionCircle.GetComponent<RectTransform>().localScale = Vector3.one;
actionCircle.GetComponent<RectTransform>().localPosition = Vector3.zero;
SkillCircleMonoList.Add(actionCircle.GetComponent<UIInfoCommonBaseSkillCircleMono>());
}
//Step #3 skill分类sort序列
var skillSort = new List<int>();
var skillViewType = new List<SkillViewType>();
foreach (var t in skillList)
{
Table.Instance.SkillDataAssets.GetSkillInfo(t.GetSkillType(), out var info);
var viewType = info?.SkillViewType ?? SkillViewType.Normal;
if (t.HasTimeLimit && viewType != SkillViewType.Positive && viewType != SkillViewType.Negative)
viewType = SkillViewType.Positive;
skillViewType.Add(viewType);
}
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Normal)skillSort.Add(i);
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Special)skillSort.Add(i);
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Unique)skillSort.Add(i);
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Positive)skillSort.Add(i);
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Negative)skillSort.Add(i);
//Step #3 设置每一个action
//bool unitIsBanned = unit.IsFrozen();
for (int i = 0; i < SkillCircleMonoList.Count; i++)
if (i < skillSort.Count && skillSort[i] < skillList.Count)
{
var skill = skillList[skillSort[i]];
bool isBanned = skill.IsFrozen(unit);
SkillCircleMonoList[i].gameObject.SetActive(true);
SkillCircleMonoList[i].SetContent(skill,unit.UnitFullType,isBanned);
}
else
SkillCircleMonoList[i].gameObject.SetActive(false);
}
private void SetContentGridStatusSkill(ShowUIInfoGridInfo evt)
{
if (!Main.MapData.GridMap.GetGridDataByGid(evt.id, out var grid))
{
SkillArea.SetActive(false);
return;
}
var skillList = new List<SkillBase>();
if (Main.MapData.GetCityDataByGid(evt.id, out var city))
AddVisibleGridInfoSkills(city.Skills, skillList);
else
AddVisibleGridInfoSkills(grid.Skills, skillList);
var virtualIconList = new List<Sprite>();
var virtualHintList = new List<string>();
AddVisibleGridSpStatus(grid, virtualIconList, virtualHintList);
var count = skillList.Count + virtualIconList.Count;
if (count == 0)
{
SkillArea.SetActive(false);
return;
}
SkillArea.SetActive(true);
while (SkillCircleMonoList.Count < count)
{
GameObject actionCircle = Instantiate(SkillCirclePrefab, SkillArea.transform, true);
actionCircle.GetComponent<RectTransform>().localScale = Vector3.one;
actionCircle.GetComponent<RectTransform>().localPosition = Vector3.zero;
SkillCircleMonoList.Add(actionCircle.GetComponent<UIInfoCommonBaseSkillCircleMono>());
}
var skillSort = new List<int>();
var skillViewType = new List<SkillViewType>();
foreach (var skill in skillList)
{
Table.Instance.SkillDataAssets.GetSkillInfo(skill.GetSkillType(), out var info);
var viewType = info?.SkillViewType ?? SkillViewType.Normal;
if (skill.HasTimeLimit && viewType != SkillViewType.Positive && viewType != SkillViewType.Negative)
viewType = SkillViewType.Positive;
skillViewType.Add(viewType);
}
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Normal)skillSort.Add(i);
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Special)skillSort.Add(i);
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Unique)skillSort.Add(i);
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Positive)skillSort.Add(i);
for(int i = 0;i < skillList.Count; i++)if(skillViewType.Count > i && skillViewType[i] == SkillViewType.Negative)skillSort.Add(i);
var itemIndex = 0;
for (int i = 0; i < skillSort.Count; i++)
{
var skill = skillList[skillSort[i]];
SkillCircleMonoList[itemIndex].gameObject.SetActive(true);
SkillCircleMonoList[itemIndex].SetContent(skill, new UnitFullType());
itemIndex++;
}
var virtualBGColor = Table.Instance.SkillDataAssets.GetBGColor(SkillViewType.Special, false);
for (int i = 0; i < virtualIconList.Count; i++)
{
SkillCircleMonoList[itemIndex].gameObject.SetActive(true);
SkillCircleMonoList[itemIndex].SetStatusContent(virtualIconList[i], virtualBGColor, virtualHintList[i]);
itemIndex++;
}
for (int i = itemIndex; i < SkillCircleMonoList.Count; i++)
SkillCircleMonoList[i].gameObject.SetActive(false);
}
private void AddVisibleGridInfoSkills(List<SkillBase> source, List<SkillBase> target)
{
foreach (var skill in source)
{
var skillType = skill.GetSkillType();
if(!Table.Instance.SkillDataAssets.GetSkillInfo(skillType, out var info)
|| info.NotShow || !skill.ShowSkill)continue;
if (!HasCompleteGridInfoSkillDisplayData(skillType, skill)) continue;
target.Add(skill);
}
}
private bool HasCompleteGridInfoSkillDisplayData(SkillType skillType, SkillBase skill)
{
var unitFullType = new UnitFullType();
if (!Table.Instance.SkillDataAssets.GetSkillIcon(skillType, unitFullType, out var icon) || icon == null) return false;
if (!Table.Instance.SkillDataAssets.GetSkillNameAndDesc_DECODE(skillType, unitFullType, skill, out var name, out var desc)) return false;
return !string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(desc);
}
private void AddVisibleGridSpStatus(GridData grid, List<Sprite> iconList, List<string> hintList)
{
if (grid.SpTypeList == null || grid.SpTypeList.Count == 0) return;
var player = grid.Player(Main.MapData);
var empire = player?.Empire ?? new Empire(grid.CivEnum, ForceEnum.Common);
var addedTypes = new HashSet<GridSpType>();
foreach (var spType in grid.SpTypeList)
{
if (spType == GridSpType.None || spType == GridSpType.Max || !addedTypes.Add(spType)) continue;
if (spType == GridSpType.KasenBeastGuide && grid.GetSkill(SkillType.KasenBeastGuideGrid, out _)) continue;
if (!Table.Instance.GridAndResourceDataAssets.GetGridSpDisplayInfo(empire, spType, out var icon, out var nameRaw, out var descRaw)) continue;
if (icon == null || string.IsNullOrWhiteSpace(nameRaw) || string.IsNullOrWhiteSpace(descRaw)) continue;
var name = MultilingualManager.Instance.GetMultilingualTextSafe(nameRaw);
var desc = MultilingualManager.Instance.GetMultilingualTextSafe(descRaw);
iconList.Add(icon);
hintList.Add($"<color=orange>{name}</color><br>{desc}");
}
}
private void SetContentAction(ShowUIInfoGridInfo evt)
{
//观战模式非自己回合时不显示Action区域
if (Main.MapData.CurPlayer != Main.MapData.PlayerMap.SelfPlayerData)
{
ActionArea.SetActive(false);
_actionCount = 0;
return;
}
//Step #1 获取actionList
//ResourceCache.Instance.SpriteCache.ActionBGAvailable
var actionList = new List<ActionLogicBase>();
var cantTypeList = new List<ShowType>();
bool showAction = false;
var subType = evt.type;
CommonActionParams param = null;
if (subType == UIGridInfoType.unit && Main.MapData.UnitMap.GetUnitDataByUnitId(evt.id,out var unit))
{
param = new CommonActionParams(mapData:Main.MapData,playerData:Main.MapData.PlayerMap.SelfPlayerData,unitData:unit,mainObjectType:MainObjectType.Unit);
showAction = ActionLogicFactory.MainObjectCanShowAction(param, out actionList,out cantTypeList);
MultilingualManager.Instance.SetUIText(ActionHint,Table.Instance.TextDataAssets.GridInfoText_UnitInfoHint);
}
else if (subType == UIGridInfoType.grid && Main.MapData.GetCityDataByGid(evt.id,out var city))
{
param = new CommonActionParams(mapData:Main.MapData,playerData:Main.MapData.PlayerMap.SelfPlayerData,cityData:city,mainObjectType:MainObjectType.City);
showAction = ActionLogicFactory.MainObjectCanShowAction(param,out actionList,out cantTypeList);
MultilingualManager.Instance.SetUIText(ActionHint,Table.Instance.TextDataAssets.GridInfoText_CityInfoHint);
// Tutor 模式 + PlayerCannotTrainUnit 限制:禁止 City 的所有 Action生产单位/升级城市等)
if (Main.MapData.MatchSettlement.SettlementType == MatchSettlementType.Tutor
&& Main.MapData.MapConfig.MatchLimits != null
&& Main.MapData.MapConfig.MatchLimits.Contains(MatchLimitType.PlayerCannotTrainUnit))
{
actionList.Clear();
cantTypeList.Clear();
showAction = false;
}
}
else if (subType == UIGridInfoType.grid && Main.MapData.GridMap.GetGridDataByGid(evt.id,out var grid))
{
param = new CommonActionParams(mapData:Main.MapData,Main.MapData.PlayerMap.SelfPlayerData,gridData:grid,mainObjectType:MainObjectType.Grid);
showAction = ActionLogicFactory.MainObjectCanShowAction(param,out actionList,out cantTypeList);
if(grid.Resource is ResourceType.Military or ResourceType.RemiliaMilitary)
MultilingualManager.Instance.SetUIText(ActionHint,Table.Instance.TextDataAssets.GridInfoText_CityInfoHint);
MultilingualManager.Instance.SetUIText(ActionHint,Table.Instance.TextDataAssets.GridInfoText_GridInfoHint);
}
if (!showAction)
{
// 我方Unit即使没有Action也保留ActionArea用于显示APBox但隐藏标题
bool isSelfUnit = subType == UIGridInfoType.unit
&& Main.MapData.UnitMap.GetUnitDataByUnitId(evt.id, out var selfCheckUnit)
&& Main.MapData.GetPlayerDataByUnitId(evt.id, out var selfCheckPlayer)
&& selfCheckPlayer.Id == Main.MapData.PlayerMap.SelfPlayerData.Id;
if (isSelfUnit)
{
ActionArea.SetActive(true);
ActionHint.gameObject.SetActive(false);
// 隐藏所有ActionCircle
for (int i = 0; i < ActionCircleMonoList.Count; i++)
ActionCircleMonoList[i].gameObject.SetActive(false);
_actionCount = 0;
return;
}
ActionArea.SetActive(false);
//_actionCount必须设置为0因为其他地方会用到
_actionCount = 0;
return;
}
ActionArea.SetActive(true);
ActionHint.gameObject.SetActive(true);
//Step #2 确定格位够不够
while (ActionCircleMonoList.Count < actionList.Count)
{
GameObject actionCircle = Instantiate(ActionCirclePrefab, ActionArea.transform, true);
actionCircle.GetComponent<RectTransform>().localScale = Vector3.one;
actionCircle.GetComponent<RectTransform>().localPosition = Vector3.zero;
ActionCircleMonoList.Add(actionCircle.GetComponent<UIInfoCommonBaseActionCircleMono>());
}
//Step #2.5 过滤掉未在ActionData中配置的action
for (int i = actionList.Count - 1; i >= 0; i--)
{
if (!Table.Instance.ActionDataAssets.GetActionInfo(actionList[i].ActionId, out _))
{
actionList.RemoveAt(i);
cantTypeList.RemoveAt(i);
}
}
_actionCount = actionList.Count;
//Step #3 设置每一个action
for (int i = 0; i < ActionCircleMonoList.Count; i++)
{
ActionCircleMonoList[i].gameObject.SetActive(i < actionList.Count);
if (i >= actionList.Count) continue;
ActionCircleMonoList[i].SetContent(actionList[i].ActionId,cantTypeList[i],param,OnActionCircleClicked);
}
}
public void ExecuteAction(ExecuteUIInfoGridInfo evt)
{
if(evt.idx >= ActionCircleMonoList.Count || evt.idx >= _actionCount) return;
ActionCircleMonoList[(int)evt.idx].ClickButton.onClick.Invoke();
}
private void OnActionCircleClicked(CommonActionId actionId, CommonActionParams param)
{
var action = ActionLogicFactory.GetActionLogic(actionId);
if (action.CheckCan(param) && action.CompleteExecute(param))
{
// Action执行成功后立即刷新APBox确保关闭/重开面板时显示最新状态
if (param.UnitData != null)
RefreshActionPoints(param.UnitData);
OnBtnCloseClick?.Invoke();
//这里特殊处理 点击了action但是不关闭gridInfo而是让单位继续行动的情况。后续需要将这个属性归化
if(action.ActionId.UnitActionType == UnitActionType.AYAMOVEAGAIN)
PresentationManager.EnqueueTask(new InputSequencerTask(MapRenderer.Instance.SelectUnitData.Grid(Main.MapData)));
}
}
//清除所有ActionPoint,重新开始
private void APBoxInit()
{
if (APContainer == null) return;
for (int i = APContainer.childCount - 1; i >= 0; i--)
{
Transform child = APContainer.GetChild(i);
if (child.GetComponent<UIInfoGridInfoActionPointMono>() != null)
Destroy(child.gameObject);
}
ActionPointMonoList.Clear();
}
/// <summary>
/// 根据当前GridInfo展示的类型决定APBox是否显示以及填充ActionPoint列表。
/// 仅在展示Unit的ActionArea时才显示APBox。
/// </summary>
private void SetContentAPBox(ShowUIInfoGridInfo evt)
{
// 只有当展示的是Unit并且ActionArea可见时才显示APBox
if (evt.type != UIGridInfoType.unit
|| !Main.MapData.UnitMap.GetUnitDataByUnitId(evt.id, out var unitData)
|| !ActionArea.activeSelf)
{
if (APBox != null) APBox.SetActive(false);
return;
}
if (APBox != null) APBox.SetActive(true);
RefreshActionPoints(unitData);
}
/// <summary>
/// 刷新APContainer下ActionPoint的数量和显示内容。
/// 根据unitData里的行动点信息动态生成/复用/隐藏ActionPoint对象。
/// </summary>
public void RefreshActionPoints(RuntimeData.UnitData unitData)
{
if (APContainer == null || ActionPointPrefab == null || unitData == null) return;
// 将ActionPoint字典展开为一个平铺列表每个点一个条目
var flatList = new List<RuntimeData.ActionPointType>();
if (unitData.ActionPoint != null)
{
foreach (var kv in unitData.ActionPoint)
{
for (int j = 0; j < kv.Value; j++)
flatList.Add(kv.Key);
}
}
// 如果总量为0仍然显示一个Empty
int needCount = flatList.Count > 0 ? flatList.Count : 1;
// 确保池中有足够的ActionPoint实例
while (ActionPointMonoList.Count < needCount)
{
GameObject go = Instantiate(ActionPointPrefab, APContainer, false);
go.GetComponent<RectTransform>().localScale = Vector3.one;
var mono = go.GetComponent<UIInfoGridInfoActionPointMono>();
if (mono == null) mono = go.AddComponent<UIInfoGridInfoActionPointMono>();
ActionPointMonoList.Add(mono);
}
// 设置每个ActionPoint的显示/隐藏和内容
for (int i = 0; i < ActionPointMonoList.Count; i++)
{
if (i < needCount)
{
ActionPointMonoList[i].gameObject.SetActive(true);
if (flatList.Count == 0)
ActionPointMonoList[i].SetEmpty();
else
ActionPointMonoList[i].SetContent(flatList[i]);
}
else
{
ActionPointMonoList[i].gameObject.SetActive(false);
ActionPointMonoList[i].ResetContent();
}
}
}
private void SetLayerRecursively(GameObject obj, int newLayer)
{
obj.layer = newLayer;
foreach (Transform child in obj.transform)
{
SetLayerRecursively(child.gameObject, newLayer);
}
}
private void SetContentTask(ShowUIInfoGridInfo evt)
{
// Step #1 判断是否有task需要显示
if (evt.type != UIGridInfoType.unit ||
!Main.MapData.UnitMap.GetUnitDataByUnitId(evt.id, out var unit) ||
!Main.MapData.GetPlayerDataByUnitId(evt.id, out var player) )
{
TaskArea.SetActive(false);
return;
}
bool shouldShowTask = (unit.UnitFullType.UnitType is UnitType.Giant or UnitType.GiantJuggernaut)&&
unit.UnitFullType.UnitLevel < 4 &&
player.Id == Main.MapData.PlayerMap.SelfPlayerId;
if (!shouldShowTask)
{
TaskArea.SetActive(false);
return;
}
//Step #2 设置具体内容
TaskArea.SetActive(true);
// GiantJuggernaut 是 Giant 上船形态,真实英雄 GiantType 在 CarryUnitFullType
var heroGiantType = unit.UnitFullType.UnitType == UnitType.GiantJuggernaut
? unit.CarryUnitFullType.GiantType
: unit.GiantType;
if (!Table.Instance.HeroDataAssets.GetHeroInfo(heroGiantType, out var heroInfo) ||
heroInfo.TaskList.Count <= unit.UnitLevel - 1 ||
!Main.MapData.PlayerMap.SelfPlayerData.PlayerHeroData.GetHeroTask(heroGiantType, out var task))
{
TaskArea.SetActive(false);
return;
}
if (task.IsForceFinished)
{
MultilingualManager.Instance.SetUIText(TaskDesc, Table.Instance.TextDataAssets.HeroTaskFinishedDesc);
}
else
{
var param1 = task.LevelShowString();
var param2 = task.TargetLevelShowString();
var param3 = "";
if (task.SkillType != SkillType.NONE && Table.Instance.SkillDataAssets.GetSkillInfo(task.SkillType, out var skillInfo))
{
param3 = MultilingualManager.Instance.GetMultilingualTextSafe(skillInfo.SkillName);
}
var taskDesc = heroInfo.TaskList[(int)(unit.UnitLevel - 1)].Desc;
MultilingualManager.Instance.SetUIText(TaskDesc, taskDesc, new List<string> { param1, param2, param3 });
}
}
public void CloseView()
{
// 不停止音乐,只恢复轮播(如果正在播放的不是轮播音乐,会继续播放)
Logic.Audio.AudioManager.Instance.ResumeBgmRotationIfEnabled();
}
protected override void OnShowAnimFinished()
{
base.OnShowAnimFinished();
// 延迟一帧执行布局刷新,确保 RectTransform 尺寸已正确计算
StartCoroutine(RefreshLayoutNextFrame());
}
private System.Collections.IEnumerator RefreshLayoutNextFrame()
{
yield return null; // 等待一帧
if (VerticalGroup != null)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(VerticalGroup);
}
}
}
}