More moves implemented

This commit is contained in:
2025-05-05 11:36:59 +02:00
parent 11ba3c73bb
commit 292c303fc0
39 changed files with 818 additions and 68 deletions

View File

@@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using PkmnLib.Dynamic.Models.BattleFlow;
namespace PkmnLib.Plugin.Gen7.Scripts.Battle;
[Script(ScriptCategory.Battle, "snatch_effect")]
public class SnatchEffect : Script
{
private Queue<IPokemon> _snatchers = new();
public void AddSnatcher(IPokemon snatcher)
{
if (_snatchers.Contains(snatcher))
{
_snatchers = new Queue<IPokemon>(_snatchers.Where(s => s != snatcher));
}
_snatchers.Enqueue(snatcher);
}
private bool TryGetSnatcher([NotNullWhen(true)] out IPokemon? snatcher)
{
while (_snatchers.Any())
{
var s = _snatchers.Dequeue();
if (s.BattleData != null && s.IsUsable)
{
snatcher = s;
return true;
}
}
snatcher = null;
return false;
}
/// <inheritdoc />
public override void StopBeforeMove(IExecutingMove move, ref bool stop)
{
if (move.UseMove.HasFlag("snatch") && TryGetSnatcher(out var snatcher))
{
stop = true;
var battleData = snatcher.BattleData;
if (battleData == null)
return;
var moveChoice = new MoveChoice(snatcher, move.MoveChoice.ChosenMove, battleData.SideIndex,
battleData.Position);
var executingMove = new ExecutingMoveImpl([snatcher], move.NumberOfHits, move.ChosenMove, move.UseMove,
moveChoice, move.Battle);
// ExecuteMove will once again call StopBeforeMove, which will try to pass the move to the next snatcher
// if one exists.
MoveTurnExecutor.ExecuteMove(executingMove);
}
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
RemoveSelf();
}
}

View File

@@ -15,4 +15,6 @@ public static class CustomTriggers
public static readonly StringKey LightScreenNumberOfTurns = "light_screen_number_of_turns";
public static readonly StringKey ReflectNumberOfTurns = "reflect_number_of_turns";
public static readonly StringKey BypassSleep = "bypass_sleep";
}

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
[Script(ScriptCategory.MoveVolatile, "bypass_sleep")]
public class BypassSleepVolatile : Script
{
/// <inheritdoc />
public override void CustomTrigger(StringKey eventName, IDictionary<StringKey, object?>? parameters)
{
if (eventName == CustomTriggers.BypassSleep && parameters != null)
parameters["bypass_sleep"] = true;
}
}

View File

@@ -0,0 +1,17 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "charge_move")]
public class ChargeMove : Script
{
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
var chargeMoveEffect = move.User.Volatile.Get<ChargeMoveEffect>();
if (chargeMoveEffect != null && chargeMoveEffect.MoveName == move.UseMove.Name)
return;
prevent = true;
move.User.Volatile.Add(new ChargeMoveEffect(move.UseMove.Name, move.User, move.MoveChoice.TargetSide,
move.MoveChoice.TargetPosition));
}
}

View File

@@ -1,11 +0,0 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "dragon_rage")]
public class DragonRage : Script
{
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
damage = 40;
}
}

View File

@@ -30,7 +30,9 @@ public class Gravity : Script
var flyEffect = ScriptUtils.ResolveName<ChargeFlyEffect>();
if (pokemon.Volatile.Contains(flyEffect))
pokemon.Volatile.Remove(flyEffect);
// TODO: Sky Drop
var skyDropEffect = ScriptUtils.ResolveName<ChargeSkyDropEffect>();
if (pokemon.Volatile.Contains(skyDropEffect))
pokemon.Volatile.Remove(skyDropEffect);
}
}
}

View File

@@ -24,7 +24,7 @@ public class GuardSplit : Script
userStats.SetStatistic(Statistic.SpecialDefense, newSpecialDefense);
targetStats.SetStatistic(Statistic.Defense, newDefense);
targetStats.SetStatistic(Statistic.SpecialDefense, newSpecialDefense);
user.RecalculateFlatStats();
target.RecalculateFlatStats();
user.RecalculateBoostedStats();
target.RecalculateBoostedStats();
}
}

