using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Models; using PkmnLib.Static; using PkmnLib.Static.Moves; namespace PkmnLib.Scripts.Gen7.Libraries; public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator { /// public uint GetDamage(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData) { var category = executingMove.UseMove.Category; if (category == MoveCategory.Status) return 0; if (hitData.Effectiveness == 0) return 0; var levelModifier = (2.0f * executingMove.User.Level) / 5.0f + 2.0f; var basePower = (float)hitData.BasePower; var statModifier = GetStatModifier(executingMove, target, hitNumber, hitData); var damageModifier = GetDamageModifier(executingMove, target, hitNumber, hitData); var floatDamage = MathF.Floor(levelModifier * basePower); floatDamage = MathF.Floor(floatDamage * statModifier); floatDamage = MathF.Floor(floatDamage / 50.0f) + 2.0f; floatDamage = MathF.Floor(floatDamage * damageModifier); if (executingMove.TargetCount > 1) floatDamage = MathF.Floor(floatDamage * 0.75f); if (hitData.IsCritical) { var critModifier = 1.5f; // TODO: script hook to change the critical modifier floatDamage = MathF.Floor(floatDamage * critModifier); } if (hasRandomness) { var battle = target.BattleData?.Battle; if (battle == null) throw new InvalidOperationException("Randomness is enabled, but no battle is set."); var random = battle.Random; var randomFactor = random.GetInt(85, 101) / 100.0f; floatDamage = MathF.Floor(floatDamage * randomFactor); } if (executingMove.User.Types.Contains(hitData.Type)) { var stabModifier = 1.5f; // TODO: script hook to change the STAB modifier floatDamage = MathF.Floor(floatDamage * stabModifier); } floatDamage = MathF.Floor(floatDamage * hitData.Effectiveness); uint damage = floatDamage switch { > uint.MaxValue => uint.MaxValue, < 1 => 1, _ => (uint)floatDamage }; // TODO: script hook to modify the damage // TODO: script hook to modify incoming damage return damage; } /// public byte GetBasePower(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData) { if (executingMove.UseMove.Category == MoveCategory.Status) return 0; var basePower = hitData.BasePower; // TODO: script hook to modify the base power return basePower; } /// [SuppressMessage("ReSharper", "UnreachableSwitchArmDueToIntegerAnalysis")] // disabled because of the TODO public bool IsCritical(IBattle battle, IExecutingMove executingMove, IPokemon target, byte hitNumber) { if (executingMove.UseMove.Category == MoveCategory.Status) return false; byte critStage = 0; // TODO: script hook to modify the crit stage var random = battle.Random; return critStage switch { 0 => random.GetInt(24) == 0, 1 => random.GetInt(8) == 0, 2 => random.GetInt(2) == 0, _ => true }; } private static float GetStatModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData) { var category = executingMove.UseMove.Category; if (category == MoveCategory.Status) return 1; var (offensive, defensive) = category switch { MoveCategory.Physical => (Statistic.Attack, Statistic.Defense), _ => (Statistic.SpecialAttack, Statistic.SpecialDefense), }; // Check if we can bypass the defensive stat boost on the target. We default to this if the // move is critical, and the target has a defensive stat boost of > 0, but a script is // allowed to change this. var bypassDefense = hitData.IsCritical && target.StatBoost.GetStatistic(defensive) > 0; // TODO: script hook // Check if we can bypass the offensive stat boost on the user. We default to this if the // move is critical, and the user has an offensive stat boost of < 0, but a script is // allowed to change this. var bypassOffense = hitData.IsCritical && executingMove.User.StatBoost.GetStatistic(offensive) < 0; // TODO: script hook var userStats = executingMove.User.BoostedStats; if (bypassOffense) userStats = executingMove.User.FlatStats; var offensiveStat = userStats.GetStatistic(offensive); var targetStats = target.BoostedStats; if (bypassDefense) targetStats = target.FlatStats; var defensiveStat = targetStats.GetStatistic(defensive); // TODO: script hook to modify the stats above var modifier = (float)offensiveStat / defensiveStat; // TODO: script hook to modify the modifier return modifier; } /// /// Gets the damage modifier. This is a value that defaults to 1.0, but can be modified by scripts /// to apply a raw modifier to the damage. /// private static float GetDamageModifier(IExecutingMove executingMove, IPokemon target, byte hitNumber, HitData hitData) { var modifier = 1.0f; // TODO: script hook to modify the modifier return modifier; } }