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)
{