From c795f20e54fe7b23ee088967e7c69e9396318411 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 5 Jul 2025 14:56:25 +0200 Subject: [PATCH] Implement highest damage AI, further work on AI runner, random fixes --- AI/AIRunner/Program.cs | 2 +- AI/AIRunner/TestCommandRunner.cs | 62 +++++++++---- PkmnLib.Dynamic/AI/HighestDamageAI.cs | 92 +++++++++++++++++++ PkmnLib.Dynamic/AI/PokemonAI.cs | 2 +- PkmnLib.Dynamic/Models/Choices/MoveChoice.cs | 11 ++- PkmnLib.Dynamic/Models/Pokemon.cs | 5 + .../Scripts/GenericScriptTests.cs | 21 +++++ .../Scripts/Battle/WonderRoomEffect.cs | 1 + .../Scripts/Moves/Bounce.cs | 1 + .../PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs | 1 + .../PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs | 1 + .../PkmnLib.Plugin.Gen7/Scripts/Moves/Fly.cs | 1 + .../Scripts/Moves/MeFirst.cs | 5 + .../Scripts/Moves/PhantomForce.cs | 1 + .../Scripts/Moves/ShadowForce.cs | 1 + .../Scripts/Moves/SkyDrop.cs | 1 + .../Scripts/Pokemon/ChargeBounceEffect.cs | 9 ++ .../Scripts/Pokemon/ChargeFlyEffect.cs | 9 ++ .../Scripts/Pokemon/ChargeSkyDropEffect.cs | 9 ++ .../Scripts/Pokemon/DigEffect.cs | 9 ++ .../Scripts/Pokemon/DiveEffect.cs | 9 ++ .../Scripts/Pokemon/HelpingHandEffect.cs | 1 + .../Scripts/Pokemon/LuckyChantEffect.cs | 1 + .../Scripts/Pokemon/PhantomForceCharge.cs | 9 ++ .../Scripts/Pokemon/ShadowForceCharge.cs | 9 ++ .../Scripts/Side/MatBlockEffect.cs | 1 + .../Scripts/Side/QuickGuardEffect.cs | 1 + .../Scripts/Side/SafeguardEffect.cs | 1 + .../Scripts/Side/SpotlightEffect.cs | 1 + .../Scripts/Utils/TurnChoiceHelper.cs | 10 +- 30 files changed, 261 insertions(+), 26 deletions(-) create mode 100644 PkmnLib.Dynamic/AI/HighestDamageAI.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/GenericScriptTests.cs diff --git a/AI/AIRunner/Program.cs b/AI/AIRunner/Program.cs index 034a302..ee9035a 100644 --- a/AI/AIRunner/Program.cs +++ b/AI/AIRunner/Program.cs @@ -12,7 +12,7 @@ internal static class Program private static Task Main(string[] args) { - Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); + Log.Logger = new LoggerConfiguration().MinimumLevel.Information().WriteTo.Console().CreateLogger(); Log.Information("Starting AI Runner..."); _availableAIs = AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()) .Where(type => type.IsSubclassOf(typeof(PokemonAI)) && !type.IsAbstract).Select(Activator.CreateInstance) diff --git a/AI/AIRunner/TestCommandRunner.cs b/AI/AIRunner/TestCommandRunner.cs index 7419486..df155f6 100644 --- a/AI/AIRunner/TestCommandRunner.cs +++ b/AI/AIRunner/TestCommandRunner.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using PkmnLib.Dynamic.AI; using PkmnLib.Dynamic.Libraries; using PkmnLib.Dynamic.Models; @@ -12,32 +13,58 @@ public static class TestCommandRunner { internal static async Task RunTestCommand(PokemonAI ai1, PokemonAI ai2, int battles) { + var t1 = DateTime.UtcNow; var library = DynamicLibraryImpl.Create([ new Gen7Plugin(), ]); Log.Information("Running {Battles} battles between {AI1} and {AI2}", battles, ai1.Name, ai2.Name); var averageTimePerTurnPerBattle = new List(battles); - var results = new List(battles); - var random = new RandomImpl(); + var results = new ConcurrentBag(); + var rootRandom = new RandomImpl(); + + const int maxTasks = 10; + var battleTasks = new Task[maxTasks]; + var randoms = new IRandom[maxTasks]; + for (var i = 0; i < maxTasks; i++) + { + randoms[i] = new RandomImpl(rootRandom.GetInt()); + battleTasks[i] = Task.CompletedTask; // Initialize tasks to avoid null references + } // Here you would implement the logic to run the AI scripts against each other. // This is a placeholder for demonstration purposes. for (var i = 0; i < battles; i++) { - Log.Information("Battle {BattleNumber}: {AI1} vs {AI2}", i + 1, ai1.Name, ai2.Name); - var battle = GenerateBattle(library, 3, random); - var timePerTurn = new List(20); - while (!battle.HasEnded) + var taskIndex = i % maxTasks; + var index = i; + var battleTask = Task.Run(async () => { - var res = await GetAndSetChoices(battle, ai1, ai2); - timePerTurn.Add(res.MsPerTurn); + Log.Information("Battle {BattleNumber}: {AI1} vs {AI2}", index + 1, ai1.Name, ai2.Name); + var random = randoms[taskIndex]; + var battle = GenerateBattle(library, 3, random); + var timePerTurn = new List(20); + while (!battle.HasEnded) + { + var res = await GetAndSetChoices(battle, ai1, ai2); + timePerTurn.Add(res.MsPerTurn); + } + var result = battle.Result; + Log.Information("Battle {BattleNumber} ended with result: {Result}", index + 1, result); + averageTimePerTurnPerBattle.Add(timePerTurn.Average()); + results.Add(result.Value); + }); + battleTasks[taskIndex] = battleTask; + if (i % maxTasks == maxTasks - 1 || i == battles - 1) + { + Log.Debug("Starting {TaskCount} tasks", maxTasks); + await Task.WhenAll(battleTasks); + Log.Debug("Batch of {TaskCount} tasks completed", maxTasks); + battleTasks = new Task[maxTasks]; // Reset tasks for the next batch } - var result = battle.Result; - Log.Information("Battle {BattleNumber} ended with result: {Result}", i + 1, result); - averageTimePerTurnPerBattle.Add(timePerTurn.Average()); - results.Add(result.Value); } - Log.Information("All battles completed"); + + var t2 = DateTime.UtcNow; + Log.Information("{Amount} battles completed in {Duration} ms", battles, (t2 - t1).TotalMilliseconds); var averageTimePerTurn = averageTimePerTurnPerBattle.Average(); Log.Information("Average time per turn: {AverageTimePerTurn} ms", averageTimePerTurn); @@ -45,8 +72,11 @@ public static class TestCommandRunner var winCount2 = results.Count(x => x.WinningSide == 1); var drawCount = results.Count(x => x.WinningSide == null); - Log.Information("Results: {AI1} wins: {WinCount1}, {AI2} wins: {WinCount2}, Draws: {DrawCount}", ai1.Name, - winCount1, ai2.Name, winCount2, drawCount); + var winRate1 = winCount1 / (double)battles * 100; + var winRate2 = winCount2 / (double)battles * 100; + Log.Information("AI {AI1} win rate: {WinRate1:F3}% ({WinCount1} wins)", ai1.Name, winRate1, winCount1); + Log.Information("AI {AI2} win rate: {WinRate2:F3}% ({WinCount2} wins)", ai2.Name, winRate2, winCount2); + Log.Information("Draw rate: {DrawRate:F3}% ({DrawCount} draws)", drawCount / (double)battles * 100, drawCount); } private static PokemonPartyImpl GenerateParty(IDynamicLibrary library, int length, IRandom random) @@ -64,7 +94,7 @@ public static class TestCommandRunner IsHidden = false, Index = abilityIndex, }, level, 0, species.GetRandomGender(random), 0, nature.Name); - var moves = defaultForm.Moves.GetDistinctLevelMoves().OrderBy(x => random.GetInt()).Take(4); + var moves = defaultForm.Moves.GetDistinctLevelMoves().OrderBy(_ => random.GetInt()).Take(4); foreach (var move in moves) mon.LearnMove(move, MoveLearnMethod.LevelUp, 255); diff --git a/PkmnLib.Dynamic/AI/HighestDamageAI.cs b/PkmnLib.Dynamic/AI/HighestDamageAI.cs new file mode 100644 index 0000000..4ab6a1c --- /dev/null +++ b/PkmnLib.Dynamic/AI/HighestDamageAI.cs @@ -0,0 +1,92 @@ +using PkmnLib.Dynamic.Models; +using PkmnLib.Dynamic.Models.Choices; +using PkmnLib.Static; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Dynamic.AI; + +public class HighestDamageAI : PokemonAI +{ + /// + public HighestDamageAI() : base("highest_damage") + { + } + + /// + public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon) + { + var opponentSide = pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0; + var opponent = battle.Sides[opponentSide].Pokemon.WhereNotNull().FirstOrDefault(x => x.IsUsable); + var moves = pokemon.Moves.WhereNotNull().Where(x => battle.CanUse(new MoveChoice(pokemon, x, opponentSide, 0))) + .ToList(); + if (opponent == null) + { + var move = moves.FirstOrDefault(); + if (move != null) + { + return new MoveChoice(pokemon, move, opponentSide, 0); + } + return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, 0); + } + + var movesWithDamage = moves.Select(move => + { + var hitData = new CustomHitData + { + BasePower = move.MoveData.BasePower, + Effectiveness = + battle.Library.StaticLibrary.Types.GetEffectiveness(move.MoveData.MoveType, opponent.Types), + Type = move.MoveData.MoveType, + }; + return new + { + Move = move, + Damage = battle.Library.DamageCalculator.GetDamage(null, move.MoveData.Category, pokemon, opponent, 1, + 0, hitData), + }; + }).OrderByDescending(x => x.Damage).ToList(); + if (movesWithDamage.Count == 0) + { + return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, 0); + } + var bestMove = movesWithDamage.First().Move; + return new MoveChoice(pokemon, bestMove, opponentSide, 0); + } + + private class CustomHitData : IHitData + { + /// + public bool IsCritical => false; + + /// + public ushort BasePower { get; init; } + + /// + public float Effectiveness { get; init; } + + /// + public uint Damage => 0; + + /// + public TypeIdentifier? Type { get; init; } + + /// + public bool IsContact => false; + + /// + public bool HasFailed => false; + + /// + public void Fail() + { + } + + /// + public void SetFlag(StringKey flag) + { + } + + /// + public bool HasFlag(StringKey flag) => false; + } +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/AI/PokemonAI.cs b/PkmnLib.Dynamic/AI/PokemonAI.cs index b63775a..7404cda 100644 --- a/PkmnLib.Dynamic/AI/PokemonAI.cs +++ b/PkmnLib.Dynamic/AI/PokemonAI.cs @@ -8,7 +8,7 @@ namespace PkmnLib.Dynamic.AI; /// /// The base class for implementing an AI for Pokémon. /// -[PublicAPI] +[PublicAPI, UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] public abstract class PokemonAI { /// diff --git a/PkmnLib.Dynamic/Models/Choices/MoveChoice.cs b/PkmnLib.Dynamic/Models/Choices/MoveChoice.cs index 431a691..b756d3d 100644 --- a/PkmnLib.Dynamic/Models/Choices/MoveChoice.cs +++ b/PkmnLib.Dynamic/Models/Choices/MoveChoice.cs @@ -38,6 +38,8 @@ public interface IMoveChoice : ITurnChoice /// Dictionary? AdditionalData { get; } + void SetAdditionalData(StringKey key, object? value); + /// /// Volatile effects that are applied to the move choice. /// @@ -84,7 +86,14 @@ public class MoveChoice : TurnChoice, IMoveChoice public ScriptContainer Script { get; set; } = new(); /// - public Dictionary? AdditionalData { get; } + public Dictionary? AdditionalData { get; private set; } + + /// + public void SetAdditionalData(StringKey key, object? value) + { + AdditionalData ??= new Dictionary(); + AdditionalData[key] = value; + } /// public IScriptSet Volatile { get; } diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 77bd337..ba2806e 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -936,6 +936,8 @@ public class PokemonImpl : ScriptSource, IPokemon AbilityScript.Clear(); } + private (IAbility, AbilityIndex)? _abilityCache; + /// public IAbility? ActiveAbility { @@ -945,9 +947,12 @@ public class PokemonImpl : ScriptSource, IPokemon return null; if (OverrideAbility != null) return OverrideAbility; + if (_abilityCache is not null && _abilityCache.Value.Item2 == AbilityIndex) + return _abilityCache.Value.Item1; var ability = Form.GetAbility(AbilityIndex); if (!Library.StaticLibrary.Abilities.TryGet(ability, out var abilityObj)) throw new KeyNotFoundException($"Ability {ability} not found."); + _abilityCache = (abilityObj, AbilityIndex); return abilityObj; } } diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/GenericScriptTests.cs b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/GenericScriptTests.cs new file mode 100644 index 0000000..bb6fbde --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/GenericScriptTests.cs @@ -0,0 +1,21 @@ +using PkmnLib.Dynamic.ScriptHandling; +using PkmnLib.Dynamic.ScriptHandling.Registry; +using Assembly = System.Reflection.Assembly; + +namespace PkmnLib.Plugin.Gen7.Tests.Scripts; + +public class GenericScriptTests +{ + [Test] + public async Task EveryScriptHasAttribute() + { + var scripts = typeof(Gen7Plugin).Assembly.GetTypes() + .Where(t => t.IsSubclassOf(typeof(Script)) && !t.IsAbstract); + foreach (var script in scripts) + { + var attributes = script.GetCustomAttributes(typeof(ScriptAttribute), false); + await Assert.That(attributes).IsNotEmpty().Because( + $"Script {script.Name} does not have the Script attribute defined."); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/WonderRoomEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/WonderRoomEffect.cs index c85d010..70cf43d 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/WonderRoomEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/WonderRoomEffect.cs @@ -2,6 +2,7 @@ using PkmnLib.Static.Moves; namespace PkmnLib.Plugin.Gen7.Scripts.Battle; +[Script(ScriptCategory.Battle, "wonder_room_effect")] public class WonderRoomEffect : Script, IScriptChangeDefensiveStatValue, IScriptOnEndTurn { public int TurnsLeft { get; private set; } = 5; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Bounce.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Bounce.cs index 63a9b52..94f6a1f 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Bounce.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Bounce.cs @@ -12,6 +12,7 @@ public class Bounce : Script, IScriptPreventMove, IScriptOnBeforeMove, IScriptOn return; move.User.Volatile.Add(new ChargeBounceEffect(move.User)); + move.MoveChoice.SetAdditionalData("bounce_charge", true); move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("bounce_charge", new Dictionary { { "user", move.User }, diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs index 671f3d3..baf292c 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs @@ -12,6 +12,7 @@ public class Dig : Script, IScriptPreventMove, IScriptOnBeforeMove return; move.User.Volatile.Add(new DigEffect(move.User)); + move.MoveChoice.SetAdditionalData("dig_charge", true); move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dig_charge", new Dictionary { { "user", move.User }, diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs index e8de4da..7a34db5 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs @@ -12,6 +12,7 @@ public class Dive : Script, IScriptPreventMove, IScriptOnSecondaryEffect return; move.User.Volatile.Add(new DiveEffect(move.User)); + move.MoveChoice.SetAdditionalData("dive_charge", true); move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dive_charge", new Dictionary { { "user", move.User }, diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Fly.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Fly.cs index 4fb5042..0d76e98 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Fly.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Fly.cs @@ -12,6 +12,7 @@ public class Fly : Script, IScriptPreventMove, IScriptOnBeforeMove return; move.User.Volatile.Add(new ChargeFlyEffect(move.User)); + move.MoveChoice.SetAdditionalData("fly_charge", true); move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("fly_charge", new Dictionary { { "user", move.User }, diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MeFirst.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MeFirst.cs index e97480f..31a7600 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MeFirst.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MeFirst.cs @@ -23,6 +23,11 @@ public class MeFirst : Script, IScriptChangeMove choice.Fail(); return; } + if (battleData.Battle.Library.MiscLibrary.IsReplacementChoice(targetMove)) + { + choice.Fail(); + return; + } var targetMoveData = targetMove.ChosenMove.MoveData; moveName = targetMoveData.Name; choice.Volatile.Add(new MeFirstPowerBoost()); diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PhantomForce.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PhantomForce.cs index a36170a..dd021a6 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PhantomForce.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PhantomForce.cs @@ -16,6 +16,7 @@ public class PhantomForce : Script, IScriptPreventMove, IScriptOnSecondaryEffect return; move.User.Volatile.Add(new PhantomForceCharge(move.User)); + move.MoveChoice.SetAdditionalData("phantom_force_charge", true); move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("phantom_force_charge", new Dictionary { diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ShadowForce.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ShadowForce.cs index 279233c..1c5fdbd 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ShadowForce.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ShadowForce.cs @@ -12,6 +12,7 @@ public class ShadowForce : Script, IScriptPreventMove, IScriptOnSecondaryEffect return; move.User.Volatile.Add(new ShadowForceCharge(move.User)); + move.MoveChoice.SetAdditionalData("shadow_force_charge", true); move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("shadow_force_charge", new Dictionary { diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/SkyDrop.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/SkyDrop.cs index 1fad7e2..6ec47a2 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/SkyDrop.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/SkyDrop.cs @@ -12,6 +12,7 @@ public class SkyDrop : Script, IScriptPreventMove, IScriptOnBeforeMove return; move.User.Volatile.Add(new ChargeSkyDropEffect(move.User)); + move.MoveChoice.SetAdditionalData("sky_drop_charge", true); move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("sky_drop_charge", new Dictionary { { "user", move.User }, diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeBounceEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeBounceEffect.cs index a91a673..aed2f75 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeBounceEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeBounceEffect.cs @@ -33,4 +33,13 @@ public class ChargeBounceEffect : Script, IScriptForceTurnSelection, IScriptChan if (!move.UseMove.HasFlag("effective_against_fly")) damage *= 2; } + + /// + public override void OnAfterMoveChoice(IMoveChoice moveChoice) + { + if (moveChoice.AdditionalData?.ContainsKey("bounce_charge") != true) + { + RemoveSelf(); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeFlyEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeFlyEffect.cs index 9915dc2..3bfbae0 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeFlyEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeFlyEffect.cs @@ -33,4 +33,13 @@ public class ChargeFlyEffect : Script, IScriptForceTurnSelection, IScriptChangeI if (!move.UseMove.HasFlag("effective_against_fly")) damage *= 2; } + + /// + public override void OnAfterMoveChoice(IMoveChoice moveChoice) + { + if (moveChoice.AdditionalData?.ContainsKey("fly_charge") != true) + { + RemoveSelf(); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeSkyDropEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeSkyDropEffect.cs index 63d5dc6..af918d7 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeSkyDropEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeSkyDropEffect.cs @@ -33,4 +33,13 @@ public class ChargeSkyDropEffect : Script, IScriptForceTurnSelection, IScriptCha if (!move.UseMove.HasFlag("effective_against_fly")) damage *= 2; } + + /// + public override void OnAfterMoveChoice(IMoveChoice moveChoice) + { + if (moveChoice.AdditionalData?.ContainsKey("sky_drop_charge") != true) + { + RemoveSelf(); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DigEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DigEffect.cs index bbd8681..af6a088 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DigEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DigEffect.cs @@ -32,4 +32,13 @@ public class DigEffect : Script, IScriptForceTurnSelection, IScriptChangeIncomin if (!move.UseMove.HasFlag("effective_against_underground")) damage *= 2; } + + /// + public override void OnAfterMoveChoice(IMoveChoice moveChoice) + { + if (moveChoice.AdditionalData?.ContainsKey("dig_charge") != true) + { + RemoveSelf(); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DiveEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DiveEffect.cs index 59d0f37..a321a83 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DiveEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DiveEffect.cs @@ -32,4 +32,13 @@ public class DiveEffect : Script, IScriptForceTurnSelection, IScriptChangeIncomi if (!move.UseMove.HasFlag("effective_against_underwater")) damage *= 2; } + + /// + public override void OnAfterMoveChoice(IMoveChoice moveChoice) + { + if (moveChoice.AdditionalData?.ContainsKey("dive_charge") != true) + { + RemoveSelf(); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HelpingHandEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HelpingHandEffect.cs index ff8b69e..f62b27c 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HelpingHandEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/HelpingHandEffect.cs @@ -1,5 +1,6 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; +[Script(ScriptCategory.Pokemon, "helping_hand")] public class HelpingHandEffect : Script, IScriptChangeBasePower, IScriptOnEndTurn { /// diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LuckyChantEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LuckyChantEffect.cs index 7bba1ed..6967b69 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LuckyChantEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LuckyChantEffect.cs @@ -1,5 +1,6 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; +[Script(ScriptCategory.Pokemon, "lucky_chant")] public class LuckyChantEffect : Script, IScriptBlockCriticalHit, IScriptOnEndTurn { private int _turnsLeft = 5; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PhantomForceCharge.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PhantomForceCharge.cs index f2436f0..b67c893 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PhantomForceCharge.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PhantomForceCharge.cs @@ -24,4 +24,13 @@ public class PhantomForceCharge : Script, IScriptForceTurnSelection, IScriptBloc { block = true; } + + /// + public override void OnAfterMoveChoice(IMoveChoice moveChoice) + { + if (moveChoice.AdditionalData?.ContainsKey("phantom_force_charge") != true) + { + moveChoice.User.Volatile.Remove(ScriptUtils.ResolveName()); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ShadowForceCharge.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ShadowForceCharge.cs index 4bab54b..8b186a9 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ShadowForceCharge.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ShadowForceCharge.cs @@ -24,4 +24,13 @@ public class ShadowForceCharge : Script, IScriptForceTurnSelection, IScriptBlock { block = true; } + + /// + public override void OnAfterMoveChoice(IMoveChoice moveChoice) + { + if (moveChoice.AdditionalData?.ContainsKey("shadow_force_charge") != true) + { + RemoveSelf(); + } + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/MatBlockEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/MatBlockEffect.cs index 8924bc4..0ea8381 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/MatBlockEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/MatBlockEffect.cs @@ -2,6 +2,7 @@ using PkmnLib.Static.Moves; namespace PkmnLib.Plugin.Gen7.Scripts.Side; +[Script(ScriptCategory.Side, "mat_block")] public class MatBlockEffect : Script, IScriptOnEndTurn, IScriptBlockIncomingHit { /// diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/QuickGuardEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/QuickGuardEffect.cs index 90fe118..7b9fbbb 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/QuickGuardEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/QuickGuardEffect.cs @@ -1,5 +1,6 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Side; +[Script(ScriptCategory.Side, "quick_guard")] public class QuickGuardEffect : Script, IScriptIsInvulnerableToMove, IScriptOnEndTurn { /// diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/SafeguardEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/SafeguardEffect.cs index 5c1f12f..f2a194c 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/SafeguardEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/SafeguardEffect.cs @@ -2,6 +2,7 @@ using PkmnLib.Plugin.Gen7.Scripts.Pokemon; namespace PkmnLib.Plugin.Gen7.Scripts.Side; +[Script(ScriptCategory.Side, "safe_guard")] public class SafeguardEffect : Script { /// diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/SpotlightEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/SpotlightEffect.cs index 5e8af0c..b8379c9 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/SpotlightEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/SpotlightEffect.cs @@ -2,6 +2,7 @@ using PkmnLib.Dynamic.BattleFlow; namespace PkmnLib.Plugin.Gen7.Scripts.Side; +[Script(ScriptCategory.Side, "spotlight")] public class SpotlightEffect : Script, IScriptChangeIncomingTargets, IScriptOnEndTurn { private readonly byte _position; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Utils/TurnChoiceHelper.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Utils/TurnChoiceHelper.cs index 5ff4281..69210da 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Utils/TurnChoiceHelper.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Utils/TurnChoiceHelper.cs @@ -4,14 +4,10 @@ public static class TurnChoiceHelper { public static IMoveChoice CreateMoveChoice(IPokemon owner, StringKey moveName, byte targetSide, byte targetPosition) { - var move = owner.Moves.FirstOrDefault(x => x?.MoveData.Name == moveName); - if (move == null) - { - if (!owner.Library.StaticLibrary.Moves.TryGet(moveName, out var moveData)) - throw new Exception($"Move '{moveName}' not found in move library."); + if (!owner.Library.StaticLibrary.Moves.TryGet(moveName, out var moveData)) + throw new Exception($"Move '{moveName}' not found in move library."); - move = new LearnedMoveImpl(moveData, MoveLearnMethod.Unknown); - } + var move = new LearnedMoveImpl(moveData, MoveLearnMethod.Unknown); return new MoveChoice(owner, move, targetSide, targetPosition); } } \ No newline at end of file