More move scripts

This commit is contained in:
2025-05-03 16:51:44 +02:00
parent f8c43b6ba0
commit 1973ff50fa
52 changed files with 704 additions and 78 deletions

View File

@@ -54,8 +54,8 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator
byte moveAccuracy)
{
var accuracyModifier = 1.0f;
executingMove.RunScriptHook(
x => x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier));
executingMove.RunScriptHook(x =>
x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier));
var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier);
// ReSharper disable once AccessToModifiedClosure
executingMove.RunScriptHook(x => x.ChangeAccuracy(executingMove, target, hitIndex, ref modifiedAccuracy));

View File

@@ -12,7 +12,7 @@ public class Gen7MiscLibrary : IMiscLibrary
{
private readonly IMoveData _struggleData = new MoveDataImpl("struggle", new TypeIdentifier(0, "none"),
MoveCategory.Physical, 50, 255, 255, MoveTarget.Any, 0,
new SecondaryEffectImpl(-1, "struggle", new Dictionary<StringKey, object?>()), []);
new SecondaryEffectImpl(-1, "struggle", new Dictionary<StringKey, object?>()), ["not_sketchable"]);
/// <inheritdoc />
public ITurnChoice ReplacementChoice(IPokemon user, byte targetSide, byte targetPosition) =>

View File

@@ -4,12 +4,11 @@
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj" />
<ProjectReference Include="..\..\PkmnLib.Static\PkmnLib.Static.csproj" />
<ProjectReference Include="..\..\PkmnLib.Dynamic\PkmnLib.Dynamic.csproj"/>
<ProjectReference Include="..\..\PkmnLib.Static\PkmnLib.Static.csproj"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
[Script(ScriptCategory.MoveVolatile, "round")]
public class RoundPowerBoost : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) =>
basePower = basePower.MultiplyOrMax(2f);
}

View File

@@ -13,7 +13,7 @@ public abstract class BaseChargeMove<TVolatile> : Script where TVolatile : Requi
return;
move.User.Volatile.Add(CreateVolatile(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("began_charging", new Dictionary<string, object>()
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("began_charging", new Dictionary<string, object>
{
{ "user", move.User },
}));

View File

@@ -13,7 +13,7 @@ public class BeakBlast : Script
if (battleData == null)
return;
choice.User.Volatile.Add(new BeakBlastEffect());
battleData.Battle.EventHook.Invoke(new DialogEvent("beak_blast_charge", new Dictionary<string, object>()
battleData.Battle.EventHook.Invoke(new DialogEvent("beak_blast_charge", new Dictionary<string, object>
{
{ "user", choice.User },
}));

View File

@@ -13,7 +13,7 @@ public class Bounce : Script
return;
move.User.Volatile.Add(new ChargeBounceEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("bounce_charge", new Dictionary<string, object>()
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("bounce_charge", new Dictionary<string, object>
{
{ "user", move.User },
}));

View File

@@ -3,5 +3,5 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "camouflage")]
public class Camouflage : Script
{
// FIXME: Implement this. How to get the terrain in a sane manner?
// FIXME: Implement this. How to get the terrain in a sane manner? See also SecretPower.cs
}

View File

@@ -1,4 +1,6 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using PkmnLib.Plugin.Gen7.Scripts.Side;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
@@ -6,16 +8,16 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "defog")]
public class Defog : Script
{
public static HashSet<StringKey> DefoggedEffects = new()
{
"mist",
"light_screen",
"reflect",
"safe_guard",
[PublicAPI] public static HashSet<StringKey> DefoggedEffects =
[
ScriptUtils.ResolveName<MistEffect>(),
ScriptUtils.ResolveName<LightScreenEffect>(),
ScriptUtils.ResolveName<ReflectEffect>(),
ScriptUtils.ResolveName<SafeguardEffect>(),
"spikes",
"toxic_spikes",
"stealth_rock",
};
];
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)

View File

@@ -13,7 +13,7 @@ public class Dig : Script
return;
move.User.Volatile.Add(new DigEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dig_charge", new Dictionary<string, object>()
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dig_charge", new Dictionary<string, object>
{
{ "user", move.User },
}));

View File

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

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "flame_wheel")]
public class FlameWheel : Script
{
private float _burnChance;
/// <inheritdoc />
public override void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
{
if (parameters is null || !parameters.TryGetValue("burn_chance", out var burnChance) ||
burnChance is not float chance)
{
throw new Exception("Flame Wheel: Missing or invalid parameters.");
}
_burnChance = chance;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.HasStatus("frozen"))
{
move.User.ClearStatus();
}
var burnChance = _burnChance;
if (move.Battle.Random.EffectChance(_burnChance, move, target, hit))
{
target.SetStatus("burned");
}
}
}

View File

