namespace Shigure; /// /// 把 / 定义在当前 group 状态下解析为 /// 单位槽位或数量。逻辑忠实移植自旧 Python 项目 utils.py: /// - 只考虑职责 != 0 的单位; /// - 最低生命值选择器保留并比较 0 和负生命值; /// - 生命值数量字段仍只统计 0 < 生命值 < 阈值; /// - 按 "1".."30" 升序遍历, 保证首/末语义稳定。 /// public static class UnitSelector { private const int DefaultThreshold = 100; /// 解析动态单位为 group 槽位("1".."30"), 无匹配返回 null。 public static string? Resolve(ModuleUnit unit, GameState state) { var group = state.Group; var threshold = ResolveThreshold( unit.HealthThreshold, unit.HealthThresholdField, state, IsHealingAbsorbKind(unit.Kind) ? 0 : DefaultThreshold); var aura = FirstAura(unit.AuraSpellIds); if (RequiresAura(unit.Kind) && (unit.AuraSpellIds is not { Count: > 0 } || unit.AuraSpellIds.Any(id => !GroupContainsAuraField(group, id)))) { return null; } return unit.Kind switch { UnitSelectorKind.LowestHealth => LowestHealth( group, threshold, data => MatchesRoleFilter(data, unit.RoleFilter, unit.Role)), UnitSelectorKind.LowestHealthWithAnyAura => unit.AuraSpellIds is { Count: > 0 } names ? LowestHealth( group, threshold, data => MatchesRoleFilter(data, unit.RoleFilter, unit.Role) && HasAnyAura(data, names)) : null, UnitSelectorKind.LowestHealthWithoutAnyAura => unit.AuraSpellIds is { Count: > 0 } names ? LowestHealth( group, threshold, data => MatchesRoleFilter(data, unit.RoleFilter, unit.Role) && !HasAnyAura(data, names)) : null, UnitSelectorKind.LowestHealthWithoutAura => aura is null ? null : LowestHealth( group, threshold, data => MatchesRoleFilter(data, unit.RoleFilter, unit.Role) && !HasAura(data, aura.Value)), UnitSelectorKind.LowestHealthWithAura => aura is null ? null : LowestHealth( group, threshold, data => MatchesRoleFilter(data, unit.RoleFilter, unit.Role) && HasAura(data, aura.Value)), UnitSelectorKind.LowestHealthWithAuraCount => aura is null || unit.AuraCount is null ? null : LowestHealth( group, threshold, data => MatchesRoleFilter(data, unit.RoleFilter, unit.Role) && AuraEquals(data, aura.Value, unit.AuraCount.Value)), UnitSelectorKind.UnitWithRole => unit.Role is null ? null : UnitWithRole(group, unit.Role.Value, unit.Reverse, _ => true), UnitSelectorKind.UnitWithRoleWithoutAura => unit.Role is null || aura is null ? null : UnitWithRole(group, unit.Role.Value, unit.Reverse, data => !HasAura(data, aura.Value)), UnitSelectorKind.UnitWithAura => aura is null ? null : UnitWithAura(group, aura.Value), UnitSelectorKind.UnitWithDispelType => unit.DispelType is null ? null : UnitWithDispelType(group, unit.DispelType.Value), UnitSelectorKind.HighestHealingAbsorb => HighestHealingAbsorb(group, threshold, _ => true), UnitSelectorKind.HighestHealingAbsorbWithAnyAura => unit.AuraSpellIds is { Count: > 0 } names ? HighestHealingAbsorb(group, threshold, data => HasAnyAura(data, names)) : null, UnitSelectorKind.HighestHealingAbsorbWithoutAnyAura => unit.AuraSpellIds is { Count: > 0 } names ? HighestHealingAbsorb(group, threshold, data => !HasAnyAura(data, names)) : null, UnitSelectorKind.HighestHealingAbsorbWithoutAura => aura is null ? null : HighestHealingAbsorb(group, threshold, data => !HasAura(data, aura.Value)), UnitSelectorKind.HighestHealingAbsorbWithAura => aura is null ? null : HighestHealingAbsorb(group, threshold, data => HasAura(data, aura.Value)), UnitSelectorKind.HighestHealingAbsorbWithAuraCount => aura is null || unit.AuraCount is null ? null : HighestHealingAbsorb(group, threshold, data => AuraEquals(data, aura.Value, unit.AuraCount.Value)), _ => null }; } /// 解析数量字段为整数。 public static int Resolve(ModuleCountField count, GameState state) { var group = state.Group; var threshold = ResolveThreshold( count.HealthThreshold, count.HealthThresholdField, state, IsHealingAbsorbKind(count.Kind) ? 0 : DefaultThreshold); if (RequiresAura(count.Kind) && (count.AuraSpellId is null || !GroupContainsAuraField(group, count.AuraSpellId.Value))) { return 0; } return count.Kind switch { CountKind.UnitsBelowHealth => CountUnits(group, data => BelowThreshold(data, threshold)), CountKind.UnitsWithoutAuraBelowHealth => count.AuraSpellId is null ? 0 : CountUnits(group, data => !HasAura(data, count.AuraSpellId.Value) && BelowThreshold(data, threshold)), CountKind.UnitsWithAura => count.AuraSpellId is null ? 0 : CountUnits(group, data => HasAura(data, count.AuraSpellId.Value)), CountKind.UnitsWithAuraBelowHealth => count.AuraSpellId is null ? 0 : CountUnits( group, data => HasAura(data, count.AuraSpellId.Value) && BelowThreshold(data, threshold)), CountKind.UnitsAboveHealingAbsorb => CountUnits( group, data => AboveHealingAbsorbThreshold(data, threshold)), CountKind.UnitsWithoutAuraAboveHealingAbsorb => count.AuraSpellId is null ? 0 : CountUnits( group, data => !HasAura(data, count.AuraSpellId.Value) && AboveHealingAbsorbThreshold(data, threshold)), CountKind.UnitsWithAuraAboveHealingAbsorb => count.AuraSpellId is null ? 0 : CountUnits( group, data => HasAura(data, count.AuraSpellId.Value) && AboveHealingAbsorbThreshold(data, threshold)), _ => 0 }; } /// 在职责 != 0 的单位里, 取生命值 < 阈值且满足 predicate 的最低血量单位(含 0 和负数)。 private static string? LowestHealth( IReadOnlyDictionary> group, int threshold, Func, bool> predicate) { string? lowestUnit = null; var lowestPct = threshold; for (var i = 1; i <= 30; i++) { var key = i.ToString(); if (!group.TryGetValue(key, out var data) || !RoleNotZero(data) || !predicate(data)) { continue; } if (!TryInt(GetField(data, "生命值"), out var pct)) { continue; } if (pct < threshold && pct < lowestPct) { lowestUnit = key; lowestPct = pct; } } return lowestUnit; } /// 按职责取首个(reverse=false)或逆序首个(reverse=true)且满足 predicate 的单位。 private static string? UnitWithRole( IReadOnlyDictionary> group, int role, bool reverse, Func, bool> predicate) { string? first = null; string? last = null; for (var i = 1; i <= 30; i++) { var key = i.ToString(); if (!group.TryGetValue(key, out var data)) { continue; } if (!TryInt(GetField(data, "职责"), out var r) || r != role || !predicate(data)) { continue; } first ??= key; last = key; } return reverse ? last : first; } /// 取拥有某光环(数值 > 0)且持续时间最长的单位。 private static string? UnitWithAura( IReadOnlyDictionary> group, long auraSpellId) { string? bestUnit = null; var bestDuration = 0; for (var i = 1; i <= 30; i++) { var key = i.ToString(); if (!group.TryGetValue(key, out var data) || !RoleNotZero(data)) { continue; } if (!TryInt(GetField(data, SpellFieldKey.AuraMember(auraSpellId)), out var duration) || duration <= 0) { continue; } if (bestUnit is null || duration > bestDuration) { bestUnit = key; bestDuration = duration; } } return bestUnit; } /// 取拥有指定驱散类型的首个单位。 private static string? UnitWithDispelType( IReadOnlyDictionary> group, int dispelType) { for (var i = 1; i <= 30; i++) { var key = i.ToString(); if (!group.TryGetValue(key, out var data) || !RoleNotZero(data)) { continue; } if (TryInt(GetField(data, "驱散"), out var val) && val == dispelType) { return key; } } return null; } /// /// 在职责 != 0 且满足 predicate 的单位里,取治疗吸收 > 阈值的最高单位; /// 没有符合阈值的单位时返回 null。 /// private static string? HighestHealingAbsorb( IReadOnlyDictionary> group, int threshold, Func, bool> predicate) { string? bestUnit = null; var highestAbsorb = 0; for (var i = 1; i <= 30; i++) { var key = i.ToString(); if (!group.TryGetValue(key, out var data) || !RoleNotZero(data) || !predicate(data)) { continue; } if (TryInt(GetField(data, "治疗吸收"), out var absorb) && absorb > 0 && absorb > threshold && absorb > highestAbsorb) { bestUnit = key; highestAbsorb = absorb; } } return bestUnit; } /// 统计职责 != 0 且满足 predicate 的单位数量。 private static int CountUnits( IReadOnlyDictionary> group, Func, bool> predicate) { var count = 0; for (var i = 1; i <= 30; i++) { if (group.TryGetValue(i.ToString(), out var data) && RoleNotZero(data) && predicate(data)) { count++; } } return count; } private static bool BelowThreshold(IReadOnlyDictionary data, int threshold) { return TryInt(GetField(data, "生命值"), out var pct) && pct > 0 && pct < threshold; } private static bool AboveHealingAbsorbThreshold( IReadOnlyDictionary data, int threshold) { return TryInt(GetField(data, "治疗吸收"), out var absorb) && absorb > threshold; } private static int ResolveThreshold( int? fixedValue, string? fieldName, GameState state, int defaultValue = DefaultThreshold) { return !string.IsNullOrWhiteSpace(fieldName) && ModuleConditionEvaluator.TryResolveInt(state, fieldName, out var dynamicValue) ? dynamicValue : fixedValue ?? defaultValue; } private static bool IsHealingAbsorbKind(UnitSelectorKind kind) => kind is UnitSelectorKind.HighestHealingAbsorb or UnitSelectorKind.HighestHealingAbsorbWithAnyAura or UnitSelectorKind.HighestHealingAbsorbWithoutAnyAura or UnitSelectorKind.HighestHealingAbsorbWithoutAura or UnitSelectorKind.HighestHealingAbsorbWithAura or UnitSelectorKind.HighestHealingAbsorbWithAuraCount; private static bool IsHealingAbsorbKind(CountKind kind) => kind is CountKind.UnitsAboveHealingAbsorb or CountKind.UnitsWithoutAuraAboveHealingAbsorb or CountKind.UnitsWithAuraAboveHealingAbsorb; private static bool AuraEquals(IReadOnlyDictionary data, long auraSpellId, int target) { return TryInt(GetField(data, SpellFieldKey.AuraMember(auraSpellId)), out var val) && val == target; } private static bool MatchesRoleFilter( IReadOnlyDictionary data, UnitRoleFilterKind? filter, int? role) { if (filter is null) { return true; } if (role is null || !TryInt(GetField(data, "职责"), out var actualRole)) { return false; } return filter == UnitRoleFilterKind.Include ? actualRole == role.Value : actualRole != role.Value; } // 职责为 None/无法解析时视为不跳过(返回 true), 与 utils.py 的 _role_not_zero 一致。 private static bool RoleNotZero(IReadOnlyDictionary data) { var role = GetField(data, "职责"); if (role is null) { return true; } return !TryInt(role, out var r) || r != 0; } private static bool HasAura(IReadOnlyDictionary data, long auraSpellId) { return TryInt(GetField(data, SpellFieldKey.AuraMember(auraSpellId)), out var n) && n != 0; } private static bool HasAnyAura(IReadOnlyDictionary data, IEnumerable auraSpellIds) { foreach (var spellId in auraSpellIds) { if (HasAura(data, spellId)) { return true; } } return false; } private static bool GroupContainsAuraField( IReadOnlyDictionary> group, long spellId) { var key = SpellFieldKey.AuraMember(spellId); return group.Values.Any(member => member.ContainsKey(key)); } private static bool RequiresAura(UnitSelectorKind kind) => kind is UnitSelectorKind.LowestHealthWithAnyAura or UnitSelectorKind.LowestHealthWithoutAnyAura or UnitSelectorKind.LowestHealthWithoutAura or UnitSelectorKind.LowestHealthWithAura or UnitSelectorKind.LowestHealthWithAuraCount or UnitSelectorKind.UnitWithRoleWithoutAura or UnitSelectorKind.UnitWithAura or UnitSelectorKind.HighestHealingAbsorbWithAnyAura or UnitSelectorKind.HighestHealingAbsorbWithoutAnyAura or UnitSelectorKind.HighestHealingAbsorbWithoutAura or UnitSelectorKind.HighestHealingAbsorbWithAura or UnitSelectorKind.HighestHealingAbsorbWithAuraCount; private static bool RequiresAura(CountKind kind) => kind is CountKind.UnitsWithoutAuraBelowHealth or CountKind.UnitsWithAura or CountKind.UnitsWithAuraBelowHealth or CountKind.UnitsWithoutAuraAboveHealingAbsorb or CountKind.UnitsWithAuraAboveHealingAbsorb; private static long? FirstAura(List? auraSpellIds) { return auraSpellIds is { Count: > 0 } ? auraSpellIds[0] : null; } private static object? GetField(IReadOnlyDictionary data, string field) { return data.TryGetValue(field, out var value) ? value : null; } // 模仿 Python int() 的 try/except: null 或无法解析返回 false, 调用侧据此跳过。 private static bool TryInt(object? value, out int result) { switch (value) { case int i: result = i; return true; case long l: result = (int)l; return true; case bool b: result = b ? 1 : 0; return true; case string s when int.TryParse(s, out var parsed): result = parsed; return true; default: result = 0; return false; } } }