More abilities, refactor custom triggers to be typed.
All checks were successful
Build / Build (push) Successful in 48s

This commit is contained in:
Deukhoofd 2025-06-13 11:15:48 +02:00
parent 4326794611
commit 6d71de375e
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
43 changed files with 630 additions and 196 deletions

View File

@ -1023,6 +1023,8 @@ public class PokemonImpl : ScriptSource, IPokemon
this.RunScriptHook(script => script.ChangeIncomingDamage(this, source, ref dmg)); this.RunScriptHook(script => script.ChangeIncomingDamage(this, source, ref dmg));
damage = 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 // If the damage is more than the current health, we cap it at the current health, to prevent
// underflow. // underflow.

View File

@ -0,0 +1,126 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.ScriptHandling;
/// <summary>
/// Interface for custom trigger arguments.
/// </summary>
public interface ICustomTriggerArgs;
/// <summary>
/// Static helper methods for working with <see cref="ICustomTriggerArgs"/>.
///
/// 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.
/// </summary>
public static class CustomTriggerArgsHelpers
{
private record TypeFuncs(
Dictionary<StringKey, Func<ICustomTriggerArgs, object?>> Getters,
Dictionary<StringKey, Action<ICustomTriggerArgs, object?>> Setters);
private static readonly Dictionary<Type, TypeFuncs> 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<Func<ICustomTriggerArgs, object?>>(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<Action<ICustomTriggerArgs, object?>>(assign, parameter, valueParameter)
.Compile();
});
TypeFuncInfo[type] = new TypeFuncs(getters, setters);
}
private static bool TryGetGetter(Type type, StringKey key,
[NotNullWhen(true)] out Func<ICustomTriggerArgs, object?>? 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<ICustomTriggerArgs, object?>? setter)
{
if (!TypeFuncInfo.ContainsKey(type))
PopulateForType(type);
return TypeFuncInfo[type].Setters.TryGetValue(key, out setter);
}
public static T Get<T>(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<T>(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);
}
}

View File

@ -356,6 +356,23 @@ public abstract class Script : IDeepCloneable
{ {
} }
/// <summary>
/// This function allows a script to change the offensive stat value of an incoming move.
/// </summary>
public virtual void ChangeIncomingMoveOffensiveStatValue(IExecutingMove executingMove, IPokemon target,
byte hitNumber, uint defensiveStat, StatisticSet<uint> targetStats, Statistic offensive, ref uint offensiveStat)
{
}
/// <summary>
/// This function allows a script to change the defensive stat value of an incoming move.
/// </summary>
public virtual void ChangeIncomingMoveDefensiveStatValue(IExecutingMove executingMove, IPokemon target,
byte hitNumber, uint origOffensiveStat, StatisticSet<uint> targetStats, Statistic defensive,
ref uint defensiveStat)
{
}
/// <summary> /// <summary>
/// This function allows a script to change the raw modifier we retrieved from the stats of the /// 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. /// 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 /// 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. /// should validate the event name is one they should handle.
/// </param> /// </param>
/// <param name="parameters"> /// <param name="args">
/// 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.
/// </param> /// </param>
public virtual void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters) public virtual void CustomTrigger(StringKey eventName, ICustomTriggerArgs args)
{ {
} }

View File

@ -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<IExecutingMove>();
var target = Substitute.For<IPokemon>();
var healPercent = 0.5f;
var user = Substitute.For<IPokemon>();
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<uint>(amount => healedAmount = amount));
target.BoostedStats.Returns(new StatisticSet<uint>(100, 100, 100, 100, 100, 100));
// Act
healPercentScript.OnInitialize(new Dictionary<StringKey, object?>
{
{ "healPercent", healPercent },
});
healPercentScript.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(healedAmount).IsEqualTo((uint)(target.BoostedStats.Hp * healPercent * 1.5f));
}
}

View File

