Implements a bunch more moves
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-05-17 17:44:15 +02:00
parent ecabe2fd10
commit a17cb92c5a
62 changed files with 1180 additions and 81 deletions

View File

@@ -83,4 +83,12 @@ public class ChangeTargetAccuracy : ChangeTargetStats
public ChangeTargetAccuracy() : base(Statistic.Accuracy)
{
}
}
[Script(ScriptCategory.Move, "change_target_evasion")]
public class ChangeTargetEvasion : ChangeTargetStats
{
public ChangeTargetEvasion() : base(Statistic.Evasion)
{
}
}

View File

@@ -12,7 +12,7 @@ public class Dive : Script
if (move.User.Volatile.Contains<DiveEffect>())
return;
move.User.Volatile.Add(new DigEffect(move.User));
move.User.Volatile.Add(new DiveEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dive_charge", new Dictionary<string, object>
{
{ "user", move.User },

View File

@@ -22,17 +22,21 @@ public class Gravity : Script
return script;
});
var chargeBounceEffect = ScriptUtils.ResolveName<ChargeBounceEffect>();
var flyEffect = ScriptUtils.ResolveName<ChargeFlyEffect>();
var skyDropEffect = ScriptUtils.ResolveName<ChargeSkyDropEffect>();
var telekinesisEffect = ScriptUtils.ResolveName<TelekinesisEffect>();
foreach (var pokemon in battleData.Battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull())
{
var chargeBounceEffect = ScriptUtils.ResolveName<ChargeBounceEffect>();
if (pokemon.Volatile.Contains(chargeBounceEffect))
pokemon.Volatile.Remove(chargeBounceEffect);
var flyEffect = ScriptUtils.ResolveName<ChargeFlyEffect>();
if (pokemon.Volatile.Contains(flyEffect))
pokemon.Volatile.Remove(flyEffect);
var skyDropEffect = ScriptUtils.ResolveName<ChargeSkyDropEffect>();
if (pokemon.Volatile.Contains(skyDropEffect))
pokemon.Volatile.Remove(skyDropEffect);
if (pokemon.Volatile.Contains(telekinesisEffect))
pokemon.Volatile.Remove(telekinesisEffect);
}
}
}

View File

@@ -1,5 +1,5 @@
using System.Linq;
using PkmnLib.Dynamic.Models.BattleFlow;
using PkmnLib.Dynamic.BattleFlow;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;

View File

@@ -17,5 +17,6 @@ public class SpitUp : Script
}
var stockpileCount = stockpileEffect.StockpileCount;
basePower = basePower.MultiplyOrMax(stockpileCount);
move.User.Volatile.Remove<StockpileEffect>();
}
}

View File

@@ -8,10 +8,6 @@ public class Stockpile : Script
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
EventBatchId batchId = new();
move.User.ChangeStatBoost(Statistic.Defense, 1, true, batchId);
move.User.ChangeStatBoost(Statistic.SpecialDefense, 1, true, batchId);
move.User.Volatile.StackOrAdd(ScriptUtils.ResolveName<StockpileEffect>(), () => new StockpileEffect());
}
}

View File

@@ -0,0 +1,27 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
/// <remarks>
/// Named 'SubstituteMove' to avoid namespace conflicts with NSubstitute.
/// </remarks>
[Script(ScriptCategory.Move, "substitute")]
public class SubstituteMove : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var hp = move.User.MaxHealth / 4;
if (move.User.CurrentHealth > hp)
{
move.User.Damage(hp, DamageSource.Misc, forceDamage: true);
move.User.Volatile.Add(new Pokemon.SubstituteEffect(hp));
move.Battle.EventHook.Invoke(new DialogEvent("substitute", new Dictionary<string, object>
{
{ "user", move.User },
}));
}
else
{
move.Battle.EventHook.Invoke(new DialogEvent("substitute_fail"));
}
}
}

View File

@@ -0,0 +1,18 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "sucker_punch")]
public class SuckerPunch : Script
{
/// <inheritdoc />
public override void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex)
{
var targetChoice = move.Battle.ChoiceQueue?.Where(x => x.User == target).FirstOrDefault();
if (targetChoice is not IMoveChoice moveChoice ||
moveChoice.ChosenMove.MoveData.Category == MoveCategory.Status)
{
move.GetHitData(target, hitIndex).Fail();
}
}
}

View File

@@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "super_fang")]
public class SuperFang : Script
{
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
// Super Fang always does 50% of the target's current HP
damage = target.CurrentHealth / 2;
}
}

View File

@@ -0,0 +1,13 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "superpower")]
public class Superpower : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
EventBatchId eventBatch = new();
move.User.ChangeStatBoost(Statistic.Attack, -1, true, eventBatch);
move.User.ChangeStatBoost(Statistic.Defense, -1, true, eventBatch);
}
}

View File

