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);
}
}