View File

@@ -24,7 +24,7 @@ public class PowerSplit : Script
userStats.SetStatistic(Statistic.SpecialAttack, newSpecialAttack);
targetStats.SetStatistic(Statistic.Attack, newAttack);
targetStats.SetStatistic(Statistic.SpecialAttack, newSpecialAttack);
user.RecalculateFlatStats();
target.RecalculateFlatStats();
user.RecalculateBoostedStats();
target.RecalculateBoostedStats();
}
}

View File

@@ -18,7 +18,7 @@ public class Rest : Script
move.GetHitData(target, hit).Fail();
return;
}
move.User.SetStatus(ScriptUtils.ResolveName<Sleep>());
((Sleep)move.User.StatusScript.Script!).Turns = 2;
if (move.User.SetStatus(ScriptUtils.ResolveName<Sleep>()) && move.User.StatusScript.Script is Sleep sleep)
sleep.Turns = 2;
}
}

View File

@@ -0,0 +1,24 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "skull_bash")]
public class SkullBash : Script
{
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
if (move.User.Volatile.Contains<SkullBashEffect>())
return;
move.User.ChangeStatBoost(Statistic.Defense, 1, true);
move.User.Volatile.Add(new SkullBashEffect(move.User));
prevent = true;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.User.Volatile.Remove<SkullBashEffect>();
}
}

View File

@@ -0,0 +1,27 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "sky_attack")]
public class SkyAttack : Script
{
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
if (move.User.Volatile.Contains<SkyAttackEffect>())
return;
move.User.Volatile.Add(new SkyAttackEffect(move.User));
prevent = true;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.User.Volatile.Remove<SkyAttackEffect>();
if (move.Battle.Random.EffectChance(30, move, target, hit))
{
target.Volatile.Add(new FlinchEffect());
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "sky_drop")]
public class SkyDrop : Script
{
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
if (move.User.Volatile.Contains<ChargeSkyDropEffect>())
return;
move.User.Volatile.Add(new ChargeSkyDropEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("sky_drop_charge", new Dictionary<string, object>
{
{ "user", move.User },
}));
prevent = true;
}
/// <inheritdoc />
public override void OnBeforeMove(IExecutingMove move)
{
move.User.Volatile.Remove(ScriptUtils.ResolveName<ChargeSkyDropEffect>());
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Linq;
using PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
using PkmnLib.Plugin.Gen7.Scripts.Utils;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "sleep_talk")]
public class SleepTalk : Script
{
/// <inheritdoc />
public override void ChangeMove(IMoveChoice choice, ref StringKey moveName)
{
if (!choice.User.HasStatus(ScriptUtils.ResolveName<Status.Sleep>()))
{
choice.Fail();
return;
}
var battleData = choice.User.BattleData;
if (battleData == null)
return;
var moves = choice.User.Moves.WhereNotNull().Where(x => x.MoveData.CanCopyMove())
.Where(x => x != choice.ChosenMove).OrderBy(_ => battleData.Battle.Random.GetInt()).FirstOrDefault();
if (moves == null)
{
choice.Fail();
return;
}
moveName = moves.MoveData.Name;
choice.Volatile.Add(new BypassSleepVolatile());
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "smack_down")]
public class SmackDown : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = target.BattleData;
if (battleData == null)
return;
target.Volatile.Add(new SmackDownEffect());
var chargeBounceEffect = ScriptUtils.ResolveName<ChargeBounceEffect>();
if (target.Volatile.Contains(chargeBounceEffect))
target.Volatile.Remove(chargeBounceEffect);
var flyEffect = ScriptUtils.ResolveName<ChargeFlyEffect>();
if (target.Volatile.Contains(flyEffect))
target.Volatile.Remove(flyEffect);
var skyDropEffect = ScriptUtils.ResolveName<ChargeSkyDropEffect>();
if (target.Volatile.Contains(skyDropEffect))
target.Volatile.Remove(skyDropEffect);
}
}