@@ -13,7 +13,7 @@ public class Fly : Script
return;
move.User.Volatile.Add(new ChargeFlyEffect(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("fly_charge", new Dictionary<string, object>()
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("fly_charge", new Dictionary<string, object>
{
{ "user", move.User },
}));

View File

@@ -11,7 +11,7 @@ public class FocusPunch : Script
{
choice.User.Volatile.Add(new FocusPunchEffect());
choice.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("focus_punch_charge",
new Dictionary<string, object>()
new Dictionary<string, object>
{
{ "pokemon", choice.User },
}));

View File

@@ -15,7 +15,7 @@ public class LightScreen : Script
return;
var turns = 5;
var dict = new Dictionary<StringKey, object?>()
var dict = new Dictionary<StringKey, object?>
{
{ "duration", turns },
};

View File

@@ -32,7 +32,7 @@ public class Magnitude : Script
// 5% chance for 10
_ => 10,
};
battleData.Battle.EventHook.Invoke(new DialogEvent("magnitude", new Dictionary<string, object>()
battleData.Battle.EventHook.Invoke(new DialogEvent("magnitude", new Dictionary<string, object>
{
{ "magnitude", magnitude },
{ "user", move.User },

View File

@@ -9,6 +9,11 @@ public class MeanLook : Script
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var targetEffect = target.Volatile.Add(new MeanLookEffectTarget());
if (targetEffect == null)
{
move.GetHitData(target, hit).Fail();
return;
}
var userEffect = new MeanLookEffectUser(targetEffect);
move.User.Volatile.Add(userEffect);
}

View File

@@ -13,6 +13,12 @@ public class RagePowder : Script
return;
var effect = battleData.BattleSide.VolatileScripts.Add(new RagePowderEffect(move.User));
if (effect == null)
{
move.GetHitData(target, hit).Fail();
return;
}
((RagePowderEffect)effect.Script!).User = move.User;
}
}

View File

@@ -0,0 +1,29 @@
using System.Linq;
using PkmnLib.Plugin.Gen7.Scripts.MoveVolatile;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "round")]
public class Round : Script
{
/// <inheritdoc />
public override void OnAfterMove(IExecutingMove move)
{
var choiceQueue = move.Battle.ChoiceQueue;
if (choiceQueue is null)
{
return;
}
var otherRoundMoves = choiceQueue.Where(x => x is IMoveChoice mc && mc.ChosenMove.MoveData.Name == "round")
.Cast<IMoveChoice>().ToList();
// We reverse the order here, as we're constantly pushing the choices to the front of the queue.
// By reversing the order, we ensure that the first choice is the one that is up next.
foreach (var otherRoundMove in otherRoundMoves.AsEnumerable().Reverse())
{
otherRoundMove.Volatile.Add(new RoundPowerBoost());
choiceQueue.MovePokemonChoiceNext(otherRoundMove.User);
}
}
}

View File

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

View File

@@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "secret_power")]
public class SecretPower : Script
{
// FIXME: Implement this. How to get the terrain in a sane manner? See also Camouflage.cs
}

View File

@@ -0,0 +1,14 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "self_destruct")]
public class SelfDestruct : Script
{
/// <inheritdoc />
public override void OnAfterMove(IExecutingMove move)
{
if (move.User.IsFainted)
return;
move.User.Faint(DamageSource.Misc);
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "shadow_force")]
public class ShadowForce : Script
{
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
if (move.User.Volatile.Contains<ShadowForceCharge>())
return;
move.User.Volatile.Add(new ShadowForceCharge(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("shadow_force_charge",
new Dictionary<string, object>
{
{ "user", move.User },
}));
prevent = true;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.Volatile.Contains<ShadowForceCharge>())
return;
move.User.Volatile.Add(new ShadowForceCharge(move.User));
}
}

View File

@@ -0,0 +1,19 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "shell_smash")]
public class ShellSmash : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
EventBatchId eventBatchId = new();
move.User.ChangeStatBoost(Statistic.Attack, 2, true, eventBatchId);
move.User.ChangeStatBoost(Statistic.SpecialAttack, 2, true, eventBatchId);
move.User.ChangeStatBoost(Statistic.Speed, 2, true, eventBatchId);
eventBatchId = new EventBatchId();
move.User.ChangeStatBoost(Statistic.Defense, -1, true, eventBatchId);
move.User.ChangeStatBoost(Statistic.SpecialDefense, -1, true, eventBatchId);
}
}

View File

@@ -0,0 +1,23 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "shell_trap")]
public class ShellTrap : Script
{
/// <inheritdoc />
public override void OnBeforeMove(IExecutingMove move)
{
move.User.Volatile.Add(new ShellTrapHelper());
}
/// <inheritdoc />
public override void FailMove(IExecutingMove move, ref bool fail)
{
var shellTrapHelper = move.User.Volatile.Get<ShellTrapHelper>();
if (shellTrapHelper is not { HasHit: true })
{
fail = true;
}
}
}

View File