@ -342,15 +342,33 @@
"magic_bounce": { "magic_bounce": {
"effect": "magic_bounce" "effect": "magic_bounce"
}, },
"magic_guard": {}, "magic_guard": {
"magician": {}, "effect": "magic_guard"
"magma_armor": {}, },
"magnet_pull": {}, "magician": {
"marvel_scale": {}, "effect": "magician"
"mega_launcher": {}, },
"merciless": {}, "magma_armor": {
"minus": {}, "effect": "magma_armor"
"misty_surge": {}, },
"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": {}, "mold_breaker": {},
"moody": {}, "moody": {},
"motor_drive": {}, "motor_drive": {},

View File

@ -136,6 +136,10 @@ public class Gen7DamageCalculator(Gen7PluginConfiguration configuration) : IDama
defensiveStat, targetStats, offensive, ref offensiveStat)); defensiveStat, targetStats, offensive, ref offensiveStat));
executingMove.RunScriptHook(script => script.ChangeDefensiveStatValue(executingMove, target, hitNumber, executingMove.RunScriptHook(script => script.ChangeDefensiveStatValue(executingMove, target, hitNumber,
origOffensiveStat, targetStats, defensive, ref defensiveStat)); 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; var modifier = (float)offensiveStat / defensiveStat;
executingMove.RunScriptHook(script => executingMove.RunScriptHook(script =>

View File

@ -10,21 +10,16 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
public class AuraBreak : Script public class AuraBreak : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? 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; return;
if (!parameters.TryGetValue("aura_type", out var auraTypeObj) || auraTypeObj is not string auraType) var typeName = auraArgs.Move.UseMove.MoveType.Name;
return; if (typeName == "dark" || typeName == "fairy")
if (auraType is "dark" or "fairy")
{ {
if (parameters.TryGetValue("modifier", out var modifierObj) && modifierObj is float) // Reverse the aura effect by reducing power by 25%
{ auraArgs.AuraEffect *= 0.75f;
// Reverse the aura effect by reducing power by 25%
parameters["modifier"] = 0.75f;
}
} }
} }
} }

View File

@ -21,11 +21,11 @@ public class Comatose : Script
} }
/// <inheritdoc /> /// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? 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;
} }
} }
} }

View File

@ -15,17 +15,10 @@ public class DarkAura : Script
if (move.GetHitData(target, hit).Type?.Name == "dark") if (move.GetHitData(target, hit).Type?.Name == "dark")
{ {
var auraModifier = 5448f / 4096f; var auraModifier = 5448f / 4096f;
var parameters = new Dictionary<StringKey, object?> var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier);
{
["aura_type"] = "dark",
["modifier"] = auraModifier,
};
move.Battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull() move.Battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull()
.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, parameters)); .RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, args));
if (parameters.TryGetValue("modifier", out var modObj) && modObj is float modValue) auraModifier = args.AuraEffect;
{
auraModifier = modValue;
}
modifier *= auraModifier; modifier *= auraModifier;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
} }

View File

@ -11,15 +11,11 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
public class EarlyBird : Script public class EarlyBird : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters) public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args)
{ {
if (eventName != CustomTriggers.ModifySleepTurns) if (eventName != CustomTriggers.ModifySleepTurns)
return; return;
if (parameters == null) if (args is CustomTriggers.ModifySleepTurnsArgs sleepArgs)
return; sleepArgs.Turns /= 2;
if (parameters.TryGetValue("turns", out var turnsObj) && turnsObj is int turns)
{
parameters["turns"] = turns / 2;
}
} }
} }

View File

