diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs
index 60d9186..43e65d4 100644
--- a/PkmnLib.Dynamic/Models/Pokemon.cs
+++ b/PkmnLib.Dynamic/Models/Pokemon.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Libraries;
@@ -251,6 +252,12 @@ public interface IPokemon : IScriptSource, IDeepCloneable
///
IItem? RemoveHeldItemForBattle();
+ ///
+ /// Tries to steal the held item of the Pokémon. If successful, the item is removed from the Pokémon and returned.
+ /// If the Pokémon does not have a held item, or the item is a form changer, this will return false.
+ ///
+ bool TryStealHeldItem([NotNullWhen(true)] out IItem? item);
+
///
/// Restores the held item of a Pokémon if it was temporarily removed.
///
@@ -812,6 +819,25 @@ public class PokemonImpl : ScriptSource, IPokemon
return _stolenHeldItem = RemoveHeldItem();
}
+ ///
+ public bool TryStealHeldItem([NotNullWhen(true)] out IItem? item)
+ {
+ if (HeldItem is null || HeldItem.Category == ItemCategory.FormChanger)
+ {
+ item = null;
+ return false;
+ }
+ var prevent = false;
+ this.RunScriptHook(script => script.PreventHeldItemSteal(this, HeldItem, ref prevent));
+ if (prevent)
+ {
+ item = null;
+ return false;
+ }
+ item = RemoveHeldItemForBattle();
+ return item is not null;
+ }
+
///
public void RestoreStolenHeldItem()
{
diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs
index 5a2b044..be41b53 100644
--- a/PkmnLib.Dynamic/ScriptHandling/Script.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs
@@ -812,6 +812,13 @@ public abstract class Script : IDeepCloneable
{
}
+ ///
+ /// This function allows a script to prevent a held item from being stolen by an effect such as Thief or Covet.
+ ///
+ public virtual void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent)
+ {
+ }
+
///
/// This function allows a script to run after a held item has changed.
///
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc b/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc
index 0a06621..415b074 100755
--- a/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc
+++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc
@@ -594,18 +594,37 @@
"speed_boost": {
"effect": "speed_boost"
},
- "stakeout": {},
- "stall": {},
- "stamina": {},
+ "stakeout": {
+ "effect": "stakeout"
+ },
+ "stall": {
+ "effect": "stall"
+ },
+ "stamina": {
+ "effect": "stamina"
+ },
"stance_change": {
+ "effect": "stance_change",
"canBeChanged": false
},
- "static": {},
- "steadfast": {},
- "steelworker": {},
- "stench": {},
- "sticky_hold": {},
- "storm_drain": {},
+ "static": {
+ "effect": "static"
+ },
+ "steadfast": {
+ "effect": "steadfast"
+ },
+ "steelworker": {
+ "effect": "steelworker"
+ },
+ "stench": {
+ "effect": "stench"
+ },
+ "sticky_hold": {
+ "effect": "sticky_hold"
+ },
+ "storm_drain": {
+ "effect": "storm_drain"
+ },
"strong_jaw": {},
"sturdy": {},
"suction_cups": {},
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Pokemon.json b/Plugins/PkmnLib.Plugin.Gen7/Data/Pokemon.json
index bb081fe..3e0803f 100755
--- a/Plugins/PkmnLib.Plugin.Gen7/Data/Pokemon.json
+++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Pokemon.json
@@ -946,6 +946,7 @@
"flags": [],
"formes": {
"blade": {
+ "isBattleOnly": true,
"abilities": [
"stance_change"
],
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Magician.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Magician.cs
index 47956f3..4839fc3 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Magician.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Magician.cs
@@ -19,7 +19,10 @@ public class Magician : Script
if (move.User.HeldItem is not null || target.HeldItem is null)
return;
+ if (!move.User.TryStealHeldItem(out var item))
+ return;
+
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
- _ = move.User.SetHeldItem(target.RemoveHeldItemForBattle());
+ _ = move.User.SetHeldItem(item);
}
}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Pickpocket.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Pickpocket.cs
index c33b852..9d3e58a 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Pickpocket.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Pickpocket.cs
@@ -11,10 +11,10 @@ public class Pickpocket : Script
///
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
- if (move.GetHitData(target, hit).IsContact && target.HeldItem is null && move.User.HeldItem is not null)
- {
- move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
- _ = target.SetHeldItem(move.User.RemoveHeldItemForBattle());
- }
+ if (!move.GetHitData(target, hit).IsContact || target.HeldItem is not null ||
+ !move.User.TryStealHeldItem(out var item))
+ return;
+ move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target));
+ _ = target.SetHeldItem(item);
}
}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stakeout.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stakeout.cs
new file mode 100644
index 0000000..0ba9295
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stakeout.cs
@@ -0,0 +1,17 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Stakeout is an ability that doubles the damage dealt to Pokémon that have switched in this turn.
+///
+/// Bulbapedia - Stakeout
+///
+[Script(ScriptCategory.Ability, "stakeout")]
+public class Stakeout : Script
+{
+ ///
+ public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
+ {
+ if (target.BattleData?.SwitchInTurn == move.Battle.CurrentTurnNumber)
+ basePower = basePower.MultiplyOrMax(2);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stall.cs
new file mode 100644
index 0000000..c6bda3f
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stall.cs
@@ -0,0 +1,16 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Stall is an ability that makes the Pokémon move last in its priority bracket.
+///
+/// Bulbapedia - Stall
+///
+[Script(ScriptCategory.Ability, "stall")]
+public class Stall : Script
+{
+ ///
+ public override void ChangeSpeed(ITurnChoice choice, ref uint speed)
+ {
+ speed = 0;
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stamina.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stamina.cs
new file mode 100644
index 0000000..6db07e2
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stamina.cs
@@ -0,0 +1,23 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Stamina is an ability that raises the user's Defense by one stage when hit by an attack.
+///
+/// Bulbapedia - Stamina
+///
+[Script(ScriptCategory.Ability, "stamina")]
+public class Stamina : Script
+{
+ ///
+ public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
+ {
+ EventBatchId batchId = new();
+ if (target.ChangeStatBoost(Statistic.Defense, 1, true, false, batchId))
+ {
+ move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)
+ {
+ BatchId = batchId,
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StanceChange.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StanceChange.cs
new file mode 100644
index 0000000..bd4bf8f
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StanceChange.cs
@@ -0,0 +1,30 @@
+using PkmnLib.Static.Moves;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Stance Change is an ability that changes Aegislash's form depending on the move used.
+///
+/// Bulbapedia - Stance Change
+///
+[Script(ScriptCategory.Ability, "stance_change")]
+public class StanceChange : Script
+{
+ ///
+ public override void OnBeforeMove(IExecutingMove move)
+ {
+ if (move.User.Species.Name != "aegislash")
+ return;
+
+ if (move.UseMove.Category is not (MoveCategory.Physical or MoveCategory.Special) ||
+ move.User.Form.Name == "blade" || !move.User.Species.TryGetForm("blade", out var bladeForm))
+ return;
+ EventBatchId batchId = new();
+ move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)
+ {
+ BatchId = batchId,
+ });
+ move.User.ChangeForm(bladeForm, batchId);
+ // Kings shield is handled in the move script.
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Static.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Static.cs
new file mode 100644
index 0000000..f9b6588
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Static.cs
@@ -0,0 +1,31 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Static is an ability that may paralyze attackers using contact moves.
+///
+/// Bulbapedia - Static
+///
+[Script(ScriptCategory.Ability, "static")]
+public class Static : Script
+{
+ private const int ChanceToParalyze = 30;
+
+ ///
+ public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
+ {
+ if (!move.GetHitData(target, hit).IsContact)
+ return;
+
+ if (move.Battle.Random.GetInt(0, 100) < ChanceToParalyze)
+ {
+ EventBatchId batchId = new();
+ if (target.SetStatus(ScriptUtils.ResolveName(), false, batchId))
+ {
+ move.Battle.EventHook.Invoke(new AbilityTriggerEvent(target)
+ {
+ BatchId = batchId,
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Steadfast.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Steadfast.cs
new file mode 100644
index 0000000..e25a4eb
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Steadfast.cs
@@ -0,0 +1,26 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Steadfast is an ability that raises the user's Speed each time it flinches.
+///
+/// Bulbapedia - Steadfast
+///
+[Script(ScriptCategory.Ability, "steadfast")]
+public class Steadfast : Script
+{
+ ///
+ public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args)
+ {
+ if (eventName != CustomTriggers.OnFlinch)
+ return;
+ if (args is not CustomTriggers.OnFlinchArgs flinchArgs)
+ return;
+
+ EventBatchId batchId = new();
+ flinchArgs.Move.Battle.EventHook.Invoke(new AbilityTriggerEvent(flinchArgs.Move.User)
+ {
+ BatchId = batchId,
+ });
+ flinchArgs.Move.User.ChangeStatBoost(Statistic.Speed, 1, true, false, batchId);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Steelworker.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Steelworker.cs
new file mode 100644
index 0000000..a175a9f
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Steelworker.cs
@@ -0,0 +1,20 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Steelworker is an ability that boosts the power of Steel-type moves.
+///
+/// Bulbapedia - Steelworker
+///
+[Script(ScriptCategory.Ability, "steelworker")]
+public class Steelworker : Script
+{
+ ///
+ public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
+ ImmutableStatisticSet targetStats, Statistic stat, ref uint value)
+ {
+ if (move.GetHitData(target, hit).Type?.Name == "steel")
+ {
+ value = value.MultiplyOrMax(1.5f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stench.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stench.cs
new file mode 100644
index 0000000..81d6c1b
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Stench.cs
@@ -0,0 +1,21 @@
+using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Stench is an ability that may cause the target to flinch when hit by a damaging move.
+///
+/// Bulbapedia - Stench
+///
+[Script(ScriptCategory.Ability, "stench")]
+public class Stench : Script
+{
+ ///
+ public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
+ {
+ if (move.Battle.Random.GetInt(100) >= 10)
+ return;
+ move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
+ target.Volatile.Add(new FlinchEffect());
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StickyHold.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StickyHold.cs
new file mode 100644
index 0000000..e74c223
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StickyHold.cs
@@ -0,0 +1,16 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Sticky Hold is an ability that prevents the Pokémon's held item from being taken.
+///
+/// Bulbapedia - Sticky Hold
+///
+[Script(ScriptCategory.Ability, "sticky_hold")]
+public class StickyHold : Script
+{
+ ///
+ public override void PreventHeldItemSteal(IPokemon pokemon, IItem heldItem, ref bool prevent)
+ {
+ prevent = true;
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StormDrain.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StormDrain.cs
new file mode 100644
index 0000000..70bff84
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/StormDrain.cs
@@ -0,0 +1,34 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
+
+///
+/// Storm Drain is an ability that draws in all Water-type moves to up its Special Attack.
+///
+/// Bulbapedia - Storm Drain
+///
+[Script(ScriptCategory.Ability, "storm_drain")]
+public class StormDrain : Script
+{
+ ///
+ public override void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList targets)
+ {
+ if (moveChoice.ChosenMove.MoveData.MoveType.Name == "water" && targets.Count == 1)
+ {
+ targets = [moveChoice.User];
+ }
+ }
+
+ ///
+ public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
+ {
+ if (move.GetHitData(target, hit).Type?.Name != "water")
+ return;
+
+ effectiveness = 0f;
+ EventBatchId batchId = new();
+ move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)
+ {
+ BatchId = batchId,
+ });
+ move.User.ChangeStatBoost(Statistic.SpecialAttack, 1, true, true, batchId);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs
index 504964a..ac2b398 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs
@@ -132,4 +132,11 @@ public static class CustomTriggers
{
public byte NumberOfHits { get; set; } = NumberOfHits;
}
+
+ public static readonly StringKey OnFlinch = "on_flinch";
+
+ public record OnFlinchArgs(IExecutingMove Move) : ICustomTriggerArgs
+ {
+ public bool Prevent { get; set; } = false;
+ }
}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BugBite.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BugBite.cs
index 86aeb66..e46528f 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BugBite.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BugBite.cs
@@ -13,11 +13,12 @@ public class BugBite : Script
var targetHeldItem = target.HeldItem;
- if (targetHeldItem is not { Category: ItemCategory.Berry })
+ if (targetHeldItem is not { Category: ItemCategory.Berry } || !target.TryStealHeldItem(out targetHeldItem))
{
move.GetHitData(target, hit).Fail();
return;
}
+
_ = target.SetHeldItem(null);
targetHeldItem.RunItemScript(battleData.Battle.Library.ScriptResolver, user);
}
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Covet.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Covet.cs
index 1e501fb..0cc61c2 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Covet.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Covet.cs
@@ -8,8 +8,8 @@ public class Covet : Script
{
if (target.HeldItem == null)
return;
- if (move.User.HeldItem != null)
+ if (!move.User.TryStealHeldItem(out var item))
return;
- _ = move.User.SetHeldItem(target.RemoveHeldItemForBattle());
+ _ = move.User.SetHeldItem(item);
}
}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Incinerate.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Incinerate.cs
index 937f037..c3ee7c1 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Incinerate.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Incinerate.cs
@@ -6,9 +6,11 @@ public class Incinerate : Script
///
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
- if (target.HeldItem is { Category: ItemCategory.Berry })
+ if (target.HeldItem is not { Category: ItemCategory.Berry } || !target.TryStealHeldItem(out _))
{
- target.RemoveHeldItemForBattle();
+ move.GetHitData(target, hit).Fail();
+ return;
}
+ // TODO: Add message for item incineration
}
}
\ 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
index a2f0a18..01e52c7 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KnockOff.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/KnockOff.cs
@@ -6,7 +6,7 @@ public class KnockOff : Script
///
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
- if (target.RemoveHeldItemForBattle() is null)
+ if (!target.TryStealHeldItem(out var item))
{
move.GetHitData(target, hit).Fail();
}
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/FlinchEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/FlinchEffect.cs
index e01e157..2e4f57c 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/FlinchEffect.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/FlinchEffect.cs
@@ -7,6 +7,11 @@ public class FlinchEffect : Script
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
prevent = true;
+ var args = new CustomTriggers.OnFlinchArgs(move);
+ move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.OnFlinch, args));
+ if (args.Prevent)
+ return;
+
RemoveSelf();
}
}
\ No newline at end of file