@@ -0,0 +1,19 @@
using PkmnLib.Plugin.Gen7.Scripts.Weather;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "shore_up")]
public class ShoreUp : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var healMod = 0.5f;
if (move.Battle.WeatherName == ScriptUtils.ResolveName<Sandstorm>())
{
healMod = 2f / 3f;
}
var heal = (uint)(target.MaxHealth * healMod);
target.Heal(heal);
}
}

View File

@@ -0,0 +1,16 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "simple_beam")]
public class SimpleBeam : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (!move.Battle.Library.StaticLibrary.Abilities.TryGet("simple", out var simpleAbility))
{
move.GetHitData(target, hit).Fail();
return;
}
target.ChangeAbility(simpleAbility);
}
}

View File

@@ -0,0 +1,37 @@
using System.Linq;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "sketch")]
public class Sketch : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
// Defensive programming; If the move we're using is not the same as the one picked, something changed
// our move to sketch. This should never happen, as moves are not allowed to change to sketch, but in the
// case it does, we should fail the move.
if (move.ChosenMove.MoveData != move.UseMove)
{
move.GetHitData(target, hit).Fail();
return;
}
var moveSlot = move.User.Moves.IndexOf(move.ChosenMove);
if (moveSlot == -1)
{
move.GetHitData(target, hit).Fail();
return;
}
var choiceQueue = move.Battle.PreviousTurnChoices;
var lastMove = choiceQueue.SelectMany(x => x).OfType<IMoveChoice>().LastOrDefault(x => x.User == target);
if (lastMove == null || lastMove.ChosenMove.MoveData.HasFlag("not_sketchable"))
{
move.GetHitData(target, hit).Fail();
return;
}
move.User.LearnMove(lastMove.ChosenMove.MoveData.Name, MoveLearnMethod.Sketch, (byte)moveSlot);
}
}

View File

@@ -0,0 +1,25 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "skill_swap")]
public class SkillSwap : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var targetAbility = target.ActiveAbility;
var userAbility = move.User.ActiveAbility;
if (targetAbility == null || userAbility == null)
{
move.GetHitData(target, hit).Fail();
return;
}
if (targetAbility.HasFlag("cant_be_changed") || userAbility.HasFlag("cant_be_changed"))
{
move.GetHitData(target, hit).Fail();
return;
}
move.User.ChangeAbility(targetAbility);
target.ChangeAbility(userAbility);
}
}

View File

@@ -12,7 +12,7 @@ public class FocusPunchEffect : Script
{
WasHit = true;
target.BattleData?.Battle.EventHook.Invoke(new DialogEvent("focus_punch_lost_focus",
new Dictionary<string, object>()
new Dictionary<string, object>
{
{ "pokemon", target },
}));

View File

@@ -33,7 +33,7 @@ public abstract class OutrageLikeEffect : Script
if (_turns <= 0)
{
RemoveSelf();
_owner.Volatile.Add(new Confusion());
_owner.Volatile.Add(new Confusion(), true);
}
}
}

View File

@@ -23,7 +23,7 @@ public class RequiresRechargeEffect : Script
{
RemoveSelf();
_owner.BattleData?.Battle.EventHook.Invoke(new DialogEvent("pokemon_must_recharge",
new Dictionary<string, object>()
new Dictionary<string, object>
{
{ "pokemon", _owner },
}));

View File

@@ -0,0 +1,27 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "shadow_force")]
public class ShadowForceCharge : Script
{
private readonly IPokemon _owner;
public ShadowForceCharge(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, "shadow_force", opposingSideIndex, position);
}
/// <inheritdoc />
public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
block = true;
}
}

View File

@@ -0,0 +1,16 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "shell_trap")]
public class ShellTrapHelper : Script
{
public bool HasHit { get; private set; }
/// <inheritdoc />
public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
{
if (move.UseMove.Category == MoveCategory.Physical)
HasHit = true;
}
}

View File

@@ -0,0 +1,20 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Side;
public class SafeguardEffect : Script
{
/// <inheritdoc />
public override void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus)
{
preventStatus = true;
}
/// <inheritdoc />
public override void PreventVolatileAdd(Script script, ref bool preventVolatileAdd)
{
if (script.Category == ScriptCategory.Pokemon && script.Name == ScriptUtils.ResolveName<Confusion>())
preventVolatileAdd = true;
}
}

View File

@@ -32,11 +32,10 @@ public class Hail : Script, IWeatherScript
if (pokemon.Types.Contains(iceType))
continue;
var ignoresHail = false;
pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.IgnoreHail,
new Dictionary<StringKey, object?>()
{
{ "ignoresHail", ignoresHail },
}));
pokemon.RunScriptHook(x => x.CustomTrigger(CustomTriggers.IgnoreHail, new Dictionary<StringKey, object?>
{
{ "ignoresHail", ignoresHail },
}));
if (ignoresHail)
continue;