@ -16,17 +16,10 @@ public class FairyAura : Script
if (move.GetHitData(target, hit).Type?.Name == "fairy") if (move.GetHitData(target, hit).Type?.Name == "fairy")
{ {
var auraModifier = 5448f / 4096f; var auraModifier = 5448f / 4096f;
var parameters = new Dictionary<StringKey, object?> var args = new CustomTriggers.ModifyAuraEffectArgs(move, target, hit, auraModifier);
{
["aura_type"] = "fairy",
["modifier"] = auraModifier,
};
move.Battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull() move.Battle.Sides.SelectMany(side => side.Pokemon).WhereNotNull()
.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, parameters)); .RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyAuraEffect, args));
if (parameters.TryGetValue("modifier", out var modObj) && modObj is float modValue) auraModifier = args.AuraEffect;
{
auraModifier = modValue;
}
modifier *= auraModifier; modifier *= auraModifier;
move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User)); move.Battle.EventHook.Invoke(new AbilityTriggerEvent(move.User));
} }

View File

@ -10,8 +10,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
public class GrassPelt : Script public class GrassPelt : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, public override void ChangeIncomingMoveDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit,
ImmutableStatisticSet<uint> targetStats, Statistic stat, ref uint value) uint offensiveStat, StatisticSet<uint> statisticSet, Statistic stat, ref uint value)
{ {
if (move.Battle.TerrainName == ScriptUtils.ResolveName<Terrain.GrassyTerrain>() && stat == Statistic.Defense) if (move.Battle.TerrainName == ScriptUtils.ResolveName<Terrain.GrassyTerrain>() && stat == Statistic.Defense)
value = value.MultiplyOrMax(1.5f); value = value.MultiplyOrMax(1.5f);

View File

@ -41,11 +41,10 @@ public class IceBody : Script
} }
/// <inheritdoc /> /// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? 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; return;
ignoreHailArgs.Ignore = true;
parameters["ignoresHail"] = true;
} }
} }

View File

@ -9,12 +9,15 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
public class Infiltrator : Script public class Infiltrator : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters) public override void CustomTrigger(StringKey eventName, ICustomTriggerArgs args)
{ {
if ((eventName != CustomTriggers.BypassProtection && eventName != CustomTriggers.BypassSubstitute) || if (eventName == CustomTriggers.BypassSubstitute && args is CustomTriggers.BypassSubstituteArgs bypassArgs)
parameters is null) {
return; bypassArgs.Bypass = true;
}
parameters["bypass"] = true; else if (eventName == CustomTriggers.BypassProtection && args is CustomTriggers.BypassProtectionArgs customArgs)
{
customArgs.Bypass = true;
}
} }
} }

View File

@ -9,11 +9,10 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
public class LiquidOoze : Script public class LiquidOoze : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? 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; return;
parameters.Invert = true;
parameters["invert"] = true;
} }
} }

View File

@ -0,0 +1,25 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Magic Guard is an ability that prevents all damage except from direct attacks.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Magic_Guard_(Ability)">Bulbapedia - Magic Guard</see>
/// </summary>
[Script(ScriptCategory.Ability, "magic_guard")]
public class MagicGuard : Script
{
/// <inheritdoc />
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;
}
}

View File

@ -0,0 +1,25 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Magician is an ability that steals the held item of a Pokémon it hits with a move.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Magician_(Ability)">Bulbapedia - Magician</see>
/// </summary>
[Script(ScriptCategory.Ability, "magician")]
public class Magician : Script
{
/// <inheritdoc />
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());
}
}

View File

@ -0,0 +1,31 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Magma Armor is an ability that prevents the Pokémon from being frozen.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Magma_Armor_(Ability)">Bulbapedia - Magma Armor</see>
/// </summary>
[Script(ScriptCategory.Ability, "magma_armor")]
public class MagmaArmor : Script
{
/// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemon, StringKey status, bool selfInflicted,
ref bool preventStatus)
{
if (status == ScriptUtils.ResolveName<Status.Frozen>())
{
pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon));
preventStatus = true;
}
}
/// <inheritdoc />
public override void OnSwitchIn(IPokemon pokemon, byte position)
{
if (pokemon.HasStatus(ScriptUtils.ResolveName<Status.Frozen>()))
{
pokemon.BattleData?.Battle.EventHook.Invoke(new AbilityTriggerEvent(pokemon));
pokemon.ClearStatus();
}
}
}

