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
108
src/AcDream.Core/Player/PlayerSkillMath.cs
Normal file
108
src/AcDream.Core/Player/PlayerSkillMath.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Properties;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.Core.Player;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>CACQualities::InqSkill @ 0x00592660</c> composition.
|
||||
/// Keeps the augmentation/enchantment ordering in one presentation-independent
|
||||
/// place so character UI and movement consume the same effective skill.
|
||||
/// </summary>
|
||||
public static class PlayerSkillMath
|
||||
{
|
||||
public readonly record struct AugmentationBonuses(
|
||||
int AllSkills,
|
||||
bool JackOfAllTrades,
|
||||
int SkilledSpecialized,
|
||||
bool SkilledMelee,
|
||||
bool SkilledMissile,
|
||||
bool SkilledMagic)
|
||||
{
|
||||
public static AugmentationBonuses FromProperties(PropertyBundle properties)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(properties);
|
||||
return new(
|
||||
Positive(properties.GetInt((uint)PropertyInt.LumAugAllSkills)),
|
||||
properties.GetInt((uint)PropertyInt.AugmentationJackOfAllTrades) > 0,
|
||||
Positive(properties.GetInt((uint)PropertyInt.LumAugSkilledSpec)),
|
||||
properties.GetInt((uint)PropertyInt.AugmentationSkilledMelee) > 0,
|
||||
properties.GetInt((uint)PropertyInt.AugmentationSkilledMissile) > 0,
|
||||
properties.GetInt((uint)PropertyInt.AugmentationSkilledMagic) > 0);
|
||||
}
|
||||
|
||||
public int BeforeEnchantments(uint skillId)
|
||||
{
|
||||
int category = skillId switch
|
||||
{
|
||||
// Retail switch at pc 0x005926F6. A positive category
|
||||
// augmentation adds ten once; its stored rank is not multiplied.
|
||||
0x29u or 0x2Cu or 0x2Du or 0x2Eu or 0x31u when SkilledMelee => 10,
|
||||
0x2Fu when SkilledMissile => 10,
|
||||
0x1Fu or 0x20u or 0x21u or 0x22u or 0x2Bu when SkilledMagic => 10,
|
||||
_ => 0,
|
||||
};
|
||||
return SaturatingAdd(AllSkills, category);
|
||||
}
|
||||
|
||||
public int AfterEnchantments(uint advancementClass)
|
||||
{
|
||||
int result = JackOfAllTrades ? 5 : 0;
|
||||
if (advancementClass == 3u)
|
||||
result = SaturatingAdd(result, SaturatingMultiply(SkilledSpecialized, 2));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct Value(
|
||||
int UnenchantedLevel,
|
||||
int EffectiveLevel,
|
||||
int VitaeModifier);
|
||||
|
||||
/// <summary>
|
||||
/// Compose one skill exactly in retail order:
|
||||
/// intrinsic + LumAugAllSkills/category bonus; EnchantSkill; then
|
||||
/// Jack of All Trades and the specialized luminance bonus.
|
||||
/// </summary>
|
||||
public static Value Calculate(
|
||||
int intrinsicLevel,
|
||||
uint skillId,
|
||||
uint advancementClass,
|
||||
AugmentationBonuses augmentations,
|
||||
EnchantmentMath.VitalMod enchantment,
|
||||
float vitaeMultiplier)
|
||||
{
|
||||
int intrinsic = Math.Max(0, intrinsicLevel);
|
||||
int unenchanted = SaturatingAdd(
|
||||
intrinsic,
|
||||
augmentations.BeforeEnchantments(skillId));
|
||||
int enchanted = EnchantmentMath.EnchantSkill(
|
||||
enchantment,
|
||||
(uint)unenchanted);
|
||||
int effective = SaturatingAdd(
|
||||
enchanted,
|
||||
augmentations.AfterEnchantments(advancementClass));
|
||||
int vitaeModifier = EnchantmentMath.SkillVitaeModifier(
|
||||
vitaeMultiplier,
|
||||
(uint)unenchanted);
|
||||
return new Value(unenchanted, effective, vitaeModifier);
|
||||
}
|
||||
|
||||
private static int Positive(int value) => value > 0 ? value : 0;
|
||||
|
||||
private static int SaturatingAdd(int left, int right)
|
||||
{
|
||||
long result = (long)left + right;
|
||||
return result > int.MaxValue
|
||||
? int.MaxValue
|
||||
: result < int.MinValue ? int.MinValue : (int)result;
|
||||
}
|
||||
|
||||
private static int SaturatingMultiply(int left, int right)
|
||||
{
|
||||
long result = (long)left * right;
|
||||
return result > int.MaxValue
|
||||
? int.MaxValue
|
||||
: result < int.MinValue ? int.MinValue : (int)result;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue