using System; using System.Collections.Generic; using System.Linq; using BaseGames.Core; using BaseGames.Enemies.Abilities; namespace BaseGames.Editor.Modules { /// 总览表格的可排序列。 public enum AbilityOverviewColumn { Owner, Id, Category, Range, Weight, Cooldown, Priority, Exclusion } /// /// 总览表格的一行。只读快照,刷新时整体重建——能力资产改动频率低, /// 不值得为增量更新引入订阅。 /// public readonly struct AbilityOverviewRow { public EnemyAbilitySO Asset { get; } /// 归属敌人(由资产路径推导;不在规范目录下时为 )。 public string Owner { get; } public string Id { get; } public AbilityCategory Category { get; } public float Range { get; } public float Weight { get; } public float Cooldown { get; } public int Priority { get; } public string Exclusion { get; } /// 资产自校验报出的首条问题;无问题为 null。 public string Issue { get; } public bool HasIssue => !string.IsNullOrEmpty(Issue); public AbilityOverviewRow( EnemyAbilitySO asset, string owner, string id, AbilityCategory category, float range, float weight, float cooldown, int priority, string exclusion, string issue) { Asset = asset; Owner = owner; Id = id; Category = category; Range = range; Weight = weight; Cooldown = cooldown; Priority = priority; Exclusion = exclusion; Issue = issue; } } /// /// 能力总览表格的取数与排序(纯函数,不碰 UI)。 /// 抽出来的理由:UIElements 构建不可单测,而表格真正会出错的是 /// 「归属怎么推导」「问题从哪来」「排序对不对」这三处——它们都是纯逻辑。 /// public static class EnemyAbilityOverview { /// 资产不在 Data/Enemies/{敌人}/ 下时的归属占位符。 public const string UnknownOwner = "—"; private const string EnemiesSegment = "Enemies"; /// /// 从资产路径推导归属敌人:取 Enemies 段之后的那一级目录名。 /// 按 AssetFolderSpec,能力资产落在 Data/Enemies/{敌人}/Abilities/ 下。 /// 不符合该布局时返回占位符,而不是猜一个名字——放错目录这件事本身 /// 应该在表里看得见。 /// public static string OwnerFromPath(string assetPath) { if (string.IsNullOrEmpty(assetPath)) return UnknownOwner; var parts = assetPath.Split('/'); for (int i = 0; i < parts.Length - 1; i++) if (parts[i] == EnemiesSegment) return string.IsNullOrEmpty(parts[i + 1]) ? UnknownOwner : parts[i + 1]; return UnknownOwner; } /// /// 把一个能力资产摊成一行。问题一列直接取资产自己的 , /// 不在这里另写一份判定规则——否则 SO 侧改了规则,表格会继续按旧规则报。 /// public static AbilityOverviewRow BuildRow(EnemyAbilitySO asset, string assetPath) { string issue = null; foreach (var r in asset.Validate()) { issue = r.Message; break; } return new AbilityOverviewRow( asset, OwnerFromPath(assetPath), asset.abilityId, asset.category, asset.rangeRadius, asset.weight, asset.cooldown, asset.priority, asset.exclusionGroup, issue); } /// /// 按列排序。次序键固定为 abilityId:同值行的相对位置才不会每次刷新都跳动, /// 也让「按归属排」自然变成「同一敌人的招挨在一起、组内按 id」。 /// public static List Sort( IReadOnlyList rows, AbilityOverviewColumn column, bool ascending = true) { IOrderedEnumerable ordered = column switch { AbilityOverviewColumn.Owner => ByText(rows, r => r.Owner, ascending), AbilityOverviewColumn.Id => ByText(rows, r => r.Id, ascending), AbilityOverviewColumn.Exclusion => ByText(rows, r => r.Exclusion, ascending), AbilityOverviewColumn.Category => By(rows, r => (int)r.Category, ascending), AbilityOverviewColumn.Range => By(rows, r => r.Range, ascending), AbilityOverviewColumn.Weight => By(rows, r => r.Weight, ascending), AbilityOverviewColumn.Cooldown => By(rows, r => r.Cooldown, ascending), AbilityOverviewColumn.Priority => By(rows, r => r.Priority, ascending), _ => ByText(rows, r => r.Id, ascending), }; return ordered.ThenBy(r => r.Id, StringComparer.Ordinal).ToList(); } private static IOrderedEnumerable By( IEnumerable src, Func key, bool ascending) => ascending ? src.OrderBy(key) : src.OrderByDescending(key); // 文本列固定用序数比较:排序结果不随编辑器区域设置变化 private static IOrderedEnumerable ByText( IEnumerable src, Func key, bool ascending) => ascending ? src.OrderBy(key, StringComparer.Ordinal) : src.OrderByDescending(key, StringComparer.Ordinal); } }