using AcDream.Core.Items;
using AcDream.Core.Properties;
using AcDream.Core.Spells;
namespace AcDream.Core.Player;
///
/// Retail CACQualities::InqSkill @ 0x00592660 composition.
/// Keeps the augmentation/enchantment ordering in one presentation-independent
/// place so character UI and movement consume the same effective skill.
///
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);
///
/// Compose one skill exactly in retail order:
/// intrinsic + LumAugAllSkills/category bonus; EnchantSkill; then
/// Jack of All Trades and the specialized luminance bonus.
///
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;
}
}