PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7/AI/ExplicitAIFunctions.cs

89 lines
4.1 KiB
C#

using System.Linq.Expressions;
using System.Reflection;
using PkmnLib.Dynamic.AI.Explicit;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.AI;
public static class ExplicitAIFunctions
{
public static void RegisterAIFunctions(ExplicitAIHandlers handlers)
{
var baseType = typeof(Script);
foreach (var type in typeof(ExplicitAIFunctions).Assembly.GetTypes().Where(t => baseType.IsAssignableFrom(t)))
{
var attribute = type.GetCustomAttribute<ScriptAttribute>();
if (attribute == null)
continue;
if (attribute.Category == ScriptCategory.Move)
{
var failureMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(m =>
m.GetCustomAttribute<AIMoveFailureFunctionAttribute>() != null);
if (failureMethod != null)
{
if (failureMethod.ReturnType != typeof(bool) || failureMethod.GetParameters().Length != 1 ||
failureMethod.GetParameters()[0].ParameterType != typeof(MoveOption))
{
throw new InvalidOperationException(
$"Method {failureMethod.Name} in {type.Name} must return bool and take a single MoveOption parameter.");
}
var optionParam = Expression.Parameter(typeof(MoveOption), "option");
var functionExpression = Expression.Lambda<AIBoolHandler>(
Expression.Call(null, failureMethod, optionParam), optionParam).Compile();
handlers.MoveFailureCheck.Add(attribute.Name, functionExpression);
}
var scoreMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(m =>
m.GetCustomAttribute<AIMoveScoreFunctionAttribute>() != null);
if (scoreMethod != null)
{
if (scoreMethod.ReturnType != typeof(void) || scoreMethod.GetParameters().Length != 2 ||
scoreMethod.GetParameters()[0].ParameterType != typeof(MoveOption) ||
scoreMethod.GetParameters()[1].ParameterType != typeof(int).MakeByRefType())
{
throw new InvalidOperationException(
$"Method {scoreMethod.Name} in {type.Name} must return void and take a MoveOption and an int by reference parameter.");
}
var optionParam = Expression.Parameter(typeof(MoveOption), "option");
var scoreParam = Expression.Parameter(typeof(int).MakeByRefType(), "score");
var functionExpression = Expression.Lambda<AIScoreMoveHandler>(
Expression.Call(null, scoreMethod, optionParam, scoreParam), optionParam, scoreParam).Compile();
handlers.MoveEffectScore.Add(attribute.Name, functionExpression);
}
}
}
handlers.GeneralMoveAgainstTargetScore.Add("predicated_damage", PredictedDamageScore);
}
private static void PredictedDamageScore(MoveOption option, ref int score)
{
var target = option.Target;
if (target == null)
return;
if (option.Move.Move.Category == MoveCategory.Status)
return;
var damage = option.EstimatedDamage;
if (target.Volatile.TryGet<SubstituteEffect>(out var substitute))
{
var health = substitute.Health;
score += (int)Math.Min(15.0f * damage / health, 20);
return;
}
score += (int)Math.Min(15.0f * damage / target.CurrentHealth, 30);
if (damage > target.CurrentHealth * 1.1f)
{
score += 10;
if ((option.Move.Move.HasFlag("multi_hit") && target.CurrentHealth == target.MaxHealth &&
target.ActiveAbility?.Name == "sturdy") || target.HasHeldItem("focus_sash"))
{
score += 8;
}
}
}
}