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(); if (attribute == null) continue; if (attribute.Category == ScriptCategory.Move) { var failureMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(m => m.GetCustomAttribute() != 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( 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() != 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( 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(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; } } } }