View File

@ -0,0 +1,24 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Magnet Pull is an ability that prevents Steel-type Pokémon from fleeing or switching out.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Magnet_Pull_(Ability)">Bulbapedia - Magnet Pull</see>
/// </summary>
[Script(ScriptCategory.Ability, "magnet_pull")]
public class MagnetPull : Script
{
/// <inheritdoc />
public override void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent)
{
if (choice.User.Types.Any(x => x.Name == "steel"))
prevent = true;
}
/// <inheritdoc />
public override void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent)
{
if (choice.User.Types.Any(x => x.Name == "steel"))
prevent = true;
}
}

View File

@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Marvel Scale is an ability that increases the Pokémon's Defense by 50% when it has a status condition.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Marvel_Scale_(Ability)">Bulbapedia - Marvel Scale</see>
/// </summary>
[Script(ScriptCategory.Ability, "marvel_scale")]
public class MarvelScale : Script
{
/// <inheritdoc />
public override void ChangeIncomingMoveDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit,
uint offensiveStat, StatisticSet<uint> statisticSet, Statistic stat, ref uint value)
{
if (!target.StatusScript.IsEmpty && stat == Statistic.Defense)
{
value = value.MultiplyOrMax(1.5f);
}
}
}

View File

@ -0,0 +1,29 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Mega Launcher is an ability that boosts the power of aura and pulse moves.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Mega_Launcher_(Ability)">Bulbapedia - Mega Launcher</see>
/// </summary>
[Script(ScriptCategory.Ability, "mega_launcher")]
public class MegaLauncher : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
if (move.UseMove.HasFlag("pulse"))
{
basePower = basePower.MultiplyOrMax(1.5f);
}
}
/// <inheritdoc />
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;
}
}

View File

@ -0,0 +1,22 @@
using PkmnLib.Plugin.Gen7.Scripts.Status;
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Merciless is an ability that always results in critical hits against poisoned targets.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Merciless_(Ability)">Bulbapedia - Merciless</see>
/// </summary>
[Script(ScriptCategory.Ability, "merciless")]
public class Merciless : Script
{
/// <inheritdoc />
public override void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage)
{
if (target.StatusScript.Script?.Name == ScriptUtils.ResolveName<Poisoned>() ||
target.StatusScript.Script?.Name == ScriptUtils.ResolveName<BadlyPoisoned>())
{
stage = 100;
}
}
}

View File

@ -0,0 +1,23 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Minus is an ability that boosts Special Attack if another ally has Plus or Minus.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Minus_(Ability)">Bulbapedia - Minus</see>
/// </summary>
[Script(ScriptCategory.Ability, "minus")]
public class Minus : Script
{
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ImmutableStatisticSet<uint> 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);
}
}
}

View File

@ -0,0 +1,28 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Abilities;
/// <summary>
/// Misty Surge is an ability that creates Misty Terrain when the Pokémon enters battle.
///
/// <see href="https://bulbapedia.bulbagarden.net/wiki/Misty_Surge_(Ability)">Bulbapedia - Misty Surge</see>
/// </summary>
[Script(ScriptCategory.Ability, "misty_surge")]
public class MistySurge : Script
{
/// <inheritdoc />
public override void OnSwitchIn(IPokemon pokemon, byte position)
{
if (pokemon.BattleData?.Battle is null)
return;
var terrainName = ScriptUtils.ResolveName<Terrain.MistyTerrain>();
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);
}
}

View File

@ -4,29 +4,108 @@ public static class CustomTriggers
{ {
public static readonly StringKey AuroraVeilDuration = "aurora_veil_duration"; 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 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 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 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 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 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 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 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 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 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 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 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;
}
} }

View File

@ -6,9 +6,9 @@ namespace PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
public class BypassSleepVolatile : Script public class BypassSleepVolatile : Script
{ {
/// <inheritdoc /> /// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? 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;
} }
} }

View File

