From 7f5088b763426ff25350231d05bbaa83d0dd3885 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 8 Mar 2025 15:48:33 +0100 Subject: [PATCH] More moves implemented --- .../Models/BattleFlow/TurnRunner.cs | 5 +- PkmnLib.Dynamic/Models/Pokemon.cs | 7 + PkmnLib.Tests/Data/Moves.json | 122 +++++++++++++++--- .../Scripts/Battle/IonDelugeEffect.cs | 17 +++ .../Scripts/Moves/Ingrain.cs | 13 ++ .../Scripts/Moves/Instruct.cs | 29 +++++ .../Scripts/Moves/IonDeluge.cs | 13 ++ .../Scripts/Moves/Judgement.cs | 38 ++++++ .../Scripts/Moves/KingsShield.cs | 16 +++ .../Scripts/Moves/KnockOff.cs | 14 ++ .../Scripts/Moves/LaserFocus.cs | 13 ++ .../Scripts/Moves/LastResort.cs | 40 ++++++ .../Scripts/Pokemon/IngrainEffect.cs | 56 ++++++++ .../Scripts/Pokemon/KingsShield.cs | 18 +++ .../Scripts/Pokemon/LaserFocusEffect.cs | 12 ++ 15 files changed, 393 insertions(+), 20 deletions(-) create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/IonDelugeEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Ingrain.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Instruct.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IonDeluge.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Judgement.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KingsShield.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KnockOff.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LaserFocus.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LastResort.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/IngrainEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/KingsShield.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LaserFocusEffect.cs diff --git a/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs b/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs index ec87d28..238bb4f 100644 --- a/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs +++ b/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs @@ -71,7 +71,10 @@ public static class TurnRunner } } - private static void ExecuteChoice(IBattle battle, ITurnChoice choice) + /// + /// Runs a single choice in a battle. + /// + public static void ExecuteChoice(IBattle battle, ITurnChoice choice) { if (choice is IPassChoice) return; diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 797ea0a..94e2ed6 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -737,6 +737,13 @@ public class PokemonImpl : ScriptSource, IPokemon /// public IItem? RemoveHeldItem() { + if (HeldItem is not null) + { + if (HeldItem.Category == ItemCategory.FormChanger) + { + return null; + } + } var previous = HeldItem; HeldItem = null; return previous; diff --git a/PkmnLib.Tests/Data/Moves.json b/PkmnLib.Tests/Data/Moves.json index a380f59..5680291 100755 --- a/PkmnLib.Tests/Data/Moves.json +++ b/PkmnLib.Tests/Data/Moves.json @@ -5795,7 +5795,10 @@ "flags": [ "snatch", "nonskybattle" - ] + ], + "effect": { + "name": "ingrain" + } }, { "name": "instruct", @@ -5809,7 +5812,10 @@ "flags": [ "protect", "ignore-substitute" - ] + ], + "effect": { + "name": "instruct" + } }, { "name": "ion_deluge", @@ -5820,7 +5826,10 @@ "priority": 1, "target": "All", "category": "status", - "flags": [] + "flags": [], + "effect": { + "name": "ion_deluge" + } }, { "name": "iron_defense", @@ -5833,7 +5842,13 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_user_defense", + "parameters": { + "amount": 2 + } + } }, { "name": "iron_head", @@ -5848,7 +5863,11 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "flinch", + "chance": 30 + } }, { "name": "iron_tail", @@ -5863,7 +5882,14 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_defense", + "chance": 30, + "parameters": { + "amount": -1 + } + } }, { "name": "judgment", @@ -5877,7 +5903,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "judgement" + } }, { "name": "jump_kick", @@ -5893,7 +5922,10 @@ "protect", "mirror", "gravity" - ] + ], + "effect": { + "name": "high_jump_kick" + } }, { "name": "karate_chop", @@ -5908,7 +5940,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "increased_critical_stage" + } }, { "name": "kinesis", @@ -5923,7 +5958,13 @@ "protect", "reflectable", "mirror" - ] + ], + "effect": { + "name": "change_target_accuracy", + "parameters": { + "amount": -1 + } + } }, { "name": "kings_shield", @@ -5934,7 +5975,10 @@ "priority": 4, "target": "Self", "category": "status", - "flags": [] + "flags": [], + "effect": { + "name": "kings_shield" + } }, { "name": "knock_off", @@ -5949,7 +5993,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "knock_off" + } }, { "name": "lands_wrath", @@ -5965,6 +6012,7 @@ "mirror", "nonskybattle" ] + // No secondary effect }, { "name": "laser_focus", @@ -5977,7 +6025,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "laser_focus" + } }, { "name": "last_resort", @@ -5992,7 +6043,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "last_resort" + } }, { "name": "lava_plume", @@ -6006,7 +6060,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "set_status", + "chance": 30, + "parameters": { + "status": "burned" + } + } }, { "name": "leaf_blade", @@ -6021,7 +6082,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "increased_critical_stage" + } }, { "name": "leaf_storm", @@ -6035,7 +6099,13 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_user_special_attack", + "parameters": { + "amount": -2 + } + } }, { "name": "leaf_tornado", @@ -6049,7 +6119,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_accuracy", + "chance": 50, + "parameters": { + "amount": -1 + } + } }, { "name": "leafage", @@ -6064,6 +6141,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "leech_life", @@ -6079,7 +6157,13 @@ "protect", "mirror", "heal" - ] + ], + "effect": { + "name": "drain", + "parameters": { + "drain_modifier": 0.5 + } + } }, { "name": "leech_seed", diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/IonDelugeEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/IonDelugeEffect.cs new file mode 100644 index 0000000..b6edf72 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/IonDelugeEffect.cs @@ -0,0 +1,17 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Battle; + +[Script(ScriptCategory.Move, "ion_deluge")] +public class IonDelugeEffect : Script +{ + /// + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + { + if (moveType.Name == "normal" && + target.Library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType)) + { + moveType = electricType; + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Ingrain.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Ingrain.cs new file mode 100644 index 0000000..528a011 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Ingrain.cs @@ -0,0 +1,13 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "ingrain")] +public class Ingrain : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + move.User.Volatile.Add(new IngrainEffect(move.User)); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Instruct.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Instruct.cs new file mode 100644 index 0000000..9761f0f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Instruct.cs @@ -0,0 +1,29 @@ +using System.Linq; +using PkmnLib.Dynamic.Models.BattleFlow; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "instruct")] +public class Instruct : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (!target.IsUsable) + return; + var battleData = move.User.BattleData; + if (battleData == null) + return; + + var lastMoveChoiceByTarget = battleData.Battle.PreviousTurnChoices.SelectMany(x => x) + .SkipWhile(x => x != move.MoveChoice).OfType().FirstOrDefault(x => x.User == target); + + if (lastMoveChoiceByTarget == null || !battleData.Battle.CanUse(lastMoveChoiceByTarget)) + { + move.GetHitData(target, hit).Fail(); + return; + } + + TurnRunner.ExecuteChoice(battleData.Battle, lastMoveChoiceByTarget); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IonDeluge.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IonDeluge.cs new file mode 100644 index 0000000..8d6f26b --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IonDeluge.cs @@ -0,0 +1,13 @@ +using PkmnLib.Plugin.Gen7.Scripts.Battle; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "ion_deluge")] +public class IonDeluge : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + move.User.BattleData?.Battle.Volatile.Add(new IonDelugeEffect()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Judgement.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Judgement.cs new file mode 100644 index 0000000..afdb8de --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Judgement.cs @@ -0,0 +1,38 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "judgement")] +public class Judgement : Script +{ + /// + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + { + var heldItem = move.User.HeldItem; + if (heldItem == null) + return; + var typeLibrary = target.Library.StaticLibrary.Types; + + moveType = heldItem.Name.ToString().ToLowerInvariant() switch + { + "dread_plate" when typeLibrary.TryGetTypeIdentifier("dark", out var dark) => dark, + "earth_plate" when typeLibrary.TryGetTypeIdentifier("ground", out var ground) => ground, + "fist_plate" when typeLibrary.TryGetTypeIdentifier("fighting", out var fighting) => fighting, + "flame_plate" when typeLibrary.TryGetTypeIdentifier("fire", out var fire) => fire, + "icicle_plate" when typeLibrary.TryGetTypeIdentifier("ice", out var ice) => ice, + "insect_plate" when typeLibrary.TryGetTypeIdentifier("bug", out var bug) => bug, + "iron_plate" when typeLibrary.TryGetTypeIdentifier("steel", out var steel) => steel, + "meadow_plate" when typeLibrary.TryGetTypeIdentifier("grass", out var grass) => grass, + "mind_plate" when typeLibrary.TryGetTypeIdentifier("psychic", out var psychic) => psychic, + "pixie_plate" when typeLibrary.TryGetTypeIdentifier("fairy", out var fairy) => fairy, + "sky_plate" when typeLibrary.TryGetTypeIdentifier("flying", out var flying) => flying, + "spooky_plate" when typeLibrary.TryGetTypeIdentifier("ghost", out var ghost) => ghost, + "stone_plate" when typeLibrary.TryGetTypeIdentifier("rock", out var rock) => rock, + "toxic_plate" when typeLibrary.TryGetTypeIdentifier("poison", out var poison) => poison, + "zap_plate" when typeLibrary.TryGetTypeIdentifier("electric", out var electric) => electric, + "draco_plate" when typeLibrary.TryGetTypeIdentifier("dragon", out var dragon) => dragon, + "splash_plate" when typeLibrary.TryGetTypeIdentifier("water", out var water) => water, + _ => moveType, + }; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KingsShield.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KingsShield.cs new file mode 100644 index 0000000..3e6a254 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KingsShield.cs @@ -0,0 +1,16 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "kings_shield")] +public class KingsShield : ProtectionScript +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + base.OnSecondaryEffect(move, target, hit); + // Default form is shield form + if (move.User.Species.Name == "aegislash" && move.User.Form.Name != "default") + { + move.User.ChangeForm(move.User.Species.GetDefaultForm()); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KnockOff.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KnockOff.cs new file mode 100644 index 0000000..a2f0a18 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KnockOff.cs @@ -0,0 +1,14 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "knock_off")] +public class KnockOff : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (target.RemoveHeldItemForBattle() is null) + { + move.GetHitData(target, hit).Fail(); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LaserFocus.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LaserFocus.cs new file mode 100644 index 0000000..2de3d10 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LaserFocus.cs @@ -0,0 +1,13 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "laser_focus")] +public class LaserFocus : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + target.Volatile.Add(new LaserFocusEffect()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LastResort.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LastResort.cs new file mode 100644 index 0000000..84da74d --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LastResort.cs @@ -0,0 +1,40 @@ +using System.Linq; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "last_resort")] +public class LastResort : Script +{ + /// + public override void PreventMoveSelection(IMoveChoice choice, ref bool prevent) + { + var battleData = choice.User.BattleData; + if (battleData == null) + { + prevent = true; + return; + } + var userMoves = choice.User.Moves.WhereNotNull().Where(x => x.MoveData.Name != "last_resort").ToList(); + if (userMoves.Count == 0) + { + prevent = true; + return; + } + + // Grab all move choices + var movesForUserSinceEnteringField = battleData.Battle.PreviousTurnChoices + // Reading backwards + .Reverse().SelectMany(x => x.Reverse()) + // We only care about move choices since the user entered the field + .TakeWhile(x => x is not SwitchChoice switchChoice || x.User != switchChoice.SwitchTo).OfType() + // We only care about the user's move choices + .Where(x => x.User == choice.User) + // Grab the chosen move, and remove duplicates + .Select(x => x.ChosenMove).Distinct().ToList(); + if (!userMoves.All(x => movesForUserSinceEnteringField.Contains(x))) + { + prevent = true; + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/IngrainEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/IngrainEffect.cs new file mode 100644 index 0000000..d2beda0 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/IngrainEffect.cs @@ -0,0 +1,56 @@ +using System.Linq; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "ingrain")] +public class IngrainEffect : Script +{ + private readonly IPokemon _owner; + + public IngrainEffect(IPokemon owner) + { + _owner = owner; + } + + /// + public override void PreventSelfSwitch(ISwitchChoice choice, ref bool prevent) => prevent = true; + + /// + public override void PreventSelfRunAway(IFleeChoice choice, ref bool prevent) => prevent = true; + + /// + public override void OnEndTurn(IBattle battle) + { + var heal = _owner.BoostedStats.Hp / 16; + _owner.Heal(heal); + } + + /// + public override void FailIncomingMove(IExecutingMove move, IPokemon target, ref bool fail) + { + if (move.UseMove.Name == "roar" || move.UseMove.Name == "whirlwind") + { + fail = true; + } + } + + /// + public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness) + { + var battleData = target.BattleData; + if (battleData == null) + return; + + if (move.UseMove.MoveType.Name != "ground") + return; + var targetTypes = target.Types; + var typeLibrary = battleData.Battle.Library.StaticLibrary.Types; + effectiveness = + // Get the effectiveness of the move against each target type + targetTypes.Select(x => typeLibrary.GetSingleEffectiveness(move.UseMove.MoveType, x)) + // Ignore all types that are immune to ground moves + .Where(x => x > 0) + // Multiply all effectiveness values together + .Aggregate(1.0f, (current, x) => current * x); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/KingsShield.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/KingsShield.cs new file mode 100644 index 0000000..52c33e6 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/KingsShield.cs @@ -0,0 +1,18 @@ +using PkmnLib.Static; +using PkmnLib.Static.Moves; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "kings_shield")] +public class KingsShield : ProtectionEffectScript +{ + /// + public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) + { + base.BlockIncomingHit(executingMove, target, hitIndex, ref block); + if (executingMove.UseMove.Category != MoveCategory.Status && executingMove.UseMove.HasFlag("contact")) + { + executingMove.User.ChangeStatBoost(Statistic.Accuracy, -2, false); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LaserFocusEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LaserFocusEffect.cs new file mode 100644 index 0000000..6e69b85 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/LaserFocusEffect.cs @@ -0,0 +1,12 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "laser_focus")] +public class LaserFocusEffect : Script +{ + /// + public override void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage) + { + stage = 100; + RemoveSelf(); + } +} \ No newline at end of file