using DatReaderWriter.DBObjs; using DatReaderWriter.Types; namespace AcDream.App.Net; /// /// Exact unsigned formula used by retail SkillFormula::Calculate @ /// 0x00591960. DAT reader fields are signed storage views, so their bits /// are deliberately reinterpreted as retail's unsigned W/X/Y/Z words. /// internal static class RetailSkillFormula { public static bool TryCalculate( SkillFormula formula, uint attribute1, uint attribute2, out uint result) { ArgumentNullException.ThrowIfNull(formula); uint divisor = unchecked((uint)formula.Divisor); if (divisor == 0u) { result = 0u; return false; } uint x = unchecked((uint)formula.Attribute1Multiplier); uint y = unchecked((uint)formula.Attribute2Multiplier); uint w = unchecked((uint)formula.AdditiveBonus); uint numerator = unchecked(x * attribute1 + y * attribute2 + w); result = (uint)Math.Floor((double)numerator / divisor + 0.5d); return true; } } /// /// Named live-session resolver for the attribute contribution to one skill. /// Table lookup and missing-property policy stay separate from retail math. /// internal sealed class LiveSkillCreditResolver(SkillTable? skillTable) { public uint Resolve( uint skillId, IReadOnlyDictionary attributeCurrents) { ArgumentNullException.ThrowIfNull(attributeCurrents); if (skillTable?.Skills is null || !skillTable.Skills.TryGetValue( (DatReaderWriter.Enums.SkillId)skillId, out var skillBase)) { return 0u; } SkillFormula formula = skillBase.Formula; attributeCurrents.TryGetValue( (uint)formula.Attribute1, out uint attribute1); attributeCurrents.TryGetValue( (uint)formula.Attribute2, out uint attribute2); return RetailSkillFormula.TryCalculate( formula, attribute1, attribute2, out uint result) ? result : 0u; } }