using System; using System.Collections.Generic; namespace AcDream.Core.Spells; /// /// Pure port of retail's ordinary in-effect registry projection: /// CEnchantmentRegistry::GetEnchantmentsInEffect @ 0x00594540, /// GetEnchantmentsInEffectFromList @ 0x00594430, /// Duel @ 0x005942B0, and Enchantment::Duel @ 0x005CACE0. /// public static class EnchantmentRegistryProjection { /// /// Merges multiplicative then additive records and retains one winner per /// spell category. Higher power wins; equal power is replaced only by a /// later start time. Vitae and cooldown registries do not participate. /// public static IReadOnlyList GetEnchantmentsInEffect( IEnumerable enchantments) { ArgumentNullException.ThrowIfNull(enchantments); ActiveEnchantmentRecord[] source = enchantments as ActiveEnchantmentRecord[] ?? [.. enchantments]; var result = new List(); DuelList(source, bucket: 1u, result); DuelList(source, bucket: 2u, result); return result; } private static void DuelList( IReadOnlyList source, uint bucket, List result) { for (int sourceIndex = 0; sourceIndex < source.Count; sourceIndex++) { ActiveEnchantmentRecord candidate = source[sourceIndex]; if (candidate.Bucket != bucket) continue; int incumbentIndex = result.FindIndex(existing => existing.SpellCategory == candidate.SpellCategory); if (incumbentIndex >= 0) { ActiveEnchantmentRecord incumbent = result[incumbentIndex]; if (!CandidateWins(incumbent, candidate)) continue; // Retail removes the losing node, then appends the winner. result.RemoveAt(incumbentIndex); } result.Add(candidate); } } private static bool CandidateWins( ActiveEnchantmentRecord incumbent, ActiveEnchantmentRecord candidate) { // Retail Enchantment::_power_level is signed int even though the wire // field is read as four raw bytes. int incumbentPower = unchecked((int)incumbent.PowerLevel); int candidatePower = unchecked((int)candidate.PowerLevel); return candidatePower > incumbentPower || (candidatePower == incumbentPower && candidate.StartTime > incumbent.StartTime); } }