@@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "swagger")]
public class Swagger : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.ChangeStatBoost(Statistic.Attack, 2, false);
target.Volatile.Add(new Pokemon.Confusion());
}
}

View File

@@ -0,0 +1,29 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "swallow")]
public class Swallow : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var stockpileEffect = move.User.Volatile.Get<StockpileEffect>();
if (stockpileEffect == null || stockpileEffect.StockpileCount == 0)
{
move.GetHitData(target, hit).Fail();
return;
}
var stockpileCount = stockpileEffect.StockpileCount;
var modifier = stockpileCount switch
{
1 => 0.25f,
2 => 0.5f,
_ => 1f,
};
var heal = move.User.MaxHealth * modifier;
move.User.Heal((uint)heal);
move.User.Volatile.Remove<StockpileEffect>();
}
}

View File

@@ -0,0 +1,24 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "switcheroo")]
public class Switcheroo : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var targetHeldItem = target.HeldItem;
var userHeldItem = move.User.HeldItem;
if (targetHeldItem?.Category is ItemCategory.FormChanger or ItemCategory.Mail ||
userHeldItem?.Category is ItemCategory.FormChanger or ItemCategory.Mail)
{
// Cannot switch items if one of them is a form changer or mail
move.GetHitData(target, hit).Fail();
return;
}
targetHeldItem = target.RemoveHeldItem();
userHeldItem = move.User.RemoveHeldItem();
_ = target.SetHeldItem(userHeldItem);
_ = move.User.SetHeldItem(targetHeldItem);
}
}

View File

@@ -0,0 +1,18 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "synchronoise")]
public class Synchronoise : Script
{
/// <inheritdoc />
public override void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList<IPokemon?> targets)
{
var battleData = moveChoice.User.BattleData;
if (battleData == null)
throw new InvalidOperationException("Battle data is null.");
targets = battleData.Battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull()
.Where(x => x.Types.Any(y => moveChoice.User.Types.Contains(y))).ToList();
}
}

View File

@@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "synthesis")]
public class Synthesis : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var healModifier = 0.5f;
var weatherName = target.BattleData?.Battle?.WeatherName;
if (weatherName == ScriptUtils.ResolveName<Weather.Sunny>())
healModifier = 2 / 3f;
else if (weatherName == ScriptUtils.ResolveName<Weather.Rain>() ||
weatherName == ScriptUtils.ResolveName<Weather.Hail>() ||
weatherName == ScriptUtils.ResolveName<Weather.Sandstorm>())
healModifier = 1 / 4f;
move.User.Heal((uint)(move.User.MaxHealth * healModifier));
}
}

View File

@@ -0,0 +1,11 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "tailwind")]
public class Tailwind : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.User.BattleData?.BattleSide.VolatileScripts.Add(new Side.TailwindEffect());
}
}

View File

@@ -0,0 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "taunt")]
public class Taunt : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new TauntEffect(4));
}
}

View File

@@ -0,0 +1,23 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "techno_blast")]
public class TechnoBlast : Script
{
/// <inheritdoc />
public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier? moveType)
{
var heldItem = move.User.HeldItem;
if (heldItem == null)
return;
var typeLibrary = target.Library.StaticLibrary.Types;
moveType = heldItem.Name.ToString().ToLowerInvariant() switch
{
"burn_drive" when typeLibrary.TryGetTypeIdentifier("fire", out var fire) => fire,
"chill_drive" when typeLibrary.TryGetTypeIdentifier("ice", out var ice) => ice,
"douse_drive" when typeLibrary.TryGetTypeIdentifier("water", out var water) => water,
"shock_drive" when typeLibrary.TryGetTypeIdentifier("electric", out var electric) => electric,
_ => moveType,
};
}
}

View File

@@ -0,0 +1,19 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "telekinesis")]
public class Telekinesis : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.Battle.Volatile.Contains<Battle.Gravity>())
{
move.GetHitData(target, hit).Fail();
return;
}
target.Volatile.Add(new TelekinesisEffect());
}
}

View File

@@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "teleport")]
public class Teleport : Script
{
// FIXME: Implement teleport
}

View File

@@ -0,0 +1,23 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "thousand_arrows")]
public class ThousandArrows : Script
{
/// <inheritdoc />
public override void ChangeEffectiveness(IExecutingMove move, IPokemon target, byte hit, ref float effectiveness)
{
if (effectiveness == 0)
effectiveness = 1;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new ThousandArrowsEffect());
target.Volatile.Remove<ChargeFlyEffect>();
target.Volatile.Remove<ChargeSkyDropEffect>();
target.Volatile.Remove<ChargeBounceEffect>();
}
}

View File

@@ -0,0 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "thousand_waves")]
public class ThousandWaves : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new ThousandWavesEffect());
}
}

View File

@@ -0,0 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "throat_chop")]
public class ThroatChop : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.Volatile.Add(new ThroatChopEffect());
}
}

