diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 5fb8cbb..ed95315 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -1023,6 +1023,8 @@ public class PokemonImpl : ScriptSource, IPokemon this.RunScriptHook(script => script.ChangeIncomingDamage(this, source, ref dmg)); damage = dmg; } + if (damage == 0) + return; // If the damage is more than the current health, we cap it at the current health, to prevent // underflow. diff --git a/PkmnLib.Dynamic/ScriptHandling/ICustomTriggerArgs.cs b/PkmnLib.Dynamic/ScriptHandling/ICustomTriggerArgs.cs new file mode 100644 index 0000000..3b55ec5 --- /dev/null +++ b/PkmnLib.Dynamic/ScriptHandling/ICustomTriggerArgs.cs @@ -0,0 +1,126 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Dynamic.ScriptHandling; + +/// +/// Interface for custom trigger arguments. +/// +public interface ICustomTriggerArgs; + +/// +/// Static helper methods for working with . +/// +/// This class provides methods to get and set properties on custom trigger arguments. This allows for plugins and scripts +/// to modify the behavior of custom triggers without needing to know the specific types of the arguments, which means +/// they do not need to reference each other directly. +/// +public static class CustomTriggerArgsHelpers +{ + private record TypeFuncs( + Dictionary> Getters, + Dictionary> Setters); + + private static readonly Dictionary TypeFuncInfo = new(); + + private static void PopulateForType(Type type) + { + if (TypeFuncInfo.ContainsKey(type)) + return; + + var getters = type.GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0).ToDictionary( + p => new StringKey(p.Name), p => + { + var parameter = Expression.Parameter(typeof(ICustomTriggerArgs), "args"); + var typeCast = Expression.Convert(parameter, type); + var propertyAccess = Expression.Property(typeCast, p); + var convert = Expression.Convert(propertyAccess, typeof(object)); + return Expression.Lambda>(convert, parameter).Compile(); + }); + var setters = type.GetProperties().Where(p => p.CanWrite && p.GetIndexParameters().Length == 0).ToDictionary( + p => new StringKey(p.Name), p => + { + var parameter = Expression.Parameter(typeof(ICustomTriggerArgs), "args"); + var typeCast = Expression.Convert(parameter, type); + var valueParameter = Expression.Parameter(typeof(object), "value"); + var propertyAccess = Expression.Property(typeCast, p); + var convert = Expression.Convert(valueParameter, p.PropertyType); + var assign = Expression.Assign(propertyAccess, convert); + return Expression.Lambda>(assign, parameter, valueParameter) + .Compile(); + }); + + TypeFuncInfo[type] = new TypeFuncs(getters, setters); + } + + private static bool TryGetGetter(Type type, StringKey key, + [NotNullWhen(true)] out Func? getter) + { + if (!TypeFuncInfo.ContainsKey(type)) + PopulateForType(type); + return TypeFuncInfo[type].Getters.TryGetValue(key, out getter); + } + + private static bool TryGetSetter(Type type, StringKey key, + [NotNullWhen(true)] out Action? setter) + { + if (!TypeFuncInfo.ContainsKey(type)) + PopulateForType(type); + return TypeFuncInfo[type].Setters.TryGetValue(key, out setter); + } + + public static T Get(this ICustomTriggerArgs args, StringKey key) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var type = args.GetType(); + if (!TryGetGetter(type, key, out var getter)) + throw new KeyNotFoundException($"Key '{key}' not found in {type.Name}."); + var value = getter(args); + if (value is T typedValue) + return typedValue; + throw new InvalidCastException( + $"Value for key '{key}' in {type.Name} is of type {value?.GetType().Name}, expected {typeof(T).Name}."); + } + + public static object? Get(this ICustomTriggerArgs args, StringKey key) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var type = args.GetType(); + if (!TryGetGetter(type, key, out var getter)) + throw new KeyNotFoundException($"Key '{key}' not found in {type.Name}."); + return getter(args); + } + + public static bool TryGet(this ICustomTriggerArgs args, StringKey key, [NotNullWhen(true)] out T? value) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var type = args.GetType(); + if (!TryGetGetter(type, key, out var getter)) + { + value = default; + return false; + } + var result = getter(args); + if (result is T typedValue) + { + value = typedValue; + return true; + } + value = default; + return false; + } + + public static void Set(this ICustomTriggerArgs args, StringKey key, object? value) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var type = args.GetType(); + if (!TryGetSetter(type, key, out var setter)) + throw new KeyNotFoundException($"Key '{key}' not found in {type.Name}."); + setter(args, value); + } +} \ No newline at end of file diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index b1eaf40..ca026d8 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -356,6 +356,23 @@ public abstract class Script : IDeepCloneable { } + /// + /// This function allows a script to change the offensive stat value of an incoming move. + /// + public virtual void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IPokemon target, + byte hitNumber, uint defensiveStat, StatisticSet targetStats, Statistic offensive, ref uint offensiveStat) + { + } + + /// + /// This function allows a script to change the defensive stat value of an incoming move. + /// + public virtual void ChangeIncomingMoveDefensiveStatValue(IExecutingMove executingMove, IPokemon target, + byte hitNumber, uint origOffensiveStat, StatisticSet targetStats, Statistic defensive, + ref uint defensiveStat) + { + } + /// /// This function allows a script to change the raw modifier we retrieved from the stats of the /// defender and attacker. The default value is the offensive stat divided by the defensive stat. @@ -633,10 +650,10 @@ public abstract class Script : IDeepCloneable /// The name of the event that is triggered. This should be unique for each different event. Overriding scripts /// should validate the event name is one they should handle. /// - /// - /// The parameters that are passed to the event. This can be null if no parameters are passed. + /// + /// The parameters that are passed to the event. /// - public virtual void CustomTrigger(StringKey eventName, IDictionary? parameters) + public virtual void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { } diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Abilities/MegaLauncherTests.cs b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Abilities/MegaLauncherTests.cs new file mode 100644 index 0000000..3a5d326 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Abilities/MegaLauncherTests.cs @@ -0,0 +1,42 @@ +using PkmnLib.Dynamic.Models; +using PkmnLib.Dynamic.ScriptHandling; +using PkmnLib.Plugin.Gen7.Scripts.Abilities; +using PkmnLib.Plugin.Gen7.Scripts.Moves; +using PkmnLib.Static; +using PkmnLib.Static.Moves; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Abilities; + +public class MegaLauncherTests +{ + [Test] + public async Task ChangeHealPercent_HealPercentIncreasesForAuraMoves() + { + // Arrange + var move = Substitute.For(); + var target = Substitute.For(); + var healPercent = 0.5f; + var user = Substitute.For(); + move.User.Returns(user); + move.UseMove.Category.Returns(MoveCategory.Special); + move.UseMove.HasFlag("pulse").Returns(true); + var megaLauncher = new ScriptContainer(new MegaLauncher()); + move.User.AbilityScript.Returns(megaLauncher); + move.GetScripts().Returns(new ScriptIterator([megaLauncher])); + var healPercentScript = new HealPercent(); + uint healedAmount = 0; + target.Heal(Arg.Do(amount => healedAmount = amount)); + target.BoostedStats.Returns(new StatisticSet(100, 100, 100, 100, 100, 100)); + + // Act + healPercentScript.OnInitialize(new Dictionary + { + { "healPercent", healPercent }, + }); + healPercentScript.OnSecondaryEffect(move, target, 0); + + // Assert + await Assert.That(healedAmount).IsEqualTo((uint)(target.BoostedStats.Hp * healPercent * 1.5f)); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc b/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc index 858099f..3beb10c 100755 --- a/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc +++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Abilities.jsonc @@ -342,15 +342,33 @@ "magic_bounce": { "effect": "magic_bounce" }, - "magic_guard": {}, - "magician": {}, - "magma_armor": {}, - "magnet_pull": {}, - "marvel_scale": {}, - "mega_launcher": {}, - "merciless": {}, - "minus": {}, - "misty_surge": {}, + "magic_guard": { + "effect": "magic_guard" + }, + "magician": { + "effect": "magician" + }, + "magma_armor": { + "effect": "magma_armor" + }, + "magnet_pull": { + "effect": "magnet_pull" + }, + "marvel_scale": { + "effect": "marvel_scale" + }, + "mega_launcher": { + "effect": "mega_launcher" + }, + "merciless": { + "effect": "merciless" + }, + "minus": { + "effect": "minus" + }, + "misty_surge": { + "effect": "misty_surge" + }, "mold_breaker": {}, "moody": {}, "motor_drive": {}, diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7DamageCalculator.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7DamageCalculator.cs index 57508e0..5b2f5d4 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7DamageCalculator.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Battling/Gen7DamageCalculator.cs @@ -136,6 +136,10 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama defensiveStat, targetStats, offensive, ref offensiveStat)); executingMove.RunScriptHook(script => script.ChangeDefensiveStatValue(executingMove, target, hitNumber, origOffensiveStat, targetStats, defensive, ref defensiveStat)); + target.RunScriptHook(script => script.ChangeIncomingMoveOffensiveStatValue(executingMove, target, hitNumber, + defensiveStat, targetStats, offensive, ref offensiveStat)); + target.RunScriptHook(script => script.ChangeIncomingMoveDefensiveStatValue(executingMove, target, hitNumber, + origOffensiveStat, targetStats, defensive, ref defensiveStat)); var modifier = (float)offensiveStat / defensiveStat; executingMove.RunScriptHook(script => diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/AuraBreak.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/AuraBreak.cs index f19b45c..967da63 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/AuraBreak.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/AuraBreak.cs @@ -10,21 +10,16 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; public class AuraBreak : Script { /// - public override void CustomTrigger(StringKey eventName, IDictionary? parameters) + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { - if (eventName != CustomTriggers.ModifyAuraEffect || parameters == null) + if (eventName != CustomTriggers.ModifyAuraEffect || args is not CustomTriggers.ModifyAuraEffectArgs auraArgs) return; - if (!parameters.TryGetValue("aura_type", out var auraTypeObj) || auraTypeObj is not string auraType) - return; - - if (auraType is "dark" or "fairy") + var typeName = auraArgs.Move.UseMove.MoveType.Name; + if (typeName == "dark" || typeName == "fairy") { - if (parameters.TryGetValue("modifier", out var modifierObj) && modifierObj is float) - { - // Reverse the aura effect by reducing power by 25% - parameters["modifier"] = 0.75f; - } + // Reverse the aura effect by reducing power by 25% + auraArgs.AuraEffect *= 0.75f; } } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Comatose.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Comatose.cs index ad41dac..92dd45d 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Comatose.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Comatose.cs @@ -21,11 +21,11 @@ public class Comatose : Script } /// - public override void CustomTrigger(StringKey eventName, IDictionary? parameters) + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { - if (eventName == CustomTriggers.BypassSleep && parameters != null) + if (eventName == CustomTriggers.BypassSleep && args is CustomTriggers.BypassSleepArgs bypassArgs) { - parameters["bypass_sleep"] = true; + bypassArgs.Bypass = true; } } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DarkAura.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DarkAura.cs index 2cd6d9f..82cefb3 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DarkAura.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/DarkAura.cs @@ -15,17 +15,10 @@ public class DarkAura : Script if (move.GetHitData(target, hit).Type?.Name == "dark") { var auraModifier = 5448f / 4096f; - var parameters = new Dictionary - { - ["aura_type"] = "dark", - ["modifier"] = auraModifier, - }; + var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier); move.Battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull() - .RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, parameters)); - if (parameters.TryGetValue("modifier", out var modObj) && modObj is float modValue) - { - auraModifier = modValue; - } + .RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, args)); + auraModifier = args.AuraEffect; modifier *= auraModifier; move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)); } diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/EarlyBird.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/EarlyBird.cs index c8be63f..4a7bab6 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/EarlyBird.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/EarlyBird.cs @@ -11,15 +11,11 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; public class EarlyBird : Script { /// - public override void CustomTrigger(StringKey eventName, IDictionary? parameters) + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { if (eventName != CustomTriggers.ModifySleepTurns) return; - if (parameters == null) - return; - if (parameters.TryGetValue("turns", out var turnsObj) && turnsObj is int turns) - { - parameters["turns"] = turns / 2; - } + if (args is CustomTriggers.ModifySleepTurnsArgs sleepArgs) + sleepArgs.Turns /= 2; } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FairyAura.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FairyAura.cs index fb08238..d1fb2f0 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FairyAura.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/FairyAura.cs @@ -16,17 +16,10 @@ public class FairyAura : Script if (move.GetHitData(target, hit).Type?.Name == "fairy") { var auraModifier = 5448f / 4096f; - var parameters = new Dictionary - { - ["aura_type"] = "fairy", - ["modifier"] = auraModifier, - }; + var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier); move.Battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull() - .RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, parameters)); - if (parameters.TryGetValue("modifier", out var modObj) && modObj is float modValue) - { - auraModifier = modValue; - } + .RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, args)); + auraModifier = args.AuraEffect; modifier *= auraModifier; move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)); } diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/GrassPelt.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/GrassPelt.cs index 1e3ecd0..2d72de5 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/GrassPelt.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/GrassPelt.cs @@ -10,8 +10,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; public class GrassPelt : Script { /// - public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, - ImmutableStatisticSet targetStats, Statistic stat, ref uint value) + public override void ChangeIncomingMoveDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, + uint offensiveStat, StatisticSet statisticSet, Statistic stat, ref uint value) { if (move.Battle.TerrainName == ScriptUtils.ResolveName() && stat == Statistic.Defense) value = value.MultiplyOrMax(1.5f); diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/IceBody.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/IceBody.cs index 67fbc60..235e67f 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/IceBody.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/IceBody.cs @@ -41,11 +41,10 @@ public class IceBody : Script } /// - public override void CustomTrigger(StringKey eventName, IDictionary? parameters) + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { - if (eventName != CustomTriggers.IgnoreHail || parameters is null) + if (eventName != CustomTriggers.IgnoreHail || args is not CustomTriggers.IgnoreHailArgs ignoreHailArgs) return; - - parameters["ignoresHail"] = true; + ignoreHailArgs.Ignore = true; } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Infiltrator.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Infiltrator.cs index 9069bab..979bb6b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Infiltrator.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Infiltrator.cs @@ -9,12 +9,15 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; public class Infiltrator : Script { /// - public override void CustomTrigger(StringKey eventName, IDictionary? parameters) + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { - if ((eventName != CustomTriggers.BypassProtection && eventName != CustomTriggers.BypassSubstitute) || - parameters is null) - return; - - parameters["bypass"] = true; + if (eventName == CustomTriggers.BypassSubstitute && args is CustomTriggers.BypassSubstituteArgs bypassArgs) + { + bypassArgs.Bypass = true; + } + else if (eventName == CustomTriggers.BypassProtection && args is CustomTriggers.BypassProtectionArgs customArgs) + { + customArgs.Bypass = true; + } } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/LiquidOoze.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/LiquidOoze.cs index 1902f59..47e9f29 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/LiquidOoze.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/LiquidOoze.cs @@ -9,11 +9,10 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; public class LiquidOoze : Script { /// - public override void CustomTrigger(StringKey eventName, IDictionary? parameters) + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { - if (eventName != CustomTriggers.ModifyDrain || parameters is null) + if (eventName != CustomTriggers.ModifyDrain || args is not CustomTriggers.ModifyDrainArgs parameters) return; - - parameters["invert"] = true; + parameters.Invert = true; } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagicGuard.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagicGuard.cs new file mode 100644 index 0000000..5a150e8 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagicGuard.cs @@ -0,0 +1,25 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Magic Guard is an ability that prevents all damage except from direct attacks. +/// +/// Bulbapedia - Magic Guard +/// +[Script(ScriptCategory.Ability, "magic_guard")] +public class MagicGuard : Script +{ + /// + public override void ChangeIncomingDamage(IPokemon pokemon, DamageSource source, ref uint damage) + { + // Magic Guard doesn't work if the Pokémon is not in battle. + if (pokemon.BattleData is null) + return; + + if (source is DamageSource.MoveDamage or DamageSource.Struggle or DamageSource.FormChange) + { + // If the damage is from a move, struggle, or form change, we don't prevent it. + return; + } + damage = 0; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Magician.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Magician.cs new file mode 100644 index 0000000..47956f3 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Magician.cs @@ -0,0 +1,25 @@ +using PkmnLib.Static.Moves; + +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Magician is an ability that steals the held item of a Pokémon it hits with a move. +/// +/// Bulbapedia - Magician +/// +[Script(ScriptCategory.Ability, "magician")] +public class Magician : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (move.UseMove.Category is MoveCategory.Status) + return; + + if (move.User.HeldItem is not null || target.HeldItem is null) + return; + + move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)); + _ = move.User.SetHeldItem(target.RemoveHeldItemForBattle()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagmaArmor.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagmaArmor.cs new file mode 100644 index 0000000..949865a --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagmaArmor.cs @@ -0,0 +1,31 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Magma Armor is an ability that prevents the Pokémon from being frozen. +/// +/// Bulbapedia - Magma Armor +/// +[Script(ScriptCategory.Ability, "magma_armor")] +public class MagmaArmor : Script +{ + /// + public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted, + ref bool preventStatus) + { + if (status == ScriptUtils.ResolveName()) + { + pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)); + preventStatus = true; + } + } + + /// + public override void OnSwitchIn(IPokemon pokemon, byte position) + { + if (pokemon.HasStatus(ScriptUtils.ResolveName())) + { + pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon)); + pokemon.ClearStatus(); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagnetPull.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagnetPull.cs new file mode 100644 index 0000000..38c31c4 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MagnetPull.cs @@ -0,0 +1,24 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Magnet Pull is an ability that prevents Steel-type Pokémon from fleeing or switching out. +/// +/// Bulbapedia - Magnet Pull +/// +[Script(ScriptCategory.Ability, "magnet_pull")] +public class MagnetPull : Script +{ + /// + public override void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent) + { + if (choice.User.Types.Any(x => x.Name == "steel")) + prevent = true; + } + + /// + public override void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent) + { + if (choice.User.Types.Any(x => x.Name == "steel")) + prevent = true; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MarvelScale.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MarvelScale.cs new file mode 100644 index 0000000..5c0f9de --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MarvelScale.cs @@ -0,0 +1,20 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Marvel Scale is an ability that increases the Pokémon's Defense by 50% when it has a status condition. +/// +/// Bulbapedia - Marvel Scale +/// +[Script(ScriptCategory.Ability, "marvel_scale")] +public class MarvelScale : Script +{ + /// + public override void ChangeIncomingMoveDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, + uint offensiveStat, StatisticSet statisticSet, Statistic stat, ref uint value) + { + if (!target.StatusScript.IsEmpty && stat == Statistic.Defense) + { + value = value.MultiplyOrMax(1.5f); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MegaLauncher.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MegaLauncher.cs new file mode 100644 index 0000000..c28879d --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MegaLauncher.cs @@ -0,0 +1,29 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Mega Launcher is an ability that boosts the power of aura and pulse moves. +/// +/// Bulbapedia - Mega Launcher +/// +[Script(ScriptCategory.Ability, "mega_launcher")] +public class MegaLauncher : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower) + { + if (move.UseMove.HasFlag("pulse")) + { + basePower = basePower.MultiplyOrMax(1.5f); + } + } + + /// + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) + { + if (eventName != CustomTriggers.ModifyHealPercent || args is not CustomTriggers.ModifyHealPercentArgs healArgs) + return; + + if (healArgs.Move.UseMove.HasFlag("pulse")) + healArgs.HealPercent *= 1.5f; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Merciless.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Merciless.cs new file mode 100644 index 0000000..7dc6f50 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Merciless.cs @@ -0,0 +1,22 @@ +using PkmnLib.Plugin.Gen7.Scripts.Status; + +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Merciless is an ability that always results in critical hits against poisoned targets. +/// +/// Bulbapedia - Merciless +/// +[Script(ScriptCategory.Ability, "merciless")] +public class Merciless : Script +{ + /// + public override void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage) + { + if (target.StatusScript.Script?.Name == ScriptUtils.ResolveName() || + target.StatusScript.Script?.Name == ScriptUtils.ResolveName()) + { + stage = 100; + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Minus.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Minus.cs new file mode 100644 index 0000000..5d42f24 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/Minus.cs @@ -0,0 +1,23 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Minus is an ability that boosts Special Attack if another ally has Plus or Minus. +/// +/// Bulbapedia - Minus +/// +[Script(ScriptCategory.Ability, "minus")] +public class Minus : Script +{ + /// + public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, + ImmutableStatisticSet targetStats, Statistic stat, ref uint value) + { + var battleData = move.User.BattleData; + if (battleData is null) + return; + if (battleData.BattleSide.Pokemon.WhereNotNull().Any(x => x.IsUsable && x.ActiveAbility?.Name == "plus")) + { + value = value.MultiplyOrMax(1.5f); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MistySurge.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MistySurge.cs new file mode 100644 index 0000000..8fab36f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Abilities/MistySurge.cs @@ -0,0 +1,28 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Abilities; + +/// +/// Misty Surge is an ability that creates Misty Terrain when the Pokémon enters battle. +/// +/// Bulbapedia - Misty Surge +/// +[Script(ScriptCategory.Ability, "misty_surge")] +public class MistySurge : Script +{ + /// + public override void OnSwitchIn(IPokemon pokemon, byte position) + { + if (pokemon.BattleData?.Battle is null) + return; + + var terrainName = ScriptUtils.ResolveName(); + if (pokemon.BattleData.Battle.TerrainName == terrainName) + return; + + EventBatchId batchId = new(); + pokemon.BattleData.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon) + { + BatchId = batchId, + }); + pokemon.BattleData.Battle.SetTerrain(terrainName, 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 be64c6c..477f58d 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/CustomTriggers.cs @@ -4,29 +4,108 @@ public static class CustomTriggers { public static readonly StringKey AuroraVeilDuration = "aurora_veil_duration"; - public static readonly StringKey BindNumberOfTurns = "bind_number_of_turns"; + public record AuroraVeilDurationArgs(IExecutingMove Move, int Duration) : ICustomTriggerArgs + { + public int Duration { get; set; } = Duration; + } - public static readonly StringKey BindPercentOfMaxHealth = "bind_percent_of_max_health"; + public static readonly StringKey ModifyBind = "modify_bind"; + + public record ModifyBindArgs(IExecutingMove Move) : ICustomTriggerArgs + { + public int Duration { get; set; } = 5; + public float DamagePercent { get; set; } = 1f / 8f; + } public static readonly StringKey IgnoreHail = "ignores_hail"; + public record IgnoreHailArgs(IPokemon Pokemon) : ICustomTriggerArgs + { + public bool Ignore { get; set; } = false; + } + public static readonly StringKey LightScreenNumberOfTurns = "light_screen_number_of_turns"; + public record LightScreenNumberOfTurnsArgs(IExecutingMove Move, int Duration) : ICustomTriggerArgs + { + public int Duration { get; set; } = Duration; + } + public static readonly StringKey ReflectNumberOfTurns = "reflect_number_of_turns"; + public record ReflectNumberOfTurnsArgs(IExecutingMove Move, int Duration) : ICustomTriggerArgs + { + public int Duration { get; set; } = Duration; + } + public static readonly StringKey BypassSleep = "bypass_sleep"; + public record BypassSleepArgs(IExecutingMove Move, bool Bypass) : ICustomTriggerArgs + { + public bool Bypass { get; set; } = Bypass; + } + public static readonly StringKey Whirlpool = "whirlpool"; + public record WhirlpoolArgs(IExecutingMove Move, IPokemon Target, byte HitIndex, int Turns, float DamagePercent) + : ICustomTriggerArgs + { + public int Turns { get; set; } = Turns; + public float DamagePercent { get; set; } = DamagePercent; + } + public static readonly StringKey ModifyAuraEffect = "modify_aura_effect"; + public record ModifyAuraEffectArgs(IExecutingMove Move, IPokemon Target, byte HitIndex, float AuraEffect) + : ICustomTriggerArgs + { + public float AuraEffect { get; set; } = AuraEffect; + } + public static readonly StringKey BypassChargeMove = "bypass_charge_move"; + public record BypassChargeMoveArgs(IExecutingMove Move, bool Bypass) : ICustomTriggerArgs + { + public bool Bypass { get; set; } = Bypass; + } + public static readonly StringKey ModifySleepTurns = "modify_sleep_turns"; + public record ModifySleepTurnsArgs(IPokemon Pokemon, int Turns) : ICustomTriggerArgs + { + public int Turns { get; set; } = Turns; + } + public static readonly StringKey BypassProtection = "bypass_protection"; + public record BypassProtectionArgs(IExecutingMove Move, IPokemon Target, byte HitIndex, bool Bypass) + : ICustomTriggerArgs + { + public bool Bypass { get; set; } = Bypass; + } + public static readonly StringKey BypassSubstitute = "bypass_subsitute"; + public record BypassSubstituteArgs(IExecutingMove Move, IPokemon Target, byte HitIndex, bool Bypass) + : ICustomTriggerArgs + { + public bool Bypass { get; set; } = Bypass; + } + public static readonly StringKey ModifyDrain = "modify_drain"; + + public record ModifyDrainArgs(IExecutingMove Move, IPokemon Target, byte Hit, uint Damage, uint Healed, bool Invert) + : ICustomTriggerArgs + { + public uint Healed { get; set; } = Healed; + public bool Invert { get; set; } = Invert; + } + + public static readonly StringKey ModifyHealPercent = "modify_heal_percent"; + + public record ModifyHealPercentArgs(IExecutingMove Move, IPokemon Target, byte Hit, float HealPercent) + : ICustomTriggerArgs + { + public float HealPercent { get; set; } = HealPercent; + } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/BypassSleepVolatile.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/BypassSleepVolatile.cs index d021f25..9c4ac2a 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/BypassSleepVolatile.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/BypassSleepVolatile.cs @@ -6,9 +6,9 @@ namespace PkmnLib.Plugin.Gen7.Scripts.MoveVolatile; public class BypassSleepVolatile : Script { /// - public override void CustomTrigger(StringKey eventName, IDictionary? parameters) + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { - if (eventName == CustomTriggers.BypassSleep && parameters != null) - parameters["bypass_sleep"] = true; + if (eventName == CustomTriggers.BypassSleep && args is CustomTriggers.BypassSleepArgs bypassArgs) + bypassArgs.Bypass = true; } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/AuroraVeil.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/AuroraVeil.cs index b5809de..0e8c381 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/AuroraVeil.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/AuroraVeil.cs @@ -40,13 +40,9 @@ public class AuroraVeil : Script var side = battle.Sides[move.User.BattleData!.SideIndex]; - var numberOfTurns = 5; - var dict = new Dictionary - { - { "duration", numberOfTurns }, - }; - move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.AuroraVeilDuration, dict)); - numberOfTurns = (int)dict.GetOrDefault("duration", numberOfTurns)!; + var args = new CustomTriggers.AuroraVeilDurationArgs(move, 5); + move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.AuroraVeilDuration, args)); + var numberOfTurns = args.Duration; var script = side.VolatileScripts.StackOrAdd(ScriptUtils.ResolveName(), () => { diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BellyDrum.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BellyDrum.cs index aba839c..3fbaa5b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BellyDrum.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BellyDrum.cs @@ -13,7 +13,7 @@ public class BellyDrum : Script return; } - target.Damage(maxHealthHalved, DamageSource.Misc); + target.Damage(maxHealthHalved, DamageSource.Misc, forceDamage: true); // Raising the user's Attack by 12 stages should always set it to +6. target.ChangeStatBoost(Statistic.Attack, 12, true, false); } diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Bind.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Bind.cs index 2b7a311..492bb47 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Bind.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Bind.cs @@ -9,13 +9,11 @@ public class Bind : Script /// public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) { - var bindTurnsParameters = new Dictionary { { "bind_number_of_turns", 5 } }; - move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BindNumberOfTurns, bindTurnsParameters)); - var bindDamageParameters = new Dictionary { { "bind_percent_of_max_health", 1f / 8f } }; - move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BindPercentOfMaxHealth, bindDamageParameters)); + var args = new CustomTriggers.ModifyBindArgs(move); + move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyBind, args)); - var bindTurns = bindTurnsParameters.GetOrDefault("bind_number_of_turns", 5) as int? ?? 5; - var bindDamage = bindDamageParameters.GetOrDefault("bind_percent_of_max_health", 1f / 8f) as float? ?? 1f / 8f; + var bindTurns = args.Duration; + var bindDamage = args.DamagePercent; var bindEffect = new BindEffect(target, bindTurns, bindDamage); target.Volatile.Add(bindEffect); diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChargeMove.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChargeMove.cs index d225498..3fafa87 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChargeMove.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChargeMove.cs @@ -7,13 +7,10 @@ public class ChargeMove : Script { public override void PreventMove(IExecutingMove move, ref bool prevent) { - var bypassCharge = false; - var parameters = new Dictionary - { - { "move", move }, - { "bypassCharge", bypassCharge }, - }; - move.RunScriptHook(script => script.CustomTrigger(CustomTriggers.BypassChargeMove, parameters)); + var args = new CustomTriggers.BypassChargeMoveArgs(move, false); + move.RunScriptHook(script => script.CustomTrigger(CustomTriggers.BypassChargeMove, args)); + if (args.Bypass) + return; var chargeMoveEffect = move.User.Volatile.Get(); if (chargeMoveEffect != null && chargeMoveEffect.MoveName == move.UseMove.Name) diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Curse.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Curse.cs index 39ae511..3478291 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Curse.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Curse.cs @@ -16,7 +16,7 @@ public class Curse : Script return; if (move.User.Types.Contains(ghostType)) { - move.User.Damage(move.User.CurrentHealth / 2, DamageSource.Misc); + move.User.Damage(move.User.CurrentHealth / 2, DamageSource.Misc, forceDamage: true); if (move.User.CurrentHealth == 0) return; target.Volatile.Add(new GhostCurseEffect(target)); diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Drain.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Drain.cs index 9a9474f..fa16e75 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Drain.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Drain.cs @@ -23,21 +23,15 @@ public class Drain : Script if (move.User.HasHeldItem("big_root")) healed = (uint)(healed * 1.3f); var invert = false; - var parameters = new Dictionary - { - { "user", user }, - { "target", target }, - { "damage", damage }, - { "healed", healed }, - { "invert", invert }, - }; - target.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyDrain, parameters)); - if (parameters.TryGetValue("invert", out var invertObj) && invertObj is bool invertBool) - invert = invertBool; + + var args = new CustomTriggers.ModifyDrainArgs(move, target, hit, damage, healed, invert); + target.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyDrain, args)); + invert = args.Invert; + healed = args.Healed; if (invert) { - user.Damage(damage, DamageSource.Misc); + user.Damage(healed, DamageSource.Misc); } else { diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DreamEater.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DreamEater.cs index 32202cc..721bd11 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DreamEater.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DreamEater.cs @@ -19,22 +19,14 @@ public class DreamEater : Script if (move.User.HasHeldItem("big_root")) healed = (uint)(healed * 1.3f); - var invert = false; - var parameters = new Dictionary - { - { "user", user }, - { "target", target }, - { "damage", damage }, - { "healed", healed }, - { "invert", invert }, - }; - target.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyDrain, parameters)); - if (parameters.TryGetValue("invert", out var invertObj) && invertObj is bool invertBool) - invert = invertBool; + var args = new CustomTriggers.ModifyDrainArgs(move, target, hit, damage, healed, false); + target.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyDrain, args)); + var invert = args.Invert; + healed = args.Healed; if (invert) { - user.Damage(damage, DamageSource.Misc); + user.Damage(healed, DamageSource.Misc); } else { diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealPercent.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealPercent.cs index 4e6480d..8230286 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealPercent.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HealPercent.cs @@ -19,6 +19,10 @@ public class HealPercent : Script /// public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) { - target.Heal((uint)(move.User.BoostedStats.Hp * _healPercent)); + var args = new CustomTriggers.ModifyHealPercentArgs(move, target, hit, _healPercent); + move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyHealPercent, args)); + var healPercent = args.HealPercent; + + target.Heal(target.BoostedStats.Hp.MultiplyOrMax(healPercent)); } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LightScreen.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LightScreen.cs index a8a6016..16fa1cb 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LightScreen.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/LightScreen.cs @@ -13,13 +13,9 @@ public class LightScreen : Script if (battleData == null) return; - var turns = 5; - var dict = new Dictionary - { - { "duration", turns }, - }; - move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.LightScreenNumberOfTurns, dict)); - turns = (int)dict.GetOrDefault("duration", turns)!; + var args = new CustomTriggers.LightScreenNumberOfTurnsArgs(move, 5); + move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.LightScreenNumberOfTurns, args)); + var turns = args.Duration; battleData.BattleSide.VolatileScripts.StackOrAdd(ScriptUtils.ResolveName(), () => new LightScreenEffect(turns)); diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Reflect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Reflect.cs index 5780b5c..5058b75 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Reflect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Reflect.cs @@ -12,12 +12,10 @@ public class Reflect : Script if (battleData is null) return; var numberOfTurns = 5; - var dict = new Dictionary - { - { "duration", numberOfTurns }, - }; - move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ReflectNumberOfTurns, dict)); - numberOfTurns = (int)dict.GetOrDefault("duration", numberOfTurns)!; + + var args = new CustomTriggers.ReflectNumberOfTurnsArgs(move, numberOfTurns); + move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ReflectNumberOfTurns, args)); + numberOfTurns = args.Duration; battleData.BattleSide.VolatileScripts.Add(new Side.ReflectEffect(numberOfTurns)); } diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Whirlpool.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Whirlpool.cs index a456b51..af9b84b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Whirlpool.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Whirlpool.cs @@ -14,14 +14,10 @@ public class Whirlpool : Script { var turns = move.Battle.Random.GetInt(4, 6); var damagePercent = 0.125f; - var parameters = new Dictionary - { - { "number_of_turns", turns }, - { "damage_percent", damagePercent }, - }; - move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.Whirlpool, parameters)); - turns = parameters.GetValueOrDefault("number_of_turns", turns) as int? ?? turns; - damagePercent = parameters.GetValueOrDefault("damage_percent", damagePercent) as float? ?? damagePercent; + var args = new CustomTriggers.WhirlpoolArgs(move, target, hit, turns, damagePercent); + move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.Whirlpool, args)); + turns = args.Turns; + damagePercent = args.DamagePercent; whirlpoolEffect.AddTargetedPokemon(target, turns, damagePercent); } } diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DestinyBondEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DestinyBondEffect.cs index 4866b47..5e5b5c5 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DestinyBondEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DestinyBondEffect.cs @@ -10,7 +10,7 @@ public class DestinyBondEffect : Script { if (pokemon.BattleData?.Battle.ChoiceQueue?.LastRanChoice is not IMoveChoice lastChoice) return; - lastChoice.User.Damage(lastChoice.User.BoostedStats.Hp * 10, DamageSource.Misc); + lastChoice.User.Damage(lastChoice.User.BoostedStats.Hp * 10, DamageSource.Misc, forceDamage: true); } } diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs index e2fc3ee..d8d7a89 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs @@ -11,17 +11,9 @@ public class ProtectionEffectScript : Script if (!executingMove.UseMove.HasFlag("protect")) return; - var bypass = false; - var parameters = new Dictionary - { - { "target", target }, - { "move", executingMove }, - { "hitIndex", hitIndex }, - { "bypass", bypass }, - }; - executingMove.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassProtection, parameters)); - bypass = parameters.GetValueOrDefault("bypass", false) as bool? ?? false; - if (bypass) + var args = new CustomTriggers.BypassProtectionArgs(executingMove, target, hitIndex, false); + executingMove.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassProtection, args)); + if (args.Bypass) return; block = true; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SubstituteEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SubstituteEffect.cs index 7890e1c..e5de7fc 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SubstituteEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SubstituteEffect.cs @@ -11,17 +11,9 @@ public class SubstituteEffect(uint health) : Script if (executingMove.UseMove.HasFlag("ignore-substitute")) return; - var bypass = false; - var parameters = new Dictionary - { - { "target", target }, - { "move", executingMove }, - { "hitIndex", hitIndex }, - { "bypass", bypass }, - }; - executingMove.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassProtection, parameters)); - bypass = parameters.GetValueOrDefault("bypass", false) as bool? ?? false; - if (bypass) + var args = new CustomTriggers.BypassSubstituteArgs(executingMove, target, hitIndex, false); + executingMove.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassSubstitute, args)); + if (args.Bypass) return; block = true; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs index e2c0d7c..eeb8376 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs @@ -22,13 +22,9 @@ public class Sleep : Script { // 1-3 turns of sleep Turns = battleData.Battle.Random.GetInt(1, 4); - source.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifySleepTurns, - new Dictionary - { - { "pokemon", pokemon }, - { "turns", Turns }, - })); - Turns = Math.Max(1, Turns); + var args = new CustomTriggers.ModifySleepTurnsArgs(pokemon, Turns); + source.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifySleepTurns, args)); + Turns = Math.Max(1, args.Turns); } } @@ -45,14 +41,9 @@ public class Sleep : Script if (move.UseMove.HasFlag("usable_while_asleep")) return; - var bypass = false; - var pars = new Dictionary - { - { "bypass_sleep", bypass }, - }; - move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassSleep, pars)); - bypass = pars.GetValueOrDefault("bypass_sleep", false) as bool? ?? false; - if (bypass) + var args = new CustomTriggers.BypassSleepArgs(move, false); + move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassSleep, args)); + if (args.Bypass) return; prevent = true; } diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs index f5a93c2..2aa53a4 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs @@ -29,12 +29,9 @@ public class Hail : Script, ILimitedTurnsScript continue; if (pokemon.Types.Contains(iceType)) continue; - var ignoresHail = false; - pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.IgnoreHail, new Dictionary - { - { "ignoresHail", ignoresHail }, - })); - if (ignoresHail) + var args = new CustomTriggers.IgnoreHailArgs(pokemon); + pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.IgnoreHail, args)); + if (args.Ignore) continue; var maxHealth = pokemon.BoostedStats.Hp; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/HarshSunlight.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/HarshSunlight.cs index a83e0e4..db0d8c7 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/HarshSunlight.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/HarshSunlight.cs @@ -24,17 +24,15 @@ public class HarshSunlight : Script } /// - public override void CustomTrigger(StringKey eventName, IDictionary? parameters) + public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args) { - if (eventName == CustomTriggers.BypassChargeMove) + if (eventName != CustomTriggers.BypassChargeMove) + return; + if (args is not CustomTriggers.BypassChargeMoveArgs bypassArgs) + return; + if (bypassArgs.Move.UseMove.Name == "solar_beam" || bypassArgs.Move.UseMove.Name == "solar_blade") { - if (parameters == null || !parameters.TryGetValue("move", out var moveObj) || - moveObj is not IExecutingMove move) - return; - if (move.UseMove.Name == "solar_beam" || move.UseMove.Name == "solar_blade") - { - parameters["bypassCharge"] = true; - } + bypassArgs.Bypass = true; } }