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