View File

@@ -0,0 +1,22 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Plugin.Gen7.Scripts.Status;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "thunder_fang")]
public class ThunderFang : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var random = move.Battle.Random;
if (random.EffectChance(10, move, target, hit))
{
target.SetStatus(ScriptUtils.ResolveName<Paralyzed>());
}
if (random.EffectChance(10, move, target, hit))
{
target.Volatile.Add(new FlinchEffect());
}
}
}

View File

@@ -0,0 +1,26 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "topsy_turvy")]
public class TopsyTurvy : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
EventBatchId batchId = new();
var hasChanged = false;
foreach (Statistic stat in Enum.GetValues(typeof(Statistic)))
{
var statBoost = target.StatBoost.GetStatistic(stat);
if (statBoost == 0)
continue;
hasChanged = true;
var newStatBoost = -statBoost;
target.ChangeStatBoost(stat, (sbyte)newStatBoost, target == move.User, batchId);
}
if (!hasChanged)
{
move.GetHitData(target, hit).Fail();
}
}
}

View File

@@ -0,0 +1,13 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "torment")]
public class Torment : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var lastTargetChoice = move.Battle.PreviousTurnChoices.SelectMany(x => x).Reverse().OfType<IMoveChoice>()
.FirstOrDefault(x => x.User == target);
target.Volatile.Add(new Pokemon.TormentEffect(lastTargetChoice));
}
}

View File

@@ -0,0 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Side;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "toxic_spikes")]
public class ToxicSpikes : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.BattleData?.Battle.Volatile.Add(new ToxicSpikesEffect());
}
}

View File

@@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "toxic_thread")]
public class ToxicThread : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>());
target.ChangeStatBoost(Statistic.Speed, -1, false);
}
}

View File

@@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "transform")]
public class Transform : Script
{
// FIXME: implement this
}

View File

@@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "tri_attack")]
public class TriAttack : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var random = move.Battle.Random;
if (!random.EffectChance(20, move, target, hit))
return;
var status = random.OneOf([
ScriptUtils.ResolveName<Status.Burned>(),
ScriptUtils.ResolveName<Status.Paralyzed>(),
ScriptUtils.ResolveName<Status.Frozen>(),
]);
target.SetStatus(status);
}
}

View File

@@ -0,0 +1,14 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "trick_or_treat")]
public class TrickOrTreat : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var library = move.Battle.Library.StaticLibrary.Types;
if (!library.TryGetTypeIdentifier("ghost", out var ghostType))
return;
target.AddType(ghostType);
}
}

View File

@@ -0,0 +1,13 @@
using PkmnLib.Plugin.Gen7.Scripts.Battle;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "trick_room")]
public class TrickRoom : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
move.Battle.Volatile.Add(new TrickRoomEffect());
}
}

View File

@@ -0,0 +1,17 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "triple_kick")]
public class TripleKick : Script
{
/// <inheritdoc />
public override void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits)
{
numberOfHits = 3;
}
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
basePower *= (ushort)(hit + 1);
}
}

View File

@@ -0,0 +1,20 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "trump_card")]
public class TrumpCard : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref ushort basePower)
{
var remainingPp = move.ChosenMove.CurrentPp;
basePower = remainingPp switch
{
>= 4 => 40,
3 => 50,
2 => 60,
1 => 80,
0 => 200,
};
}
}

View File

@@ -0,0 +1,17 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "twineedle")]
public class Twineedle : Script
{
/// <inheritdoc />
public override void ChangeNumberOfHits(IMoveChoice choice, ref byte numberOfHits) => numberOfHits = 2;
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.Battle.Random.EffectChance(20, move, target, hit))
{
target.SetStatus(ScriptUtils.ResolveName<Status.Poisoned>());
}
}
}

View File

@@ -0,0 +1,12 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "u_turn")]
public class UTurn : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
battleData?.BattleSide.SwapPokemon(battleData.Position, null);
}
}

View File

@@ -0,0 +1,18 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "venom_drench")]
public class VenomDrench : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (!target.HasStatus(ScriptUtils.ResolveName<Status.Poisoned>()) &&
!target.HasStatus(ScriptUtils.ResolveName<Status.BadlyPoisoned>()))
return;
EventBatchId eventBatch = new();
target.ChangeStatBoost(Statistic.Attack, -1, false, eventBatch);
target.ChangeStatBoost(Statistic.SpecialAttack, -1, false, eventBatch);
target.ChangeStatBoost(Statistic.Speed, -1, false, eventBatch);
}
}

View File

@@ -0,0 +1,15 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "venoshock")]
public class Venoshock : Script
{
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
if (target.HasStatus(ScriptUtils.ResolveName<Status.Poisoned>()) ||
target.HasStatus(ScriptUtils.ResolveName<Status.BadlyPoisoned>()))
{
damage *= 2;
}
}
}