@ -40,13 +40,9 @@ public class AuroraVeil : Script
var side = battle.Sides[move.User.BattleData!.SideIndex]; var side = battle.Sides[move.User.BattleData!.SideIndex];
var numberOfTurns = 5; var args = new CustomTriggers.AuroraVeilDurationArgs(move, 5);
var dict = new Dictionary<StringKey, object?> move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.AuroraVeilDuration, args));
{ var numberOfTurns = args.Duration;
{ "duration", numberOfTurns },
};
move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.AuroraVeilDuration, dict));
numberOfTurns = (int)dict.GetOrDefault("duration", numberOfTurns)!;
var script = side.VolatileScripts.StackOrAdd(ScriptUtils.ResolveName<AuroraVeilEffect>(), () => var script = side.VolatileScripts.StackOrAdd(ScriptUtils.ResolveName<AuroraVeilEffect>(), () =>
{ {

View File

@ -13,7 +13,7 @@ public class BellyDrum : Script
return; 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. // Raising the user's Attack by 12 stages should always set it to +6.
target.ChangeStatBoost(Statistic.Attack, 12, true, false); target.ChangeStatBoost(Statistic.Attack, 12, true, false);
} }

View File

@ -9,13 +9,11 @@ public class Bind : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{ {
var bindTurnsParameters = new Dictionary<StringKey, object?> { { "bind_number_of_turns", 5 } }; var args = new CustomTriggers.ModifyBindArgs(move);
move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BindNumberOfTurns, bindTurnsParameters)); move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyBind, args));
var bindDamageParameters = new Dictionary<StringKey, object?> { { "bind_percent_of_max_health", 1f / 8f } };
move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BindPercentOfMaxHealth, bindDamageParameters));
var bindTurns = bindTurnsParameters.GetOrDefault("bind_number_of_turns", 5) as int? ?? 5; var bindTurns = args.Duration;
var bindDamage = bindDamageParameters.GetOrDefault("bind_percent_of_max_health", 1f / 8f) as float? ?? 1f / 8f; var bindDamage = args.DamagePercent;
var bindEffect = new BindEffect(target, bindTurns, bindDamage); var bindEffect = new BindEffect(target, bindTurns, bindDamage);
target.Volatile.Add(bindEffect); target.Volatile.Add(bindEffect);

View File

