fix(ui): restore retail vitals and window interactions
This commit is contained in:
parent
4d84456c21
commit
1bd2b30291
36 changed files with 1462 additions and 86 deletions
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Properties;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.Core.Player;
|
||||
|
|
@ -305,7 +306,7 @@ public sealed class LocalPlayerState
|
|||
/// </summary>
|
||||
public uint? GetMaxApprox(VitalKind kind)
|
||||
{
|
||||
uint? baseValue = GetBaseMaxApprox(kind);
|
||||
uint? baseValue = GetMaxBeforeSecondaryEnchantments(kind);
|
||||
if (baseValue is not uint unbuffed) return null;
|
||||
// Preserve the "no data" sentinel — when the unbuffed max is 0
|
||||
// we lack the inputs to compute anything reasonable. The retail
|
||||
|
|
@ -315,12 +316,13 @@ public sealed class LocalPlayerState
|
|||
var mod = _spellbook?.GetVitalMod(StatKeyForKind(kind))
|
||||
?? EnchantmentMath.VitalMod.Identity;
|
||||
// Apply: (unbuffed * mult) + additive, then clamp to retail's
|
||||
// min-vital floor (5 if base >= 5 else 1) — matches
|
||||
// CreatureVital::GetMaxValue at PDB 0x0058F2DD.
|
||||
// min-vital floor (5 if base >= 5 else 1). The final cast is
|
||||
// retail CEnchantmentRegistry::EnchantAttribute2nd's _ftol2
|
||||
// conversion at 0x00594787: truncate toward zero, do not round.
|
||||
float buffed = (unbuffed * mod.Multiplier) + mod.Additive;
|
||||
uint minFloor = unbuffed >= 5 ? 5u : 1u;
|
||||
if (buffed < minFloor) buffed = minFloor;
|
||||
return (uint)System.Math.Round(buffed);
|
||||
return (uint)buffed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -333,7 +335,27 @@ public sealed class LocalPlayerState
|
|||
if (vital is null) return null;
|
||||
return vital.Value.Ranks
|
||||
+ vital.Value.Start
|
||||
+ AttributeContribution(kind);
|
||||
+ AttributeContribution(kind, effective: false)
|
||||
+ GearHealthBonus(kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>CACQualities::InqAttribute2nd @ 0x00592020</c> computes a
|
||||
/// maximum vital's formula contribution through <c>InqAttribute</c> with
|
||||
/// enchantments enabled, adds <c>GearMaxHealth</c> (property 379) for
|
||||
/// health, and only then calls <c>EnchantAttribute2nd</c>. Keeping this
|
||||
/// stage separate prevents primary-attribute records from leaking into
|
||||
/// the secondary-attribute modifier while still allowing buffed
|
||||
/// Endurance/Self to feed the vital formula.
|
||||
/// </summary>
|
||||
private uint? GetMaxBeforeSecondaryEnchantments(VitalKind kind)
|
||||
{
|
||||
VitalSnapshot? vital = Get(kind);
|
||||
if (vital is null) return null;
|
||||
return vital.Value.Ranks
|
||||
+ vital.Value.Start
|
||||
+ AttributeContribution(kind, effective: true)
|
||||
+ GearHealthBonus(kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -622,21 +644,37 @@ public sealed class LocalPlayerState
|
|||
// coefficients, but the values themselves don't change between dat
|
||||
// versions in retail.
|
||||
|
||||
private uint AttributeContribution(VitalKind kind)
|
||||
private uint AttributeContribution(VitalKind kind, bool effective)
|
||||
{
|
||||
uint endurance = GetAttrCurrent(AttributeKind.Endurance, effective);
|
||||
uint self = GetAttrCurrent(AttributeKind.Self, effective);
|
||||
switch (kind)
|
||||
{
|
||||
case VitalKind.Health:
|
||||
return GetAttrCurrent(AttributeKind.Endurance) / 2u;
|
||||
// SecondaryAttributeTable's SkillFormula is
|
||||
// floor((Endurance / 2) + 0.5), not integer truncation.
|
||||
return (endurance / 2u) + (endurance & 1u);
|
||||
case VitalKind.Stamina:
|
||||
return GetAttrCurrent(AttributeKind.Endurance);
|
||||
return endurance;
|
||||
case VitalKind.Mana:
|
||||
return GetAttrCurrent(AttributeKind.Self);
|
||||
return self;
|
||||
default:
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
private uint GetAttrCurrent(AttributeKind kind) =>
|
||||
_attrs.TryGetValue(kind, out var a) ? a.Current : 0u;
|
||||
private uint GetAttrCurrent(AttributeKind kind, bool effective)
|
||||
{
|
||||
if (!_attrs.TryGetValue(kind, out var attribute)) return 0u;
|
||||
if (!effective || _spellbook is null) return attribute.Current;
|
||||
int value = GetEffectiveAttribute(kind) ?? 0;
|
||||
return value > 0 ? (uint)value : 0u;
|
||||
}
|
||||
|
||||
private uint GearHealthBonus(VitalKind kind)
|
||||
{
|
||||
if (kind != VitalKind.Health) return 0u;
|
||||
int value = _properties.GetInt((uint)PropertyInt.GearMaxHealth);
|
||||
return value > 0 ? (uint)value : 0u;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue