More moves

This commit is contained in:
2025-04-17 13:07:45 +02:00
parent 1b54c78b07
commit d02c05874b
31 changed files with 682 additions and 65 deletions

View File

@@ -133,11 +133,12 @@ public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator
if (bypassDefense)
targetStats = target.FlatStats;
var defensiveStat = targetStats.GetStatistic(defensive);
var origOffensiveStat = offensiveStat;
executingMove.RunScriptHook(script =>
script.ChangeOffensiveStatValue(executingMove, target, hitNumber, ref offensiveStat));
script.ChangeOffensiveStatValue(executingMove, target, hitNumber, defensiveStat, ref offensiveStat));
executingMove.RunScriptHook(script =>
script.ChangeDefensiveStatValue(executingMove, target, hitNumber, ref defensiveStat));
script.ChangeDefensiveStatValue(executingMove, target, hitNumber, origOffensiveStat, ref defensiveStat));
var modifier = (float)offensiveStat / defensiveStat;
executingMove.RunScriptHook(script =>

View File

@@ -6,7 +6,8 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
public class FoulPlay : Script
{
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, ref uint value)
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint _,
ref uint value)
{
value = move.UseMove.Category == MoveCategory.Physical
? target.BoostedStats.Attack

View File

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

View File

@@ -0,0 +1,28 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
/// <summary>
/// Implements the secondary effect of Parting Shot, which lowers the target's Attack and Special Attack by one stage each,
/// then forces the user to switch out if at least one stat change was successful.
/// </summary>
[Script(ScriptCategory.Move, "parting_shot")]
public class PartingShot : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
var evtBatch = new EventBatchId();
var attackChanged = target.ChangeStatBoost(Statistic.Attack, -1, false, evtBatch);
var specialAttackChanged = target.ChangeStatBoost(Statistic.SpecialAttack, -1, false, evtBatch);
if (attackChanged || specialAttackChanged)
{
battleData.BattleSide.SwapPokemon(battleData.Position, null);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "pay_day")]
public class PayDay : Script
{
// TODO: Implement the Pay Day move effect
}

View File

@@ -0,0 +1,23 @@
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
/// <summary>
/// Implements the secondary effect of Payback, which doubles the move's power if the user moves after the target.
/// </summary>
[Script(ScriptCategory.Move, "payback")]
public class Payback : Script
{
/// <inheritdoc />
public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
{
var battleData = move.User.BattleData;
// Check if the target has already moved this turn
if (battleData?.Battle.ChoiceQueue?.FirstOrDefault(x => x.User == target) != null)
{
basePower = basePower.MultiplyOrMax(2);
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Linq;
using PkmnLib.Static;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
/// <summary>
/// Implements the secondary effect of Perish Song, which causes all Pokemon on the field to faint in 3 turns.
/// </summary>
[Script(ScriptCategory.Move, "perish_song")]
public class PerishSong : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.Volatile.Contains<PerishSongEffect>())
return;
var battleData = move.User.BattleData;
if (battleData == null)
return;
// Add Perish Song volatile to all Pokémon on the field
foreach (var pokemon in battleData.Battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull())
{
pokemon.Volatile.Add(new PerishSongEffect(pokemon));
}
}
}

View File

@@ -0,0 +1,26 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
/// <summary>
/// Implements the secondary effect of Petal Dance, which forces the user to continue using the move for 2-3 turns,
/// then confuses the user.
/// </summary>
[Script(ScriptCategory.Move, "petal_dance")]
public class PetalDance : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.Volatile.Contains<PetalDanceEffect>())
return;
var battleData = move.User.BattleData;
if (battleData == null)
return;
var turns = battleData.Battle.Random.GetBool() ? 2 : 3;
move.User.Volatile.Add(new PetalDanceEffect(move.User, turns, move.MoveChoice.TargetSide,
move.MoveChoice.TargetPosition));
}
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
/// <summary>
/// Implements the secondary effect of Phantom Force, which makes the user semi-invulnerable on the first turn
/// and attacks on the second turn.
/// </summary>
[Script(ScriptCategory.Move, "phantom_force")]
public class PhantomForce : Script
{
/// <inheritdoc />
public override void PreventMove(IExecutingMove move, ref bool prevent)
{
if (move.User.Volatile.Contains<PhantomForceCharge>())
return;
move.User.Volatile.Add(new PhantomForceCharge(move.User));
move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("phantom_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<PhantomForceCharge>())
return;
move.User.Volatile.Add(new PhantomForceCharge(move.User));
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
using PkmnLib.Plugin.Gen7.Scripts.Status;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "poison_tail")]
public class PoisonTail : Script
{
/// <inheritdoc />
public override void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage)
{
if (stage == 255)
return;
stage += 1;
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
if (battleData == null)
return;
if (battleData.Battle.Random.EffectChance(10, move, target, hit))
{
target.SetStatus(ScriptUtils.ResolveName<BadlyPoisoned>());
}
}
}

View File

@@ -0,0 +1,39 @@
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "pollen_puff")]
public class PollenPuff : Script
{
/// <inheritdoc />
/// <inheritdoc />
public override void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category)
{
var battleData = move.User.BattleData;
var targetBattleData = target.BattleData;
if (battleData == null || targetBattleData == null)
return;
if (battleData.SideIndex == targetBattleData.SideIndex)
{
category = MoveCategory.Status;
}
}
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var battleData = move.User.BattleData;
var targetBattleData = target.BattleData;
if (battleData == null || targetBattleData == null)
return;
if (battleData.SideIndex == targetBattleData.SideIndex)
{
var maxHealth = target.MaxHealth;
target.Heal(maxHealth / 2);
}
}
}

View File

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

View File

@@ -0,0 +1,30 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "power_split")]
public class PowerSplit : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var user = move.User;
var userStats = user.FlatStats;
var targetStats = target.FlatStats;
var userAttack = userStats.GetStatistic(Statistic.Attack);
var targetAttack = targetStats.GetStatistic(Statistic.Attack);
var userSpecialAttack = userStats.GetStatistic(Statistic.SpecialAttack);
var targetSpecialAttack = targetStats.GetStatistic(Statistic.SpecialAttack);
var newAttack = (userAttack + targetAttack) / 2;
var newSpecialAttack = (userSpecialAttack + targetSpecialAttack) / 2;
userStats.SetStatistic(Statistic.Attack, newAttack);
userStats.SetStatistic(Statistic.SpecialAttack, newSpecialAttack);
targetStats.SetStatistic(Statistic.Attack, newAttack);
targetStats.SetStatistic(Statistic.SpecialAttack, newSpecialAttack);
user.RecalculateFlatStats();
target.RecalculateFlatStats();
}
}

View File

@@ -0,0 +1,26 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "power_swap")]
public class PowerSwap : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
var user = move.User;
var eventBatchId = new EventBatchId();
var userAttack = user.StatBoost.Attack;
var targetAttack = target.StatBoost.Attack;
var userSpecialAttack = user.StatBoost.SpecialAttack;
var targetSpecialAttack = target.StatBoost.SpecialAttack;
user.ChangeStatBoost(Statistic.Attack, (sbyte)(targetAttack - userAttack), true, eventBatchId);
user.ChangeStatBoost(Statistic.SpecialAttack, (sbyte)(targetSpecialAttack - userSpecialAttack), true,
eventBatchId);
target.ChangeStatBoost(Statistic.Attack, (sbyte)(userAttack - targetAttack), true, eventBatchId);
target.ChangeStatBoost(Statistic.SpecialAttack, (sbyte)(userSpecialAttack - targetSpecialAttack), true,
eventBatchId);
}
}

View File

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

View File

@@ -0,0 +1,26 @@
using System;
using System.Linq;
using PkmnLib.Static;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
[Script(ScriptCategory.Move, "power_trip")]
public class PowerTrip : Script
{
/// <inheritdoc />
public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage)
{
var modifier = 1;
foreach (Statistic stat in Enum.GetValues(typeof(Statistic)))
{
if (stat is Statistic.Accuracy or Statistic.Evasion)
continue;
var statChange = move.User.StatBoost.GetStatistic(stat);
if (statChange > 0)
modifier += statChange;
}
damage = damage.MultiplyOrMax(modifier);
}
}

View File

@@ -0,0 +1,26 @@
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
/// <summary>
/// Implements the secondary effect of Thrash, which forces the user to continue using the move for 2-3 turns,
/// then confuses the user.
/// </summary>
[Script(ScriptCategory.Move, "thrash")]
public class Thrash : Script
{
/// <inheritdoc />
public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
{
if (move.User.Volatile.Contains<ThrashEffect>())
return;
var battleData = move.User.BattleData;
if (battleData == null)
return;
var turns = battleData.Battle.Random.GetBool() ? 2 : 3;
move.User.Volatile.Add(new ThrashEffect(move.User, turns, move.MoveChoice.TargetSide,
move.MoveChoice.TargetPosition));
}
}