@ -7,13 +7,10 @@ public class ChargeMove : Script
{ {
public override void PreventMove(IExecutingMove move, ref bool prevent) public override void PreventMove(IExecutingMove move, ref bool prevent)
{ {
var bypassCharge = false; var args = new CustomTriggers.BypassChargeMoveArgs(move, false);
var parameters = new Dictionary<StringKey, object?> move.RunScriptHook(script => script.CustomTrigger(CustomTriggers.BypassChargeMove, args));
{ if (args.Bypass)
{ "move", move }, return;
{ "bypassCharge", bypassCharge },
};
move.RunScriptHook(script => script.CustomTrigger(CustomTriggers.BypassChargeMove, parameters));
var chargeMoveEffect = move.User.Volatile.Get<ChargeMoveEffect>(); var chargeMoveEffect = move.User.Volatile.Get<ChargeMoveEffect>();
if (chargeMoveEffect != null && chargeMoveEffect.MoveName == move.UseMove.Name) if (chargeMoveEffect != null && chargeMoveEffect.MoveName == move.UseMove.Name)

View File

@ -16,7 +16,7 @@ public class Curse : Script
return; return;
if (move.User.Types.Contains(ghostType)) 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) if (move.User.CurrentHealth == 0)
return; return;
target.Volatile.Add(new GhostCurseEffect(target)); target.Volatile.Add(new GhostCurseEffect(target));

View File

@ -23,21 +23,15 @@ public class Drain : Script
if (move.User.HasHeldItem("big_root")) if (move.User.HasHeldItem("big_root"))
healed = (uint)(healed * 1.3f); healed = (uint)(healed * 1.3f);
var invert = false; var invert = false;
var parameters = new Dictionary<StringKey, object?>
{ var args = new CustomTriggers.ModifyDrainArgs(move, target, hit, damage, healed, invert);
{ "user", user }, target.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyDrain, args));
{ "target", target }, invert = args.Invert;
{ "damage", damage }, healed = args.Healed;
{ "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;
if (invert) if (invert)
{ {
user.Damage(damage, DamageSource.Misc); user.Damage(healed, DamageSource.Misc);
} }
else else
{ {

View File

@ -19,22 +19,14 @@ public class DreamEater : Script
if (move.User.HasHeldItem("big_root")) if (move.User.HasHeldItem("big_root"))
healed = (uint)(healed * 1.3f); healed = (uint)(healed * 1.3f);
var invert = false; var args = new CustomTriggers.ModifyDrainArgs(move, target, hit, damage, healed, false);
var parameters = new Dictionary<StringKey, object?> target.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifyDrain, args));
{ var invert = args.Invert;
{ "user", user }, healed = args.Healed;
{ "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;
if (invert) if (invert)
{ {
user.Damage(damage, DamageSource.Misc); user.Damage(healed, DamageSource.Misc);
} }
else else
{ {

View File

@ -19,6 +19,10 @@ public class HealPercent : Script
/// <inheritdoc /> /// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) 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));
} }
} }

View File

@ -13,13 +13,9 @@ public class LightScreen : Script
if (battleData == null) if (battleData == null)
return; return;
var turns = 5; var args = new CustomTriggers.LightScreenNumberOfTurnsArgs(move, 5);
var dict = new Dictionary<StringKey, object?> move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.LightScreenNumberOfTurns, args));
{ var turns = args.Duration;
{ "duration", turns },
};
move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.LightScreenNumberOfTurns, dict));
turns = (int)dict.GetOrDefault("duration", turns)!;
battleData.BattleSide.VolatileScripts.StackOrAdd(ScriptUtils.ResolveName<LightScreenEffect>(), battleData.BattleSide.VolatileScripts.StackOrAdd(ScriptUtils.ResolveName<LightScreenEffect>(),
() => new LightScreenEffect(turns)); () => new LightScreenEffect(turns));

View File

@ -12,12 +12,10 @@ public class Reflect : Script
if (battleData is null) if (battleData is null)
return; return;
var numberOfTurns = 5; var numberOfTurns = 5;
var dict = new Dictionary<StringKey, object?>
{ var args = new CustomTriggers.ReflectNumberOfTurnsArgs(move, numberOfTurns);
{ "duration", numberOfTurns }, move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ReflectNumberOfTurns, args));
}; numberOfTurns = args.Duration;
move.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ReflectNumberOfTurns, dict));
numberOfTurns = (int)dict.GetOrDefault("duration", numberOfTurns)!;
battleData.BattleSide.VolatileScripts.Add(new Side.ReflectEffect(numberOfTurns)); battleData.BattleSide.VolatileScripts.Add(new Side.ReflectEffect(numberOfTurns));
} }

View File

@ -14,14 +14,10 @@ public class Whirlpool : Script
{ {
var turns = move.Battle.Random.GetInt(4, 6); var turns = move.Battle.Random.GetInt(4, 6);
var damagePercent = 0.125f; var damagePercent = 0.125f;
var parameters = new Dictionary<StringKey, object?> var args = new CustomTriggers.WhirlpoolArgs(move, target, hit, turns, damagePercent);
{ move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.Whirlpool, args));
{ "number_of_turns", turns }, turns = args.Turns;
{ "damage_percent", damagePercent }, damagePercent = args.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;
whirlpoolEffect.AddTargetedPokemon(target, turns, damagePercent); whirlpoolEffect.AddTargetedPokemon(target, turns, damagePercent);
} }
} }

View File

