875 lines
35 KiB
C#
875 lines
35 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description:
|
||
* @Date: 2026年03月31日 星期二 20:03:43
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using Logic.Action;
|
||
using Logic.Multilingual;
|
||
using RuntimeData;
|
||
using UI.HintUI;
|
||
using UnityEngine;
|
||
// 全局命名空间下的 PlayerInfo 与 TH1_Logic.MatchConfig.PlayerInfo 同名,用别名消歧
|
||
using GlobalPlayerInfo = global::PlayerInfo;
|
||
|
||
|
||
namespace TH1_Logic.MatchConfig
|
||
{
|
||
public enum WikiType
|
||
{
|
||
BigTypeForce = 0,
|
||
BigTypeUnit = 1,
|
||
BigTypeHero = 2,
|
||
BigTypeBase = 3,
|
||
BigTypeAction = 4,
|
||
BigTypeTech = 5,
|
||
BigTypeSkill = 6,
|
||
BigTypeResource = 7,
|
||
BigTypeBuilding = 8,
|
||
|
||
SmallTypeSkillNormal = 100,
|
||
SmallTypeSkillSpecial = 101,
|
||
SmallTypeSkillUnique = 102,
|
||
SmallTypeSkillPositive = 103,
|
||
SmallTypeSkillNegative = 104,
|
||
SmallTypeSkillBase = 105,
|
||
SmallTypeTechGroup = 110,
|
||
SmallTypeTechAtom= 111,
|
||
SmallTypeActionActive = 120,
|
||
SmallTypeActionBuild = 121,
|
||
SmallTypeActionTrainUnit = 122,
|
||
SmallTypeActionUnitAction = 123,
|
||
SmallTypeActionCityLevelUp = 124,
|
||
SmallTypeBaseBase = 130,
|
||
SmallTypeBaseBuild = 131,
|
||
SmallTypeBaseCoin = 132,
|
||
SmallTypeBaseUnit = 133,
|
||
SmallTypeBaseMoveAttack = 134,
|
||
SmallTypeBaseTech = 135,
|
||
SmallTypeBaseCulture = 136,
|
||
SmallTypeBaseOther = 137,
|
||
SmallTypeBaseActionPoint = 138,
|
||
SmallTypeBaseActionMechanic = 139,
|
||
SmallTypeBaseMoveMechanic = 140,
|
||
SmallTypeBaseAttackMechanic = 141,
|
||
SmallTypeBaseDamageSettlement = 142,
|
||
SmallTypeBaseUnitRule = 143,
|
||
SmallTypeHeroOther = 200,
|
||
SmallTypeHeroScarlet = 201,
|
||
SmallTypeHeroHouraisan = 202,
|
||
SmallTypeHeroMoriya = 203,
|
||
SmallTypeHeroKomeiji = 204,
|
||
SmallTypeHeroHakurei = 205,
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
public enum WikiDescType
|
||
{
|
||
Normal,
|
||
Literary,
|
||
TechLock,//Action或者Unit的科技解锁要求
|
||
Hint,
|
||
UnitInfo,
|
||
UnitSkill,
|
||
SkillBelongUnit,
|
||
PreTech,//科技的前置科技
|
||
HeroUpgrade,
|
||
PlayerTechPool,//拥有科技池
|
||
HasTechAtom,//拥有科技子条目
|
||
HasInitTechAtom,//拥有初始科技子条目
|
||
BelongTech,//科技子条目属于什么科技?
|
||
Level1Tech,//阵营科技池里拥有的1级科技
|
||
Level2Tech,//阵营科技池里拥有的2级科技
|
||
Level3Tech,//阵营科技池里拥有的3级科技
|
||
TechBelongEmpire,//一个tech属于哪些帝国
|
||
UnitAction,
|
||
}
|
||
|
||
|
||
// 虚拟 WikiItem 的来源,用于 Id 命名空间分段
|
||
public enum WikiSource : byte
|
||
{
|
||
Manual = 0,
|
||
Tech = 1,
|
||
TechAtom = 2,
|
||
Action = 3,
|
||
Skill = 4,
|
||
Unit = 5,
|
||
Player = 6,
|
||
}
|
||
|
||
|
||
// uint Id 编码:高 8 bit = WikiSource,低 24 bit = 原始 raw id
|
||
public static class WikiId
|
||
{
|
||
public const uint MANUAL_MAX = 0x00FFFFFFu;
|
||
|
||
public static uint Encode(WikiSource src, uint raw)
|
||
=> ((uint)src << 24) | (raw & 0x00FFFFFFu);
|
||
|
||
public static WikiSource GetSource(uint id) => (WikiSource)(id >> 24);
|
||
public static uint GetRaw(uint id) => id & 0x00FFFFFFu;
|
||
}
|
||
|
||
|
||
[CreateAssetMenu(fileName = "WikiData", menuName = "TH1/WikiData")]
|
||
public class WikiData : ScriptableObject
|
||
{
|
||
public List<WikiItem> Items = new List<WikiItem>();
|
||
// WikiType -> 多语言 Name 的配置表,编辑器内直接填写
|
||
public List<WikiTypeInfo> WikiTypeInfoList = new List<WikiTypeInfo>();
|
||
// WikiDescType -> 多语言 Name 的配置表,编辑器内直接填写
|
||
public List<WikiDescTypeInfo> WikiDescTypeInfoList = new List<WikiDescTypeInfo>();
|
||
|
||
// 运行时由 Tech/TechAtom/Action/Skill 数据源生成的虚拟 item,不参与序列化
|
||
[NonSerialized] private List<WikiItem> _virtualItems = new List<WikiItem>();
|
||
// 合并视图:Items + _virtualItems
|
||
[NonSerialized] private List<WikiItem> _allItems = new List<WikiItem>();
|
||
|
||
[NonSerialized] private Dictionary<uint, WikiItem> _idCache;
|
||
[NonSerialized] private string[] _lowerNameCache;
|
||
[NonSerialized] private HashSet<WikiType>[] _itemTypeSets;
|
||
[NonSerialized] private bool _cacheBuilt;
|
||
|
||
[NonSerialized] private Dictionary<WikiType, string> _typeNameCache;
|
||
[NonSerialized] private Dictionary<WikiDescType, string> _descTypeNameCache;
|
||
|
||
public IReadOnlyList<WikiItem> AllItems
|
||
{
|
||
get { EnsureCache(); return _allItems; }
|
||
}
|
||
|
||
public void AddWikiItem(string name)
|
||
{
|
||
uint id = 1;
|
||
if (Items.Count != 0) id = Items[^1].Id + 1;
|
||
if (id > WikiId.MANUAL_MAX)
|
||
{
|
||
Debug.LogError($"[WikiData] 手填 Id {id} 越界,超过保留段 0x{WikiId.MANUAL_MAX:X}");
|
||
return;
|
||
}
|
||
var item = new WikiItem();
|
||
item.Id = id;
|
||
item.Name = name;
|
||
Items.Add(item);
|
||
InvalidateCache();
|
||
}
|
||
|
||
public void InvalidateCache()
|
||
{
|
||
_cacheBuilt = false;
|
||
_idCache = null;
|
||
_lowerNameCache = null;
|
||
_itemTypeSets = null;
|
||
_typeNameCache = null;
|
||
_descTypeNameCache = null;
|
||
}
|
||
|
||
// 根据 WikiType 查询对应的多语言 Name;未配置时返回 string.Empty
|
||
public string GetTypeName(WikiType type)
|
||
{
|
||
if (_typeNameCache == null) BuildTypeNameCache();
|
||
return _typeNameCache.TryGetValue(type, out var name) ? name : string.Empty;
|
||
}
|
||
|
||
private void BuildTypeNameCache()
|
||
{
|
||
int n = WikiTypeInfoList != null ? WikiTypeInfoList.Count : 0;
|
||
_typeNameCache = new Dictionary<WikiType, string>(n);
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var info = WikiTypeInfoList[i];
|
||
if (info == null) continue;
|
||
_typeNameCache[info.Type] = info.Name;
|
||
}
|
||
}
|
||
|
||
// 根据 WikiDescType 查询对应的多语言 Name;未配置时返回 string.Empty
|
||
public string GetDescTypeName(WikiDescType type)
|
||
{
|
||
if (_descTypeNameCache == null) BuildDescTypeNameCache();
|
||
return _descTypeNameCache.TryGetValue(type, out var name) ? name : string.Empty;
|
||
}
|
||
|
||
private void BuildDescTypeNameCache()
|
||
{
|
||
int n = WikiDescTypeInfoList != null ? WikiDescTypeInfoList.Count : 0;
|
||
_descTypeNameCache = new Dictionary<WikiDescType, string>(n);
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var info = WikiDescTypeInfoList[i];
|
||
if (info == null) continue;
|
||
_descTypeNameCache[info.Type] = info.Name;
|
||
}
|
||
}
|
||
|
||
// 由 Table 构造完成后调用;从 Tech/TechAtom/Action/Skill 数据源生成虚拟 item
|
||
public void RebuildVirtualItems()
|
||
{
|
||
if (_virtualItems == null) _virtualItems = new List<WikiItem>();
|
||
_virtualItems.Clear();
|
||
|
||
BuildFromTech(_virtualItems);
|
||
BuildFromTechAtom(_virtualItems);
|
||
BuildFromAction(_virtualItems);
|
||
BuildFromSkill(_virtualItems);
|
||
BuildFromUnit(_virtualItems);
|
||
BuildFromPlayer(_virtualItems);
|
||
|
||
InvalidateCache();
|
||
}
|
||
|
||
// 反向解码:从 wikiId 还原来源类型与原始 raw id(UI 跳转用)
|
||
public bool TryGetOrigin(uint wikiId, out WikiSource src, out uint raw)
|
||
{
|
||
src = WikiId.GetSource(wikiId);
|
||
raw = WikiId.GetRaw(wikiId);
|
||
return src != WikiSource.Manual;
|
||
}
|
||
|
||
private void EnsureCache()
|
||
{
|
||
if (_cacheBuilt) return;
|
||
|
||
// 构建合并视图
|
||
if (_allItems == null) _allItems = new List<WikiItem>();
|
||
_allItems.Clear();
|
||
_allItems.AddRange(Items);
|
||
if (_virtualItems != null) _allItems.AddRange(_virtualItems);
|
||
|
||
int n = _allItems.Count;
|
||
_idCache = new Dictionary<uint, WikiItem>(n);
|
||
_lowerNameCache = new string[n];
|
||
_itemTypeSets = new HashSet<WikiType>[n];
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var it = _allItems[i];
|
||
_idCache[it.Id] = it;
|
||
_lowerNameCache[i] = string.IsNullOrEmpty(it.Name) ? string.Empty : it.Name.ToLowerInvariant();
|
||
// WikiType 枚举值最大已超过 63,无法用 ulong 位掩码(C# 位移按 mod 64),改用 HashSet 直接判定
|
||
var set = new HashSet<WikiType>();
|
||
if (it.Types != null)
|
||
{
|
||
foreach (var t in it.Types) set.Add(t);
|
||
}
|
||
_itemTypeSets[i] = set;
|
||
}
|
||
_cacheBuilt = true;
|
||
}
|
||
|
||
// 通过 Id 直接获取 WikiItem;不存在返回 null
|
||
public WikiItem GetById(uint id)
|
||
{
|
||
EnsureCache();
|
||
_idCache.TryGetValue(id, out var item);
|
||
return item;
|
||
}
|
||
|
||
// 通过 SkillType 反查对应 Wiki 条目的 Id;找不到返回 0。
|
||
// Skill 条目的 wikiId 用递增 raw 编码(见 BuildFromSkill),无法直接用 SkillType 编码,
|
||
// 需要遍历查找。技能数量有限,O(n) 可接受。
|
||
public uint FindWikiIdBySkillType(SkillType skillType)
|
||
{
|
||
if (skillType == SkillType.NONE) return 0;
|
||
EnsureCache();
|
||
for (int i = 0; i < _allItems.Count; i++)
|
||
{
|
||
var it = _allItems[i];
|
||
if (it == null) continue;
|
||
if (WikiId.GetSource(it.Id) != WikiSource.Skill) continue;
|
||
if (it.SkillType == skillType) return it.Id;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// 通过 CommonActionId 反查对应 Wiki 条目的 Id;找不到(含未通过白名单/未生成)返回 0。
|
||
// Action 条目的 wikiId 是 ActionList 的 index 编码(见 BuildFromAction),同样 O(n) 遍历。
|
||
public uint FindWikiIdByActionId(CommonActionId actionId)
|
||
{
|
||
if (actionId == null) return 0;
|
||
EnsureCache();
|
||
for (int i = 0; i < _allItems.Count; i++)
|
||
{
|
||
var it = _allItems[i];
|
||
if (it == null) continue;
|
||
if (WikiId.GetSource(it.Id) != WikiSource.Action) continue;
|
||
if (it.ActionId != null && it.ActionId.Equals(actionId)) return it.Id;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// 通过 Name 多语言 key 的包含关系筛选;大小写不敏感;keyword 为空时返回空
|
||
// 调用方传入 outResult 复用 List 以避免 GC
|
||
public void FindByName(string keyword, List<WikiItem> outResult)
|
||
{
|
||
if (outResult == null) return;
|
||
outResult.Clear();
|
||
if (string.IsNullOrEmpty(keyword)) return;
|
||
EnsureCache();
|
||
string lower = keyword.ToLowerInvariant();
|
||
for (int i = 0; i < _allItems.Count; i++)
|
||
{
|
||
if (_lowerNameCache[i].IndexOf(lower, StringComparison.Ordinal) >= 0)
|
||
outResult.Add(_allItems[i]);
|
||
}
|
||
}
|
||
|
||
// 全包含筛选:返回同时拥有 required 中所有 WikiType 的 item;required 为空返回空
|
||
// 调用方传入 outResult 复用 List 以避免 GC
|
||
public void FindByTypes(List<WikiType> required, List<WikiItem> outResult)
|
||
{
|
||
if (outResult == null) return;
|
||
outResult.Clear();
|
||
if (required == null || required.Count == 0) return;
|
||
EnsureCache();
|
||
for (int i = 0; i < _allItems.Count; i++)
|
||
{
|
||
var set = _itemTypeSets[i];
|
||
if (set == null) continue;
|
||
bool match = true;
|
||
for (int j = 0; j < required.Count; j++)
|
||
{
|
||
if (!set.Contains(required[j])) { match = false; break; }
|
||
}
|
||
if (match) outResult.Add(_allItems[i]);
|
||
}
|
||
}
|
||
|
||
//-------------------- 虚拟 WikiItem 构建器 --------------------
|
||
|
||
private static void BuildFromTech(List<WikiItem> outList)
|
||
{
|
||
var assets = Table.Instance?.TechDataAssets;
|
||
if (assets == null) return;
|
||
foreach (var info in assets.TechList)
|
||
{
|
||
if (info == null) continue;
|
||
if (string.IsNullOrEmpty(info.TechName)) continue; // Name 为空 → 跳过
|
||
// TechGroup 默认带一个 Normal 描述;如果有前置则再追加 PreTech,
|
||
// 实际前置文本由 WikiDescGroup 的 PreTech 分支按 TechType 反查生成
|
||
var techDescs = new List<DescItem>
|
||
{
|
||
new DescItem { DescType = WikiDescType.Normal, Desc = info.Description }
|
||
};
|
||
if (info.FatherTechList != null && info.FatherTechList.Count > 0)
|
||
techDescs.Add(new DescItem { DescType = WikiDescType.PreTech });
|
||
if (info.TechAtomList != null && info.TechAtomList.Count > 0)
|
||
techDescs.Add(new DescItem { DescType = WikiDescType.HasTechAtom });
|
||
if (HasAnyEmpireWithTech(info.TechType))
|
||
techDescs.Add(new DescItem { DescType = WikiDescType.TechBelongEmpire });
|
||
|
||
var item = new WikiItem
|
||
{
|
||
Id = WikiId.Encode(WikiSource.Tech, (uint)info.TechType),
|
||
Name = info.TechName,
|
||
IsAutoGenerated = true,
|
||
Types = WikiTagRules.ForTech(info),
|
||
// 按需求 TechList 一律不挂 Icon
|
||
HasIcon = false,
|
||
DescItems = techDescs,
|
||
TechType = info.TechType,
|
||
};
|
||
outList.Add(item);
|
||
}
|
||
}
|
||
|
||
private static void BuildFromTechAtom(List<WikiItem> outList)
|
||
{
|
||
var assets = Table.Instance?.TechDataAssets;
|
||
if (assets == null) return;
|
||
foreach (var info in assets.TechAtomList)
|
||
{
|
||
if (info == null) continue;
|
||
if (string.IsNullOrEmpty(info.TechAtomName)) continue;
|
||
// TechAtom 的 GetIcon 接受 civ/force/gridSpType;图鉴里没有玩家上下文,传 Common+无 GridSp 取默认图
|
||
Sprite icon = null;
|
||
try { icon = info.GetIcon(CivEnum.Common, ForceEnum.Common); }
|
||
catch { icon = null; }
|
||
|
||
var atomDescs = new List<DescItem>
|
||
{
|
||
new DescItem { DescType = WikiDescType.Normal, Desc = info.Desc }
|
||
};
|
||
// 仅当存在 Tech 把这个 atom 列在 TechAtomList 里时,才追加 BelongTech 段
|
||
// 具体的 Tech 名称列表由 WikiDescGroup 的 BelongTech 分支运行时拼。
|
||
if (HasOwningTech(info.TechAtom))
|
||
atomDescs.Add(new DescItem { DescType = WikiDescType.BelongTech });
|
||
|
||
var item = new WikiItem
|
||
{
|
||
Id = WikiId.Encode(WikiSource.TechAtom, (uint)info.TechAtom),
|
||
Name = info.TechAtomName,
|
||
IsAutoGenerated = true,
|
||
Types = WikiTagRules.ForTechAtom(info),
|
||
// TechAtom 复用其原本的 IconViewSizeType
|
||
Icon = icon,
|
||
IconSizeType = info.iconViewSizeType,
|
||
HasIcon = icon != null,
|
||
DescItems = atomDescs,
|
||
TechAtomKey = info.TechAtom,
|
||
};
|
||
outList.Add(item);
|
||
}
|
||
}
|
||
|
||
private static void BuildFromAction(List<WikiItem> outList)
|
||
{
|
||
var assets = Table.Instance?.ActionDataAssets;
|
||
if (assets == null) return;
|
||
// CommonActionId 是 class,无法稳定编码 → 用 ActionList 的 index 作为 raw id
|
||
var list = assets.ActionList;
|
||
for (int i = 0; i < list.Count; i++)
|
||
{
|
||
var info = list[i];
|
||
if (info == null || info.ActionId == null) continue;
|
||
if (string.IsNullOrEmpty(info.ActionName)) continue;
|
||
|
||
// 只接收白名单内的 ActionType,其余忽略
|
||
if (!WikiTagRules.IsActionWhitelisted(info.ActionId.ActionType)) continue;
|
||
|
||
var descs = new List<DescItem>
|
||
{
|
||
new DescItem { DescType = WikiDescType.Normal, Desc = info.Desc }
|
||
};
|
||
|
||
// 仅当存在 TechAtom 解锁此 action 时,才追加 TechLock 段(具体名称在 WikiDescGroup 运行时拼)
|
||
if (HasUnlockingTechAtom(info.ActionId))
|
||
descs.Add(new DescItem { DescType = WikiDescType.TechLock });
|
||
|
||
Sprite icon = null;
|
||
try { icon = info.GetIcon(CivEnum.Common, ForceEnum.Common, GridSpType.None); }
|
||
catch { icon = null; }
|
||
|
||
var item = new WikiItem
|
||
{
|
||
Id = WikiId.Encode(WikiSource.Action, (uint)i),
|
||
Name = info.ActionName,
|
||
IsAutoGenerated = true,
|
||
Types = WikiTagRules.ForAction(info),
|
||
// Action 复用其原本的 IconViewSizeType
|
||
Icon = icon,
|
||
IconSizeType = info.IconViewSizeType,
|
||
HasIcon = icon != null,
|
||
DescItems = descs,
|
||
ActionId = info.ActionId,
|
||
};
|
||
outList.Add(item);
|
||
}
|
||
}
|
||
|
||
// 是否存在某个 TechInfo.TechAtomList 包含此 atom(即此 atom 至少归属一个 Tech)
|
||
private static bool HasOwningTech(TechAtom atom)
|
||
{
|
||
var techAssets = Table.Instance?.TechDataAssets;
|
||
if (techAssets == null) return false;
|
||
var techList = techAssets.TechList;
|
||
if (techList == null) return false;
|
||
for (int i = 0; i < techList.Count; i++)
|
||
{
|
||
var t = techList[i];
|
||
if (t == null || t.TechAtomList == null) continue;
|
||
if (t.TechAtomList.Contains(atom)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// 是否存在 TechAtom 通过 EnableAction + TechActions 解锁此 actionId
|
||
private static bool HasUnlockingTechAtom(CommonActionId actionId)
|
||
{
|
||
if (actionId == null) return false;
|
||
var techAssets = Table.Instance?.TechDataAssets;
|
||
if (techAssets == null) return false;
|
||
var atomList = techAssets.TechAtomList;
|
||
if (atomList == null) return false;
|
||
|
||
for (int i = 0; i < atomList.Count; i++)
|
||
{
|
||
var atom = atomList[i];
|
||
if (atom == null) continue;
|
||
if (!atom.EnableAction) continue;
|
||
if (atom.TechActions == null) continue;
|
||
for (int j = 0; j < atom.TechActions.Count; j++)
|
||
{
|
||
if (atom.TechActions[j] != null && atom.TechActions[j].Equals(actionId))
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private static void BuildFromSkill(List<WikiItem> outList)
|
||
{
|
||
var assets = Table.Instance?.SkillDataAssets;
|
||
if (assets == null) return;
|
||
|
||
// 每个 SkillInfo 只出一条 WikiItem。
|
||
// SkillShowList 里的 pack 是给特定 UnitFullType 的特化图标/描述,
|
||
// 不是独立技能;图鉴展示一律使用 info.SkillIcon / SkillName / SkillDesc。
|
||
uint raw = 0;
|
||
foreach (var info in assets.SkillInfoList)
|
||
{
|
||
if (info == null) continue;
|
||
if (info.NotShow) continue;
|
||
if (string.IsNullOrEmpty(info.SkillName)) continue;
|
||
|
||
var item = new WikiItem
|
||
{
|
||
Id = WikiId.Encode(WikiSource.Skill, raw++),
|
||
Name = info.SkillName,
|
||
IsAutoGenerated = true,
|
||
Types = WikiTagRules.ForSkill(info),
|
||
Icon = info.SkillIcon,
|
||
IconSizeType = IconViewSizeType._256x256,
|
||
HasIcon = info.SkillIcon != null,
|
||
DescItems = new List<DescItem>
|
||
{
|
||
new DescItem { DescType = WikiDescType.Normal, Desc = info.SkillDesc }
|
||
},
|
||
SkillType = info.SkillType,
|
||
};
|
||
outList.Add(item);
|
||
}
|
||
}
|
||
|
||
private static void BuildFromUnit(List<WikiItem> outList)
|
||
{
|
||
var assets = Table.Instance?.UnitTypeDataAssets;
|
||
if (assets == null) return;
|
||
var list = assets.UnitTypeList;
|
||
if (list == null) return;
|
||
|
||
// UnitTypeInfo 没有稳定 id,沿用 ActionList 的 index 编码方式
|
||
for (int i = 0; i < list.Count; i++)
|
||
{
|
||
var info = list[i];
|
||
if (info == null) continue;
|
||
if (string.IsNullOrEmpty(info.Name)) continue;
|
||
|
||
// 白名单:GiantType=None 走 BigTypeUnit(取所有 Level);
|
||
// GiantType!=None 走 BigTypeHero(取 Level=1~4,每级一条);其余忽略
|
||
if (!WikiTagRules.IsUnitWhitelisted(info)) continue;
|
||
if (!ContentGate.CanShowHeroIntroduction(info)) continue;
|
||
|
||
// Hero(GiantType != None)走 HeroDataAssets 取 HeroAvatar;普通 Unit 沿用 info.Sprite
|
||
Sprite unitIcon = info.Sprite;
|
||
if (info.GiantType != GiantType.None)
|
||
{
|
||
var heroAssets = Table.Instance?.HeroDataAssets;
|
||
if (heroAssets != null && heroAssets.GetHeroInfo(info.GiantType, out var heroInfo) && heroInfo != null)
|
||
{
|
||
if (heroInfo.HeroAvatar != null) unitIcon = heroInfo.HeroAvatar;
|
||
}
|
||
}
|
||
|
||
// 特殊模块:UnitInfo(6 项数值) + UnitSkill(技能列表,仅有技能时)
|
||
// + UnitAction(可用 Action,仅有 EnableActions 时)+ HeroUpgrade(仅 Hero)
|
||
var descItems = new List<DescItem>
|
||
{
|
||
new DescItem { DescType = WikiDescType.Normal, Desc = info.Desc },
|
||
new DescItem { DescType = WikiDescType.UnitInfo },
|
||
};
|
||
if (info.Skills != null && info.Skills.Count > 0)
|
||
descItems.Add(new DescItem { DescType = WikiDescType.UnitSkill });
|
||
if (info.EnableActions != null && info.EnableActions.Count > 0)
|
||
descItems.Add(new DescItem { DescType = WikiDescType.UnitAction });
|
||
if (info.GiantType != GiantType.None)
|
||
descItems.Add(new DescItem { DescType = WikiDescType.HeroUpgrade });
|
||
|
||
var item = new WikiItem
|
||
{
|
||
Id = WikiId.Encode(WikiSource.Unit, (uint)i),
|
||
Name = info.Name,
|
||
IsAutoGenerated = true,
|
||
Types = WikiTagRules.ForUnit(info),
|
||
// Unit 全部按 IconViewSizeType.Unit 处理
|
||
Icon = unitIcon,
|
||
IconSizeType = IconViewSizeType.Unit,
|
||
HasIcon = unitIcon != null,
|
||
DescItems = descItems,
|
||
// 运行时反查字段
|
||
UnitFullType = new UnitFullType(info.UnitType, info.GiantType, info.UnitLevel),
|
||
HeroGiantType = info.GiantType,
|
||
};
|
||
outList.Add(item);
|
||
}
|
||
}
|
||
|
||
// PlayerDataList 中是否存在 TechPool 包含指定 TechType 的 Player;
|
||
// 用于决定 Tech 条目是否生成 TechBelongEmpire 段
|
||
private static bool HasAnyEmpireWithTech(global::TechType techType)
|
||
{
|
||
var assets = Table.Instance?.PlayerDataAssets;
|
||
if (assets?.PlayerDataList == null) return false;
|
||
var list = assets.PlayerDataList;
|
||
for (int i = 0; i < list.Count; i++)
|
||
{
|
||
var info = list[i];
|
||
if (info == null) continue;
|
||
if (string.IsNullOrEmpty(info.ForceName)) continue;
|
||
if (info.TechPool == null) continue;
|
||
if (info.TechPool.Contains(techType)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// PlayerInfo.TechPool 中是否存在指定 CostLevel 的 Tech;用于决定是否生成 LevelXTech 段
|
||
private static bool HasTechPoolAtCostLevel(GlobalPlayerInfo info, int costLevel)
|
||
{
|
||
if (info?.TechPool == null) return false;
|
||
var techAssets = Table.Instance?.TechDataAssets;
|
||
if (techAssets == null) return false;
|
||
for (int i = 0; i < info.TechPool.Count; i++)
|
||
{
|
||
if (!techAssets.GetTechInfo(info.TechPool[i], out var techInfo) || techInfo == null) continue;
|
||
if (techInfo.CostLevel == costLevel) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private static void BuildFromPlayer(List<WikiItem> outList)
|
||
{
|
||
var assets = Table.Instance?.PlayerDataAssets;
|
||
if (assets == null) return;
|
||
var list = assets.PlayerDataList;
|
||
if (list == null) return;
|
||
|
||
// PlayerInfo 没有稳定唯一 id(同 ForceId 可对应多个 Civ),沿用 list index 作为 raw id
|
||
for (int i = 0; i < list.Count; i++)
|
||
{
|
||
var info = list[i];
|
||
if (info == null) continue;
|
||
if (string.IsNullOrEmpty(info.ForceName)) continue;
|
||
if (!WikiTagRules.IsPlayerWhitelisted(info)) continue;
|
||
|
||
var playerDescs = new List<DescItem>
|
||
{
|
||
new DescItem { DescType = WikiDescType.Normal, Desc = info.EmpireDesc }
|
||
};
|
||
// 把 TechPool 按 CostLevel 拆 3 段,每段一个 CommonGridItemDescGroup
|
||
if (HasTechPoolAtCostLevel(info, 1))
|
||
playerDescs.Add(new DescItem { DescType = WikiDescType.Level1Tech });
|
||
if (HasTechPoolAtCostLevel(info, 2))
|
||
playerDescs.Add(new DescItem { DescType = WikiDescType.Level2Tech });
|
||
if (HasTechPoolAtCostLevel(info, 3))
|
||
playerDescs.Add(new DescItem { DescType = WikiDescType.Level3Tech });
|
||
if (info.TechAtomList != null && info.TechAtomList.Count > 0)
|
||
playerDescs.Add(new DescItem { DescType = WikiDescType.HasInitTechAtom });
|
||
|
||
var item = new WikiItem
|
||
{
|
||
Id = WikiId.Encode(WikiSource.Player, (uint)i),
|
||
Name = info.ForceName,
|
||
IsAutoGenerated = true,
|
||
Types = WikiTagRules.ForPlayer(info),
|
||
// Force 使用 PlayerInfo.LeaderAvatar 作为图标
|
||
Icon = info.LeaderAvatar,
|
||
HasIcon = info.LeaderAvatar != null,
|
||
DescItems = playerDescs,
|
||
ForceId = info.ForceId,
|
||
};
|
||
outList.Add(item);
|
||
}
|
||
}
|
||
|
||
// 根据数据源 Info 字段生成 WikiType 标签的规则。当前 WikiType 仅占位,规则待补。
|
||
private static class WikiTagRules
|
||
{
|
||
public static List<WikiType> ForTech(TechInfo info)
|
||
=> new List<WikiType> { WikiType.BigTypeTech, WikiType.SmallTypeTechGroup };
|
||
|
||
public static List<WikiType> ForTechAtom(TechAtomInfo info)
|
||
=> new List<WikiType> { WikiType.BigTypeTech, WikiType.SmallTypeTechAtom };
|
||
|
||
// GiantType=None → BigTypeUnit
|
||
// GiantType!=None & Level∈[1,4] → BigTypeHero + 按 GiantEmpire 分类的 SmallTypeHero*
|
||
public static bool IsUnitWhitelisted(UnitTypeInfo info)
|
||
{
|
||
if (info == null) return false;
|
||
if (info.GiantType == GiantType.None) return true;
|
||
return info.UnitLevel >= 1 && info.UnitLevel <= 4;
|
||
}
|
||
|
||
public static List<WikiType> ForUnit(UnitTypeInfo info)
|
||
{
|
||
var list = new List<WikiType>();
|
||
if (info == null) return list;
|
||
if (info.GiantType == GiantType.None)
|
||
{
|
||
list.Add(WikiType.BigTypeUnit);
|
||
}
|
||
else
|
||
{
|
||
list.Add(WikiType.BigTypeHero);
|
||
list.Add(MapGiantEmpireToHeroSmall(info.GiantEmpire.Force));
|
||
}
|
||
return list;
|
||
}
|
||
|
||
// GiantEmpire.Force → SmallTypeHero*
|
||
// Remilia → Scarlet, Kaguya → Houraisan, Kanako → Moriya, Satori → Komeiji, Reimu → Hakurei, 其余 → Other
|
||
private static WikiType MapGiantEmpireToHeroSmall(ForceEnum force)
|
||
{
|
||
switch (force)
|
||
{
|
||
case ForceEnum.Remilia: return WikiType.SmallTypeHeroScarlet;
|
||
case ForceEnum.Kaguya: return WikiType.SmallTypeHeroHouraisan;
|
||
case ForceEnum.Kanako: return WikiType.SmallTypeHeroMoriya;
|
||
case ForceEnum.Satori: return WikiType.SmallTypeHeroKomeiji;
|
||
case ForceEnum.Reimu: return WikiType.SmallTypeHeroHakurei;
|
||
default: return WikiType.SmallTypeHeroOther;
|
||
}
|
||
}
|
||
|
||
// PlayerInfo 按当前可选阵营集合纳入,避免 Wiki 阵营列表滞后于选阵营入口。
|
||
// 全局 PlayerInfo(PlayerDataAssets.cs)和 TH1_Logic.MatchConfig.PlayerInfo 同名,
|
||
// 走 using 别名 GlobalPlayerInfo 显式指向前者
|
||
public static bool IsPlayerWhitelisted(GlobalPlayerInfo info)
|
||
=> info != null && ContentGate.CanSelectEmpire(info.CivId, info.ForceId);
|
||
|
||
public static List<WikiType> ForPlayer(GlobalPlayerInfo info)
|
||
=> new List<WikiType> { WikiType.BigTypeForce };
|
||
|
||
public static List<WikiType> ForAction(ActionInfo info)
|
||
{
|
||
var list = new List<WikiType>();
|
||
if (info == null || info.ActionId == null) return list;
|
||
var small = MapActionTypeToSmall(info.ActionId.ActionType);
|
||
if (small.HasValue)
|
||
{
|
||
// BigTypeAction + 对应 SmallTypeAction*;这样 SelectGroups 里把
|
||
// BigTypeAction → [SmallTypeActionBuild,...] 配好后才能筛得出来
|
||
list.Add(WikiType.BigTypeAction);
|
||
list.Add(small.Value);
|
||
}
|
||
return list;
|
||
}
|
||
|
||
// 白名单:只有这些 ActionType 才会被纳入虚拟 WikiItem
|
||
public static bool IsActionWhitelisted(CommonActionType type)
|
||
=> MapActionTypeToSmall(type).HasValue;
|
||
|
||
private static WikiType? MapActionTypeToSmall(CommonActionType type)
|
||
{
|
||
switch (type)
|
||
{
|
||
// Build 类:Gain / Build / BuildWonder / GridMisc 全部归到 SmallTypeActionBuild
|
||
case CommonActionType.Gain:
|
||
case CommonActionType.Build:
|
||
case CommonActionType.BuildWonder:
|
||
case CommonActionType.GridMisc:
|
||
return WikiType.SmallTypeActionBuild;
|
||
case CommonActionType.UnitAction:
|
||
return WikiType.SmallTypeActionUnitAction;
|
||
case CommonActionType.CityLevelUpAction:
|
||
return WikiType.SmallTypeActionCityLevelUp;
|
||
case CommonActionType.TrainUnit:
|
||
return WikiType.SmallTypeActionTrainUnit;
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public static List<WikiType> ForSkill(SkillInfo info)
|
||
{
|
||
var list = new List<WikiType> { WikiType.BigTypeSkill };
|
||
if (info == null) return list;
|
||
switch (info.SkillViewType)
|
||
{
|
||
case SkillViewType.Normal: list.Add(WikiType.SmallTypeSkillNormal); break;
|
||
case SkillViewType.Special: list.Add(WikiType.SmallTypeSkillSpecial); break;
|
||
case SkillViewType.Unique: list.Add(WikiType.SmallTypeSkillUnique); break;
|
||
case SkillViewType.Positive: list.Add(WikiType.SmallTypeSkillPositive); break;
|
||
case SkillViewType.Negative: list.Add(WikiType.SmallTypeSkillNegative); break;
|
||
// SkillViewType.Max 是哨兵值,忽略
|
||
}
|
||
return list;
|
||
}
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
private void OnValidate()
|
||
{
|
||
InvalidateCache();
|
||
}
|
||
#endif
|
||
}
|
||
|
||
|
||
[Serializable]
|
||
public class WikiTypeInfo
|
||
{
|
||
public WikiType Type;
|
||
[MultilingualField]
|
||
public string Name;
|
||
}
|
||
|
||
|
||
[Serializable]
|
||
public class WikiDescTypeInfo
|
||
{
|
||
public WikiDescType Type;
|
||
[MultilingualField]
|
||
public string Name;
|
||
}
|
||
|
||
|
||
[Serializable]
|
||
public class WikiItem
|
||
{
|
||
public uint Id;
|
||
[MultilingualField]
|
||
public string Name;
|
||
public List<WikiType> Types = new List<WikiType>();
|
||
public List<DescItem> DescItems = new List<DescItem>();
|
||
// Icon + 走 IconSizingUtility 的尺寸类型;HasIcon=false 时表示该条目不显示图标
|
||
public Sprite Icon;
|
||
public IconViewSizeType IconSizeType;
|
||
public bool HasIcon;
|
||
[NonSerialized] public bool IsAutoGenerated;
|
||
|
||
// 运行时由 BuildFromXxx 写入,给 WikiDescGroup 的特殊模块当数据源。
|
||
// 不参与序列化;Manual 条目不会带这些。
|
||
[NonSerialized] public UnitFullType UnitFullType;
|
||
[NonSerialized] public SkillType SkillType;
|
||
[NonSerialized] public GiantType HeroGiantType;
|
||
[NonSerialized] public TechType TechType;
|
||
[NonSerialized] public TechAtom TechAtomKey;
|
||
[NonSerialized] public CommonActionId ActionId;
|
||
[NonSerialized] public uint ForceId;
|
||
|
||
// 去重:保留每个枚举值的首次出现,移除后续重复项
|
||
public void Refresh()
|
||
{
|
||
var seenTypes = new HashSet<WikiType>();
|
||
Types.RemoveAll(t => !seenTypes.Add(t));
|
||
|
||
var seenDescTypes = new HashSet<WikiDescType>();
|
||
DescItems.RemoveAll(d => !seenDescTypes.Add(d.DescType));
|
||
}
|
||
}
|
||
|
||
|
||
[Serializable]
|
||
public class DescItem
|
||
{
|
||
public WikiDescType DescType;
|
||
[MultilingualField]
|
||
public string Desc;
|
||
public bool UseHint;
|
||
public HintDataProvider HintProvider;
|
||
|
||
public string GetDesc()
|
||
{
|
||
if (UseHint) return HintProvider.GetHintText();
|
||
return Desc;
|
||
}
|
||
}
|
||
}
|