Implements accuracy/evasion

This commit is contained in:
Deukhoofd 2024-12-22 12:19:42 +01:00
parent 06ce7fd38d
commit 5b518df24a
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
7 changed files with 57 additions and 6 deletions

View File

@ -27,4 +27,9 @@ public interface IBattleStatCalculator
/// Calculate a single boosted stat of a Pokemon, including stat boosts. /// Calculate a single boosted stat of a Pokemon, including stat boosts.
/// </summary> /// </summary>
uint CalculateBoostedStat(IPokemon pokemon, Statistic stat); uint CalculateBoostedStat(IPokemon pokemon, Statistic stat);
/// <summary>
/// Calculates the accuracy for a move, taking into account any accuracy modifiers.
/// </summary>
byte CalculateModifiedAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, byte moveAccuracy);
} }

View File

@ -140,10 +140,10 @@ internal static class MoveTurnExecutor
// modifying it. // modifying it.
if (accuracy != 255) 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) if (accuracy < 100 && battle.Random.GetInt(100) >= accuracy)
{ {
executingMove.RunScriptHook(x => x.OnMoveMiss(executingMove, target)); executingMove.RunScriptHook(x => x.OnMoveMiss(executingMove, target));

View File

@ -43,9 +43,10 @@ public class ScriptRegistry
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); 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) 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. // We create a lambda that creates a new instance of the script type.
// This is more performant than using Activator.CreateInstance. // This is more performant than using Activator.CreateInstance.

View File

@ -207,7 +207,7 @@ public abstract class Script
/// This function allows a script to modify the accuracy of a move used. This value represents /// 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. /// the percentage accuracy, so anything above 100% will make it always hit.
/// </summary> /// </summary>
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)
{ {
} }

View File

@ -248,7 +248,7 @@ public abstract record ClampedStatisticSet<T> : StatisticSet<T>
{ {
} }
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) if (value.CompareTo(min) < 0)
return min; return min;
@ -307,6 +307,20 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
/// <inheritdoc /> /// <inheritdoc />
protected override sbyte Max => 6; 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);
}
/// <inheritdoc cref="StatBoostStatisticSet"/> /// <inheritdoc cref="StatBoostStatisticSet"/>
public StatBoostStatisticSet() : base(0, 0, 0, 0, 0, 0) public StatBoostStatisticSet() : base(0, 0, 0, 0, 0, 0)
{ {

View File

@ -1,5 +1,7 @@
using System;
using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static; using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Libraries; namespace PkmnLib.Plugin.Gen7.Libraries;
@ -48,6 +50,33 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator
return (uint)boostedStat; return (uint)boostedStat;
} }
/// <inheritdoc />
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) private static uint CalculateHealthStat(IPokemon pokemon)
{ {
var baseValue = (ulong)pokemon.Form.BaseStats.Hp; var baseValue = (ulong)pokemon.Form.BaseStats.Hp;

View File

@ -28,6 +28,8 @@ public class AuroraVeilEffect : Script
{ {
public int NumberOfTurns { get; set; } public int NumberOfTurns { get; set; }
private AuroraVeilEffect(){}
public AuroraVeilEffect(int numberOfTurns) public AuroraVeilEffect(int numberOfTurns)
{ {
NumberOfTurns = numberOfTurns; NumberOfTurns = numberOfTurns;