View File

@@ -0,0 +1,25 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "smelling_salts")]
public class SmellingSalts : Script
{
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
// If the target is paralyzed, double the damage
if (target.HasStatus(ScriptUtils.ResolveName<Status.Paralyzed>()))
{
damage *= 2;
}
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
// If the target is paralyzed, remove the paralysis
if (target.HasStatus(ScriptUtils.ResolveName<Status.Paralyzed>()))
{
target.ClearStatus();
}
}
}

View File

@@ -0,0 +1,15 @@
using PkmnLib.Plugin.Gen7.Scripts.Battle;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "snatch")]
public class Snatch : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.Battle.Volatile.Add(new SnatchEffect());
var snatchEffect = move.Battle.Volatile.Get<SnatchEffect>();
snatchEffect?.AddSnatcher(target);
}
}

View File

@@ -0,0 +1,20 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "snore")]
public class Snore : Script
{
/// <inheritdoc />
public override void FailMove(IExecutingMove move, ref bool fail)
{
if (!move.User.HasStatus(ScriptUtils.ResolveName<Status.Sleep>()))
fail = true;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new FlinchEffect());
}
}

View File

@@ -0,0 +1,22 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "soak")]
public class Soak : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (target.ActiveAbility?.Name == "multitype")
{
move.GetHitData(target, hit).Fail();
return;
}
var typeLibrary = move.Battle.Library.StaticLibrary.Types;
// If water type is not found, we can't do anything.
if (!typeLibrary.TryGetTypeIdentifier("water", out var waterType))
return;
target.SetTypes([waterType]);
}
}

View File

@@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "sparkling_aria")]
public class SparklingAria : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (target.HasStatus(ScriptUtils.ResolveName<Status.Burned>()))
target.ClearStatus();
}
}

View File

@@ -0,0 +1,22 @@
using System.Linq;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "spectral_thief")]
public class SpectralThief : Script
{
/// <inheritdoc />
public override void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex)
{
var positiveStats = target.StatBoost.Where(x => x.value > 0).ToArray();
if (positiveStats.Length > 0)
{
EventBatchId batchId = new();
foreach (var positiveStat in positiveStats)
{
move.User.ChangeStatBoost(positiveStat.statistic, positiveStat.value, true, batchId);
target.ChangeStatBoost(positiveStat.statistic, (sbyte)-positiveStat.value, true, batchId);
}
}
}
}

View File

@@ -0,0 +1,19 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "speed_swap")]
public class SpeedSwap : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var userSpeed = move.User.FlatStats.Speed;
var targetSpeed = target.FlatStats.Speed;
move.User.FlatStats.SetStatistic(Statistic.Speed, targetSpeed);
target.FlatStats.SetStatistic(Statistic.Speed, userSpeed);
move.User.RecalculateBoostedStats();
target.RecalculateBoostedStats();
}
}

View File

@@ -0,0 +1,15 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "spider_web")]
public class SpiderWeb : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.User.Volatile.Add(new SpiderWebEffect());
var effect = move.User.Volatile.Get<SpiderWebEffect>();
effect?.AddTarget(target);
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "static_damage")]
public class StaticDamage : Script
{
private uint Damage { get; set; }
/// <inheritdoc />
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
{
if (parameters == null)
throw new Exception("Parameters cannot be null for StaticDamage script.");
if (parameters.TryGetValue("damage", out var damage))
{
if (damage is int d)
Damage = (uint)d;
else
throw new Exception($"Invalid damage value: {damage}");
}
else
{
throw new Exception("Missing required parameter: damage");
}
}
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
damage = Damage;
}
}

View File