View File

@@ -1,37 +1,10 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "outrage")]
public class OutrageEffect : Script
public class OutrageEffect : OutrageLikeEffect
{
private readonly IPokemon _owner;
private int _turns;
private readonly byte _targetSide;
private readonly byte _targetPosition;
public OutrageEffect(IPokemon owner, int turns, byte targetSide, byte targetPosition)
public OutrageEffect(IPokemon owner, int turns, byte targetSide, byte targetPosition) : base(owner, turns,
targetSide, targetPosition, "outrage")
{
_owner = owner;
_turns = turns;
_targetSide = targetSide;
_targetPosition = targetPosition;
}
/// <inheritdoc />
public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{
choice = TurnChoiceHelper.CreateMoveChoice(_owner, "outrage", _targetSide, _targetPosition);
}
/// <inheritdoc />
public override void OnAfterHits(IExecutingMove move, IPokemon target)
{
_turns--;
if (_turns <= 0)
{
RemoveSelf();
_owner.Volatile.Add(new Confusion());
}
}
}

View File

@@ -0,0 +1,39 @@
using PkmnLib.Plugin.Gen7.Scripts.Utils;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
public abstract class OutrageLikeEffect : Script
{
private readonly IPokemon _owner;
private int _turns;
private readonly byte _targetSide;
private readonly byte _targetPosition;
private readonly StringKey _move;
public OutrageLikeEffect(IPokemon owner, int turns, byte targetSide, byte targetPosition, StringKey move)
{
_owner = owner;
_turns = turns;
_targetSide = targetSide;
_targetPosition = targetPosition;
_move = move;
}
/// <inheritdoc />
public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{
choice = TurnChoiceHelper.CreateMoveChoice(_owner, "_move", _targetSide, _targetPosition);
}
/// <inheritdoc />
public override void OnAfterHits(IExecutingMove move, IPokemon target)
{
_turns--;
if (_turns <= 0)
{
RemoveSelf();
_owner.Volatile.Add(new Confusion());
}
}
}

View File

@@ -0,0 +1,27 @@
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "perish_song")]
public class PerishSongEffect : Script
{
private int _turns;
private IPokemon _owner;
public PerishSongEffect(IPokemon owner, int turns = 3)
{
_owner = owner;
_turns = turns;
}
/// <inheritdoc />
public override void OnEndTurn(IBattle battle)
{
_turns--;
if (_turns <= 0)
{
RemoveSelf();
_owner.Faint(DamageSource.Misc);
}
}
}

View File

@@ -0,0 +1,10 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "petal_dance")]
public class PetalDanceEffect : OutrageLikeEffect
{
public PetalDanceEffect(IPokemon owner, int turns, byte targetSide, byte targetPosition) : base(owner, turns,
targetSide, targetPosition, "petal_dance")
{
}
}

View File

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

View File

@@ -0,0 +1,28 @@
using System.Collections.Generic;
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "powder")]
public class PowderEffect : Script
{
/// <inheritdoc />
/// <inheritdoc />
public override void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
var hit = executingMove.GetHitData(target, hitIndex);
if (hit.Type.Name == "fire")
{
executingMove.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("powder_explodes",
new Dictionary<string, object>
{
{ "user", executingMove.User },
{ "target", target },
}));
var health = executingMove.User.MaxHealth / 4;
executingMove.User.Damage(health, DamageSource.Misc);
block = true;
}
}
}

View File

@@ -0,0 +1,19 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "power_trick")]
public class PowerTrickEffect : Script
{
/// <inheritdoc />
public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat,
ref uint value)
{
value = defensiveStat;
}
/// <inheritdoc />
public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat,
ref uint value)
{
value = offensiveStat;
}
}

View File

@@ -0,0 +1,10 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
[Script(ScriptCategory.Pokemon, "thrash")]
public class ThrashEffect : OutrageLikeEffect
{
public ThrashEffect(IPokemon owner, int turns, byte targetSide, byte targetPosition) : base(owner, turns,
targetSide, targetPosition, "thrash")
{
}
}

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Plugin.Gen7.Scripts.Status;
[Script(ScriptCategory.Status, "badly_poisoned")]
public class BadlyPoisoned : Script
{
}