Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage. Co-Authored-By: Codex <noreply@openai.com>
67 lines
2.6 KiB
C#
67 lines
2.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace AcDream.Core.Spells;
|
|
|
|
/// <summary>
|
|
/// Pure port of retail's ordinary in-effect registry projection:
|
|
/// <c>CEnchantmentRegistry::GetEnchantmentsInEffect @ 0x00594540</c>,
|
|
/// <c>GetEnchantmentsInEffectFromList @ 0x00594430</c>,
|
|
/// <c>Duel @ 0x005942B0</c>, and <c>Enchantment::Duel @ 0x005CACE0</c>.
|
|
/// </summary>
|
|
public static class EnchantmentRegistryProjection
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static IReadOnlyList<ActiveEnchantmentRecord> GetEnchantmentsInEffect(
|
|
IEnumerable<ActiveEnchantmentRecord> enchantments)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(enchantments);
|
|
ActiveEnchantmentRecord[] source = enchantments as ActiveEnchantmentRecord[]
|
|
?? [.. enchantments];
|
|
var result = new List<ActiveEnchantmentRecord>();
|
|
DuelList(source, bucket: 1u, result);
|
|
DuelList(source, bucket: 2u, result);
|
|
return result;
|
|
}
|
|
|
|
private static void DuelList(
|
|
IReadOnlyList<ActiveEnchantmentRecord> source,
|
|
uint bucket,
|
|
List<ActiveEnchantmentRecord> 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);
|
|
}
|
|
}
|