feat(player): port retail augmentation stat chain
This commit is contained in:
parent
0cb60d98a0
commit
461a1fb7b4
22 changed files with 953 additions and 138 deletions
|
|
@ -64,6 +64,7 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
/// </summary>
|
||||
private int _runSkillBase = -1;
|
||||
private int _jumpSkillBase = -1;
|
||||
private PlayerSkillMath.AugmentationBonuses _movementSkillAugmentations;
|
||||
|
||||
public RuntimeCharacterState(SpellTable? spellTable = null)
|
||||
{
|
||||
|
|
@ -142,13 +143,14 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
&& MovementSkills.PlayerKillerStatus == -1
|
||||
&& MovementSkills.LastPkAttackTimestamp is null
|
||||
&& _runSkillBase == -1
|
||||
&& _jumpSkillBase == -1);
|
||||
&& _jumpSkillBase == -1
|
||||
&& _movementSkillAugmentations == default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign P Slice P1 (2026-07-30): stores the pre-<c>EnchantSkill</c>
|
||||
/// base run/jump skill (PlayerDescription's formulaBonus+init+ranks)
|
||||
/// and pushes the vitae/enchantment-adjusted result into
|
||||
/// and pushes the augmentation/vitae/enchantment-adjusted result into
|
||||
/// <see cref="MovementSkills"/> — the SAME call shape
|
||||
/// <c>LiveSessionEventRouter</c>'s pre-P1 <c>onSkillsUpdated</c> callback
|
||||
/// already used (<c>MovementSkills.Update(runSkill, jumpSkill)</c>), now
|
||||
|
|
@ -165,6 +167,22 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
RecomputeMovementSkills();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs the player-quality augmentation terms consumed by retail
|
||||
/// <c>CACQualities::InqRunRate</c>/<c>InqJumpVelocity</c>. The object
|
||||
/// table remains authoritative for live PropertyInt updates; Runtime
|
||||
/// retains only this immutable derived snapshot.
|
||||
/// </summary>
|
||||
public void UpdateMovementSkillAugmentations(
|
||||
PlayerSkillMath.AugmentationBonuses augmentations)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_movementSkillAugmentations == augmentations)
|
||||
return;
|
||||
_movementSkillAugmentations = augmentations;
|
||||
RecomputeMovementSkills();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-derives the adjusted run/jump skill from the stored base plus the
|
||||
/// CURRENT spellbook state (vitae + skill enchantments) — matching
|
||||
|
|
@ -178,10 +196,10 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
private void RecomputeMovementSkills()
|
||||
{
|
||||
int run = _runSkillBase >= 0
|
||||
? ApplySkillEnchantments(_runSkillBase, RunSkillId)
|
||||
? CalculateMovementSkill(_runSkillBase, RunSkillId)
|
||||
: -1;
|
||||
int jump = _jumpSkillBase >= 0
|
||||
? ApplySkillEnchantments(_jumpSkillBase, JumpSkillId)
|
||||
? CalculateMovementSkill(_jumpSkillBase, JumpSkillId)
|
||||
: -1;
|
||||
// #266 apparatus (permanent, low-volume — fires only on skill-base or
|
||||
// enchantment changes, the [snap] class): the full stat-chain state at
|
||||
|
|
@ -196,14 +214,19 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
MovementSkills.Update(run, jump);
|
||||
}
|
||||
|
||||
private int ApplySkillEnchantments(int baseSkill, uint skillId)
|
||||
private int CalculateMovementSkill(int baseSkill, uint skillId)
|
||||
{
|
||||
EnchantmentMath.VitalMod mod = Spellbook.GetSkillMod(skillId);
|
||||
float adjusted = baseSkill * mod.Multiplier + mod.Additive;
|
||||
// CEnchantmentRegistry::EnchantSkill pc 416240: floor to 0 below
|
||||
// 0.5, then truncate (retail _ftol2, a C-style cast).
|
||||
if (adjusted < 0.5f) adjusted = 0f;
|
||||
return (int)adjusted;
|
||||
float vitae = EnchantmentMath.GetVitaeMultiplier(
|
||||
Spellbook.ActiveEnchantments);
|
||||
uint advancementClass = LocalPlayer.GetSkill(skillId)?.Status ?? 0u;
|
||||
return PlayerSkillMath.Calculate(
|
||||
baseSkill,
|
||||
skillId,
|
||||
advancementClass,
|
||||
_movementSkillAugmentations,
|
||||
mod,
|
||||
vitae).EffectiveLevel;
|
||||
}
|
||||
|
||||
private void OnEnchantmentsChangedForMovement() => RecomputeMovementSkills();
|
||||
|
|
@ -331,6 +354,7 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
Try(Options.ResetSession, ref failures);
|
||||
_runSkillBase = -1;
|
||||
_jumpSkillBase = -1;
|
||||
_movementSkillAugmentations = default;
|
||||
Try(MovementSkills.ResetSession, ref failures);
|
||||
if (failures is not null)
|
||||
{
|
||||
|
|
@ -355,6 +379,7 @@ public sealed class RuntimeCharacterState : IDisposable
|
|||
Try(Options.ResetSession, ref failures);
|
||||
_runSkillBase = -1;
|
||||
_jumpSkillBase = -1;
|
||||
_movementSkillAugmentations = default;
|
||||
Try(MovementSkills.ResetSession, ref failures);
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -200,27 +200,27 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
|
|||
SubscribeToRecompute<ClientObject>(
|
||||
h => inventory.Objects.ObjectAdded += h,
|
||||
h => inventory.Objects.ObjectAdded -= h,
|
||||
() => { RecomputeBurden(inventory, character); RecomputePvpStatus(inventory, character); });
|
||||
() => RecomputePlayerQualities(inventory, character));
|
||||
SubscribeToRecompute<ClientObject>(
|
||||
h => inventory.Objects.ObjectUpdated += h,
|
||||
h => inventory.Objects.ObjectUpdated -= h,
|
||||
() => { RecomputeBurden(inventory, character); RecomputePvpStatus(inventory, character); });
|
||||
() => RecomputePlayerQualities(inventory, character));
|
||||
SubscribeToRecompute<ClientObject>(
|
||||
h => inventory.Objects.ObjectRemoved += h,
|
||||
h => inventory.Objects.ObjectRemoved -= h,
|
||||
() => { RecomputeBurden(inventory, character); RecomputePvpStatus(inventory, character); });
|
||||
() => RecomputePlayerQualities(inventory, character));
|
||||
SubscribeToRecompute<ClientObjectMove>(
|
||||
h => inventory.Objects.ObjectMoved += h,
|
||||
h => inventory.Objects.ObjectMoved -= h,
|
||||
() => { RecomputeBurden(inventory, character); RecomputePvpStatus(inventory, character); });
|
||||
() => RecomputePlayerQualities(inventory, character));
|
||||
SubscribeToRecompute<uint>(
|
||||
h => inventory.Objects.ContainerContentsReplaced += h,
|
||||
h => inventory.Objects.ContainerContentsReplaced -= h,
|
||||
() => { RecomputeBurden(inventory, character); RecomputePvpStatus(inventory, character); });
|
||||
() => RecomputePlayerQualities(inventory, character));
|
||||
SubscribeParameterless(
|
||||
h => inventory.Objects.Cleared += h,
|
||||
h => inventory.Objects.Cleared -= h,
|
||||
() => { RecomputeBurden(inventory, character); RecomputePvpStatus(inventory, character); });
|
||||
() => RecomputePlayerQualities(inventory, character));
|
||||
Subscribe<LocalPlayerState.AttributeKind>(
|
||||
h => character.Character.LocalPlayer.AttributeChanged += h,
|
||||
h => character.Character.LocalPlayer.AttributeChanged -= h,
|
||||
|
|
@ -365,7 +365,8 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
|
|||
/// </summary>
|
||||
private static void RecomputeBurden(
|
||||
LiveInventorySessionBindings inventory,
|
||||
LiveCharacterSessionBindings character)
|
||||
LiveCharacterSessionBindings character,
|
||||
bool notify = true)
|
||||
{
|
||||
uint player = inventory.PlayerGuid();
|
||||
ClientObject? playerObject = inventory.Objects.Get(player);
|
||||
|
|
@ -381,6 +382,22 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
|
|||
: inventory.Objects.SumCarriedBurden(player);
|
||||
float load = EncumbranceSystem.Load(capacity, burden);
|
||||
character.Character.MovementSkills.UpdateBurden(load);
|
||||
if (notify)
|
||||
character.OnMovementStatsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
private static void RecomputePlayerQualities(
|
||||
LiveInventorySessionBindings inventory,
|
||||
LiveCharacterSessionBindings character)
|
||||
{
|
||||
RecomputeBurden(inventory, character, notify: false);
|
||||
RecomputePvpStatus(inventory, character, notify: false);
|
||||
|
||||
uint player = inventory.PlayerGuid();
|
||||
PropertyBundle properties = inventory.Objects.Get(player)?.Properties
|
||||
?? character.Character.LocalPlayer.Properties;
|
||||
character.Character.UpdateMovementSkillAugmentations(
|
||||
PlayerSkillMath.AugmentationBonuses.FromProperties(properties));
|
||||
character.OnMovementStatsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
|
|
@ -397,7 +414,8 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
|
|||
/// </summary>
|
||||
private static void RecomputePvpStatus(
|
||||
LiveInventorySessionBindings inventory,
|
||||
LiveCharacterSessionBindings character)
|
||||
LiveCharacterSessionBindings character,
|
||||
bool notify = true)
|
||||
{
|
||||
uint player = inventory.PlayerGuid();
|
||||
ClientObject? playerObject = inventory.Objects.Get(player);
|
||||
|
|
@ -415,7 +433,8 @@ public sealed class LiveSessionEventRouter : ILiveSessionEventRouting
|
|||
character.Character.MovementSkills.UpdatePlayerKillerStatus(
|
||||
pkStatus,
|
||||
lastPkAttackTimestamp);
|
||||
character.OnMovementStatsUpdated?.Invoke();
|
||||
if (notify)
|
||||
character.OnMovementStatsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue