refactor(net): own live session composition
Extract reset, selection, entered-world, and route construction behind LiveSessionHost while preserving the sole LiveSessionController authority. Retain partial route and subscription cleanup for retry, and replace the embedded ACE-only shortcut with the exact named-retail unsigned skill formula. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
18d4b999de
commit
557eb7ef6b
22 changed files with 1430 additions and 236 deletions
72
src/AcDream.App/Net/RetailSkillFormula.cs
Normal file
72
src/AcDream.App/Net/RetailSkillFormula.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.App.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Exact unsigned formula used by retail <c>SkillFormula::Calculate @
|
||||
/// 0x00591960</c>. DAT reader fields are signed storage views, so their bits
|
||||
/// are deliberately reinterpreted as retail's unsigned W/X/Y/Z words.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Named live-session resolver for the attribute contribution to one skill.
|
||||
/// Table lookup and missing-property policy stay separate from retail math.
|
||||
/// </summary>
|
||||
internal sealed class LiveSkillCreditResolver(SkillTable? skillTable)
|
||||
{
|
||||
public uint Resolve(
|
||||
uint skillId,
|
||||
IReadOnlyDictionary<uint, uint> 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue