From 5b518df24adc131dc7c4bed19a183eb313f56010 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sun, 22 Dec 2024 12:19:42 +0100 Subject: [PATCH] Implements accuracy/evasion --- .../Libraries/BattleStatCalculator.cs | 5 ++++ .../Models/BattleFlow/MoveTurnExecutor.cs | 4 +-- .../ScriptHandling/Registry/ScriptRegistry.cs | 5 ++-- PkmnLib.Dynamic/ScriptHandling/Script.cs | 2 +- PkmnLib.Static/StatisticSet.cs | 16 +++++++++- .../Libraries/Gen7BattleStatCalculator.cs | 29 +++++++++++++++++++ .../Scripts/Side/AuroraVeilEffect.cs | 2 ++ 7 files changed, 57 insertions(+), 6 deletions(-) diff --git a/PkmnLib.Dynamic/Libraries/BattleStatCalculator.cs b/PkmnLib.Dynamic/Libraries/BattleStatCalculator.cs index 98fa7d1..2275927 100644 --- a/PkmnLib.Dynamic/Libraries/BattleStatCalculator.cs +++ b/PkmnLib.Dynamic/Libraries/BattleStatCalculator.cs @@ -27,4 +27,9 @@ public interface IBattleStatCalculator /// Calculate a single boosted stat of a Pokemon, including stat boosts. /// uint CalculateBoostedStat(IPokemon pokemon, Statistic stat); + + /// + /// Calculates the accuracy for a move, taking into account any accuracy modifiers. + /// + byte CalculateModifiedAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, byte moveAccuracy); } \ No newline at end of file diff --git a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs index 00e2d2c..caef755 100644 --- a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs +++ b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs @@ -140,10 +140,10 @@ internal static class MoveTurnExecutor // modifying it. if (accuracy != 255) { - executingMove.RunScriptHook(x => x.ChangeAccuracy(executingMove, target, hitIndex, ref accuracy)); + accuracy = battle.Library.StatCalculator.CalculateModifiedAccuracy(executingMove, target, + hitIndex, accuracy); } - // TODO: Deal with accuracy/evasion stats. if (accuracy < 100 && battle.Random.GetInt(100) >= accuracy) { executingMove.RunScriptHook(x => x.OnMoveMiss(executingMove, target)); diff --git a/PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs b/PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs index 43bf61d..0df56c5 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Registry/ScriptRegistry.cs @@ -43,9 +43,10 @@ public class ScriptRegistry if (type == null) throw new ArgumentNullException(nameof(type)); - var constructor = type.GetConstructor(Type.EmptyTypes); + var constructor = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + null, Type.EmptyTypes, null); if (constructor == null) - throw new ArgumentException("The type must have a parameterless constructor."); + throw new ArgumentException($"Type {type} does not have a parameterless constructor."); // We create a lambda that creates a new instance of the script type. // This is more performant than using Activator.CreateInstance. diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index b13cef2..bf51a89 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -207,7 +207,7 @@ public abstract class Script /// This function allows a script to modify the accuracy of a move used. This value represents /// the percentage accuracy, so anything above 100% will make it always hit. /// - public virtual void ChangeAccuracy(IExecutingMove move, IPokemon target, byte hit, ref byte accuracy) + public virtual void ChangeAccuracyModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier) { } diff --git a/PkmnLib.Static/StatisticSet.cs b/PkmnLib.Static/StatisticSet.cs index 32fe807..cabd135 100644 --- a/PkmnLib.Static/StatisticSet.cs +++ b/PkmnLib.Static/StatisticSet.cs @@ -248,7 +248,7 @@ public abstract record ClampedStatisticSet : StatisticSet { } - private static T Clamp(T value, T min, T max) + protected static T Clamp(T value, T min, T max) { if (value.CompareTo(min) < 0) return min; @@ -307,6 +307,20 @@ public record StatBoostStatisticSet : ClampedStatisticSet /// protected override sbyte Max => 6; + private sbyte _evasion; + public sbyte Evasion + { + get => _evasion; + set => _evasion = Clamp(value, Min, Max); + } + + private sbyte _accuracy; + public sbyte Accuracy + { + get => _accuracy; + set => _accuracy = Clamp(value, Min, Max); + } + /// public StatBoostStatisticSet() : base(0, 0, 0, 0, 0, 0) { diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7BattleStatCalculator.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7BattleStatCalculator.cs index 4f8cf83..e64ac8b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7BattleStatCalculator.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7BattleStatCalculator.cs @@ -1,5 +1,7 @@ +using System; using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Models; +using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Static; namespace PkmnLib.Plugin.Gen7.Libraries; @@ -48,6 +50,33 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator return (uint)boostedStat; } + /// + public byte CalculateModifiedAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, byte moveAccuracy) + { + var accuracyModifier = 1.0f; + executingMove.RunScriptHook(x => x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier)); + var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier); + var targetEvasion = target.StatBoost.Evasion; + var userAccuracy = executingMove.User.StatBoost.Accuracy; + var difference = targetEvasion - userAccuracy; + var statModifier = difference switch + { + > 0 => 3.0f / (3.0f + difference), + < 0 => 3.0f + Math.Abs(difference) / 3.0f, + _ => 1.0f + }; + modifiedAccuracy = (int)(modifiedAccuracy * statModifier); + modifiedAccuracy = modifiedAccuracy switch + { + > 255 => 255, + < 0 => 0, + _ => modifiedAccuracy + }; + // NOTE: the main games also consider friendship here, but we don't yet have the concept of a "player Pokémon" + // in the battle system, so for now we're just ignoring that. + return (byte)modifiedAccuracy; + } + private static uint CalculateHealthStat(IPokemon pokemon) { var baseValue = (ulong)pokemon.Form.BaseStats.Hp; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/AuroraVeilEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/AuroraVeilEffect.cs index 794365e..a5bfaac 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/AuroraVeilEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/AuroraVeilEffect.cs @@ -27,6 +27,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Side; public class AuroraVeilEffect : Script { public int NumberOfTurns { get; set; } + + private AuroraVeilEffect(){} public AuroraVeilEffect(int numberOfTurns) {