@ -10,7 +10,7 @@ public class DestinyBondEffect : Script
{ {
if (pokemon.BattleData?.Battle.ChoiceQueue?.LastRanChoice is not IMoveChoice lastChoice) if (pokemon.BattleData?.Battle.ChoiceQueue?.LastRanChoice is not IMoveChoice lastChoice)
return; return;
lastChoice.User.Damage(lastChoice.User.BoostedStats.Hp * 10, DamageSource.Misc); lastChoice.User.Damage(lastChoice.User.BoostedStats.Hp * 10, DamageSource.Misc, forceDamage: true);
} }
} }

View File

@ -11,17 +11,9 @@ public class ProtectionEffectScript : Script
if (!executingMove.UseMove.HasFlag("protect")) if (!executingMove.UseMove.HasFlag("protect"))
return; return;
var bypass = false; var args = new CustomTriggers.BypassProtectionArgs(executingMove, target, hitIndex, false);
var parameters = new Dictionary<StringKey, object?> executingMove.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassProtection, args));
{ if (args.Bypass)
{ "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)
return; return;
block = true; block = true;

View File

@ -11,17 +11,9 @@ public class SubstituteEffect(uint health) : Script
if (executingMove.UseMove.HasFlag("ignore-substitute")) if (executingMove.UseMove.HasFlag("ignore-substitute"))
return; return;
var bypass = false; var args = new CustomTriggers.BypassSubstituteArgs(executingMove, target, hitIndex, false);
var parameters = new Dictionary<StringKey, object?> executingMove.User.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassSubstitute, args));
{ if (args.Bypass)
{ "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)
return; return;
block = true; block = true;

View File

@ -22,13 +22,9 @@ public class Sleep : Script
{ {
// 1-3 turns of sleep // 1-3 turns of sleep
Turns = battleData.Battle.Random.GetInt(1, 4); Turns = battleData.Battle.Random.GetInt(1, 4);
source.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifySleepTurns, var args = new CustomTriggers.ModifySleepTurnsArgs(pokemon, Turns);
new Dictionary<StringKey, object?> source.RunScriptHook(x => x.CustomTrigger(CustomTriggers.ModifySleepTurns, args));
{ Turns = Math.Max(1, args.Turns);
{ "pokemon", pokemon },
{ "turns", Turns },
}));
Turns = Math.Max(1, Turns);
} }
} }
@ -45,14 +41,9 @@ public class Sleep : Script
if (move.UseMove.HasFlag("usable_while_asleep")) if (move.UseMove.HasFlag("usable_while_asleep"))
return; return;
var bypass = false; var args = new CustomTriggers.BypassSleepArgs(move, false);
var pars = new Dictionary<StringKey, object?> move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassSleep, args));
{ if (args.Bypass)
{ "bypass_sleep", bypass },
};
move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassSleep, pars));
bypass = pars.GetValueOrDefault("bypass_sleep", false) as bool? ?? false;
if (bypass)
return; return;
prevent = true; prevent = true;
} }

View File

@ -29,12 +29,9 @@ public class Hail : Script, ILimitedTurnsScript
continue; continue;
if (pokemon.Types.Contains(iceType)) if (pokemon.Types.Contains(iceType))
continue; continue;
var ignoresHail = false; var args = new CustomTriggers.IgnoreHailArgs(pokemon);
pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.IgnoreHail, new Dictionary<StringKey, object?> pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.IgnoreHail, args));
{ if (args.Ignore)
{ "ignoresHail", ignoresHail },
}));
if (ignoresHail)
continue; continue;
var maxHealth = pokemon.BoostedStats.Hp; var maxHealth = pokemon.BoostedStats.Hp;

View File

@ -24,17 +24,15 @@ public class HarshSunlight : Script
} }
/// <inheritdoc /> /// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? 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) || bypassArgs.Bypass = true;
moveObj is not IExecutingMove move)
return;
if (move.UseMove.Name == "solar_beam" || move.UseMove.Name == "solar_blade")
{
parameters["bypassCharge"] = true;
}
} }
} }