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,5 +1,6 @@
|
|||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Player;
|
||||
using AcDream.Core.Properties;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.Core.Tests.Player;
|
||||
|
|
@ -170,6 +171,123 @@ public sealed class LocalPlayerStateTests
|
|||
Assert.Equal(1f, s.StaminaPercent!.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaxApprox_PrimaryAttributeModifierWithCollidingKeyDoesNotAffectHealth()
|
||||
{
|
||||
// Regression: PropertyAttribute.Strength and
|
||||
// PropertyAttribute2nd.MaxHealth both use numeric key 1. The HUD's
|
||||
// GetVitalMod path previously omitted retail's SecondAtt domain
|
||||
// filter, so a near-identity Strength modifier reproduced the live
|
||||
// report exactly: 99999 current / 99998 max.
|
||||
var book = new Spellbook(SpellTable.Create([TestSpell(1u)]));
|
||||
book.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
SpellId: 1u,
|
||||
LayerId: 1u,
|
||||
Duration: 60d,
|
||||
CasterGuid: 0u,
|
||||
StatModType: (uint)EnchantmentMath.EnchantmentTypeFlag.Attribute,
|
||||
StatModKey: 1u,
|
||||
StatModValue: 0.99999f,
|
||||
Bucket: 1u));
|
||||
var s = new LocalPlayerState(book);
|
||||
s.OnVitalUpdate(
|
||||
vitalId: 7u,
|
||||
ranks: 99_999u,
|
||||
start: 0u,
|
||||
xp: 0u,
|
||||
current: 99_999u);
|
||||
|
||||
Assert.Equal(99_999u, s.GetBaseMaxApprox(LocalPlayerState.VitalKind.Health));
|
||||
Assert.Equal(99_999u, s.GetMaxApprox(LocalPlayerState.VitalKind.Health));
|
||||
Assert.Equal(1f, s.HealthPercent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaxApprox_SecondaryAttributeModifierTruncatesLikeRetail()
|
||||
{
|
||||
// CEnchantmentRegistry::EnchantAttribute2nd ends in _ftol2. A
|
||||
// fractional secondary-attribute result is truncated, not rounded.
|
||||
var book = new Spellbook(SpellTable.Create([TestSpell(1u)]));
|
||||
book.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
SpellId: 1u,
|
||||
LayerId: 1u,
|
||||
Duration: 60d,
|
||||
CasterGuid: 0u,
|
||||
StatModType: (uint)EnchantmentMath.EnchantmentTypeFlag.SecondAtt,
|
||||
StatModKey: EnchantmentMath.StatKey.MaxHealth,
|
||||
StatModValue: 0.75f,
|
||||
Bucket: 2u));
|
||||
var s = new LocalPlayerState(book);
|
||||
s.OnVitalUpdate(
|
||||
vitalId: 7u,
|
||||
ranks: 100u,
|
||||
start: 0u,
|
||||
xp: 0u,
|
||||
current: 100u);
|
||||
|
||||
Assert.Equal(100u, s.GetMaxApprox(LocalPlayerState.VitalKind.Health));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaxApprox_PrimaryAttributeBuffsFeedVitalFormula_ExactLiveRegression()
|
||||
{
|
||||
// Live screenshot regression: the server-authoritative currents were
|
||||
// 38/75/25 while the HUD showed the raw-formula maxima 30/60/10.
|
||||
// Retail InqAttribute2nd evaluates Endurance/Self through enchanted
|
||||
// InqAttribute before applying the separate secondary-attribute mod.
|
||||
var book = new Spellbook(SpellTable.Create([TestSpell(1u), TestSpell(2u)]));
|
||||
book.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
SpellId: 1u,
|
||||
LayerId: 1u,
|
||||
Duration: 60d,
|
||||
CasterGuid: 0u,
|
||||
StatModType: (uint)EnchantmentMath.EnchantmentTypeFlag.Attribute,
|
||||
StatModKey: 2u, // Endurance
|
||||
StatModValue: 15f,
|
||||
Bucket: 2u));
|
||||
book.OnEnchantmentAdded(new ActiveEnchantmentRecord(
|
||||
SpellId: 2u,
|
||||
LayerId: 2u,
|
||||
Duration: 60d,
|
||||
CasterGuid: 0u,
|
||||
StatModType: (uint)EnchantmentMath.EnchantmentTypeFlag.Attribute,
|
||||
StatModKey: 6u, // Self
|
||||
StatModValue: 15f,
|
||||
Bucket: 2u));
|
||||
var s = new LocalPlayerState(book);
|
||||
s.OnAttributeUpdate(atType: 2u, ranks: 0u, start: 30u, xp: 0u);
|
||||
s.OnAttributeUpdate(atType: 6u, ranks: 0u, start: 10u, xp: 0u);
|
||||
s.OnVitalUpdate(vitalId: 7u, ranks: 0u, start: 15u, xp: 0u, current: 38u);
|
||||
s.OnVitalUpdate(vitalId: 8u, ranks: 0u, start: 30u, xp: 0u, current: 75u);
|
||||
s.OnVitalUpdate(vitalId: 9u, ranks: 0u, start: 0u, xp: 0u, current: 25u);
|
||||
|
||||
Assert.Equal(30u, s.GetBaseMaxApprox(LocalPlayerState.VitalKind.Health));
|
||||
Assert.Equal(60u, s.GetBaseMaxApprox(LocalPlayerState.VitalKind.Stamina));
|
||||
Assert.Equal(10u, s.GetBaseMaxApprox(LocalPlayerState.VitalKind.Mana));
|
||||
Assert.Equal(38u, s.GetMaxApprox(LocalPlayerState.VitalKind.Health));
|
||||
Assert.Equal(75u, s.GetMaxApprox(LocalPlayerState.VitalKind.Stamina));
|
||||
Assert.Equal(25u, s.GetMaxApprox(LocalPlayerState.VitalKind.Mana));
|
||||
Assert.Equal(1f, s.HealthPercent);
|
||||
Assert.Equal(1f, s.StaminaPercent);
|
||||
Assert.Equal(1f, s.ManaPercent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMaxApprox_HealthRoundsHalfEnduranceAndIncludesGearMaxHealth()
|
||||
{
|
||||
// SkillFormula::Calculate @ 0x00591960 rounds 45/2 to 23, and
|
||||
// InqAttribute2nd @ 0x00592020 adds property 379 before enchantment.
|
||||
var s = new LocalPlayerState();
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[(uint)PropertyInt.GearMaxHealth] = 7;
|
||||
s.OnProperties(properties);
|
||||
s.OnAttributeUpdate(atType: 2u, ranks: 0u, start: 45u, xp: 0u);
|
||||
s.OnVitalUpdate(vitalId: 7u, ranks: 0u, start: 10u, xp: 0u, current: 40u);
|
||||
|
||||
Assert.Equal(40u, s.GetBaseMaxApprox(LocalPlayerState.VitalKind.Health));
|
||||
Assert.Equal(40u, s.GetMaxApprox(LocalPlayerState.VitalKind.Health));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnVitalCurrent_UpdatesOnlyCurrent_LeavesRanksStartXpAlone()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue