108 lines
3.9 KiB
C#
108 lines
3.9 KiB
C#
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;
|
|
}
|
|
}
|