@@ -0,0 +1,35 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "charge_sky_drop")]
public class ChargeSkyDropEffect : Script
{
private readonly IPokemon _owner;
public ChargeSkyDropEffect(IPokemon owner)
{
_owner = owner;
}
/// <inheritdoc />
public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{
var opposingSideIndex = (byte)(_owner.BattleData?.SideIndex == 0 ? 1 : 0);
choice = TurnChoiceHelper.CreateMoveChoice(_owner, "sky_drop", opposingSideIndex, position);
}
/// <inheritdoc />
public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
if (!executingMove.UseMove.HasFlag("hit_flying"))
block = true;
}
/// <inheritdoc />
public override void ChangeIncomingMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
if (!move.UseMove.HasFlag("effective_against_fly"))
damage *= 2;
}
}

View File

@@ -0,0 +1,20 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "skull_bash")]
public class SkullBashEffect : Script
{
private readonly IPokemon _owner;
public SkullBashEffect(IPokemon owner)
{
_owner = owner;
}
/// <inheritdoc />
public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{
choice = TurnChoiceHelper.CreateMoveChoice(_owner, "skull_bash", sideIndex, position);
}
}

View File

@@ -0,0 +1,20 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "sky_attack")]
public class SkyAttackEffect : Script
{
private readonly IPokemon _owner;
public SkyAttackEffect(IPokemon owner)
{
_owner = owner;
}
/// <inheritdoc />
public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{
choice = TurnChoiceHelper.CreateMoveChoice(_owner, "sky_attack", sideIndex, position);
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "smack_down")]
public class SmackDownEffect : Script
{
/// <inheritdoc />
public override void ChangeTypesForIncomingMove(IExecutingMove executingMove, IPokemon target, byte hitIndex,
IList<TypeIdentifier> types)
{
var typeLibrary = target.Library.StaticLibrary.Types;
if (executingMove.UseMove.MoveType.Name != "ground")
return;
// Remove all types that are immune to ground moves
types.RemoveAll(x => typeLibrary.GetSingleEffectiveness(executingMove.UseMove.MoveType, x) == 0);
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "spider_web_effect")]
public class SpiderWebEffect : Script
{
private HashSet<IPokemon> _targets = new();
/// <inheritdoc />
public override void PreventOpponentRunAway(IFleeChoice choice, ref bool prevent)
{
if (_targets.Contains(choice.User))
prevent = true;
}
/// <inheritdoc />
public override void PreventOpponentSwitch(ISwitchChoice choice, ref bool prevent)
{
if (_targets.Contains(choice.User))
prevent = true;
}
public void AddTarget(IPokemon target)
{
_targets.Add(target);
}
}

View File

@@ -1,7 +1,59 @@
using System;
using System.Collections.Generic;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Status;
[Script(ScriptCategory.Status, "sleep")]
public class Sleep : Script
{
public int Turns { get; set; }
private IPokemon? _owner;
/// <inheritdoc />
public override void OnAddedToParent(IScriptSource source)
{
// Rare case where the script is added again. Can happen for example when baton pass is used.
if (Turns != 0)
return;
if (source is not IPokemon pokemon)
throw new InvalidOperationException("Sleep script can only be added to a Pokemon.");
_owner = pokemon;
var battleData = pokemon.BattleData;
if (battleData != null)
{
// 1-3 turns of sleep
Turns = battleData.Battle.Random.GetInt(1, 4);
}
}
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
Turns--;
if (Turns <= 0)
{
RemoveSelf();
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("pokemon_woke_up",
new Dictionary<string, object>
{
{ "pokemon", move.User },
}));
return;
}
if (move.UseMove.HasFlag("usable_while_asleep"))
return;
var bypass = false;
var pars = new Dictionary<StringKey, object?>
{
{ "bypass_sleep", bypass },
};
move.RunScriptHook(x => x.CustomTrigger(CustomTriggers.BypassSleep, pars));
bypass = pars.GetValueOrDefault("bypass_sleep", false) as bool? ?? false;
if (bypass)
return;
prevent = true;
}
}

View File

@@ -6,7 +6,7 @@ using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Weather;
[Script(ScriptCategory.Weather, "hail")]
public class Hail : Script, IWeatherScript
public class Hail : Script, ILimitedTurnsScript
{
private int? _duration;