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