using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
using PkmnLib.Static.Utils;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
///
/// Tests for the move script and its volatile.
/// Gen VII Bulbapedia behavior (Generations III to VII): "Ice Ball inflicts damage over five turns, doubling
/// in power for each consecutive hit; the power will reset after five turns or if Ice Ball is interrupted."
/// "The move doubles its power further if the user previously employed Defense Curl."
///
public class IceBallTests
{
///
/// Creates a fully mocked test setup for Ice Ball tests. The user gets a real as
/// its volatile set, so the effect applied by the move can be inspected.
///
private static (IceBall script, IExecutingMove move, IPokemon user, IPokemon target, ScriptSet userVolatile)
CreateTestSetup()
{
var script = new IceBall();
var move = Substitute.For();
var user = Substitute.For();
user.GetScripts().Returns(_ => new ScriptIterator(new List>()));
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
move.User.Returns(user);
var moveData = Substitute.For();
moveData.Name.Returns(new StringKey("ice_ball"));
move.UseMove.Returns(moveData);
var target = Substitute.For();
return (script, move, user, target, userVolatile);
}
///
/// Bulbapedia: "Ice Ball inflicts damage over five turns". On the first use there is no accumulated
/// consecutive-hit bonus yet, so the base power is unchanged.
///
[Test]
public async Task ChangeBasePower_NoIceBallEffect_BasePowerUnchanged()
{
// Arrange
var (script, move, _, target, _) = CreateTestSetup();
ushort basePower = 30;
// Act
script.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)30);
}
///
/// Bulbapedia: Ice Ball inflicts damage "doubling in power for each consecutive hit". With a
/// of n, the power is multiplied by 2^n.
///
[Test, Arguments(0, (ushort)30), Arguments(1, (ushort)60), Arguments(2, (ushort)120), Arguments(3, (ushort)240),
Arguments(4, (ushort)480)]
public async Task ChangeBasePower_ConsecutiveHits_PowerDoublesPerHit(int turnCount, ushort expectedPower)
{
// Arrange
var (script, move, user, target, userVolatile) = CreateTestSetup();
userVolatile.Add(new IceBallEffect(user, "ice_ball") { TurnCount = turnCount });
ushort basePower = 30;
// Act
script.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo(expectedPower);
}
///
/// Technical test: a doubled base power that would overflow the ushort range is clamped to
/// instead of wrapping around.
///
[Test]
public async Task ChangeBasePower_MultiplicationOverflows_ClampsToUShortMax()
{
// Arrange
var (script, move, user, target, userVolatile) = CreateTestSetup();
userVolatile.Add(new IceBallEffect(user, "ice_ball") { TurnCount = 4 });
ushort basePower = 50000;
// Act
script.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo(ushort.MaxValue);
}
///
/// Bulbapedia: "Ice Ball inflicts damage over five turns" — the first use starts the multi-turn effect
/// by attaching the volatile to the user, with no consecutive hits yet.
///
[Test]
public async Task OnSecondaryEffect_FirstUse_AddsIceBallEffectToUser()
{
// Arrange
var (script, move, _, target, userVolatile) = CreateTestSetup();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var effect = userVolatile.Get();
await Assert.That(effect).IsNotNull();
await Assert.That(effect!.TurnCount).IsEqualTo(0);
}
///
/// Bulbapedia: the power doubles "for each consecutive hit" — every hit after the first increments the
/// consecutive-hit counter on the existing .
///
[Test]
public async Task OnSecondaryEffect_EffectAlreadyPresent_IncrementsTurnCount()
{
// Arrange
var (script, move, user, target, userVolatile) = CreateTestSetup();
var effect = new IceBallEffect(user, "ice_ball");
userVolatile.Add(effect);
// Act
effect.OnEndTurn(user, move.Battle);
// Assert
await Assert.That(effect.TurnCount).IsEqualTo(1);
}
///
/// Bulbapedia: "doubling in power for each consecutive hit" — the third consecutive hit must have four
/// times the base power (30 → 60 → 120). This simulates the actual per-turn flow: hit, end of turn,
/// next hit.
///
[Test]
public async Task ChangeBasePower_ThirdConsecutiveHit_PowerIsQuadrupled()
{
// Arrange
var (script, move, user, target, userVolatile) = CreateTestSetup();
var battle = Substitute.For();
// Act - turn 1: first hit, then end of turn
script.OnSecondaryEffect(move, target, 0);
var effect = userVolatile.Get()!;
effect.OnEndTurn(user, battle);
// Turn 2: second hit doubles the power
ushort secondHitPower = 30;
script.ChangeBasePower(move, target, 0, ref secondHitPower);
await Assert.That(secondHitPower).IsEqualTo((ushort)60);
script.OnSecondaryEffect(move, target, 0);
effect.OnEndTurn(user, battle);
// Turn 3: third hit quadruples the power
ushort thirdHitPower = 30;
script.ChangeBasePower(move, target, 0, ref thirdHitPower);
// Assert
await Assert.That(thirdHitPower).IsEqualTo((ushort)120);
}
///
/// Bulbapedia: "The move doubles its power further if the user previously employed Defense Curl."
/// The doubling is implemented by the volatile on the user, whose own
/// hook runs alongside the move script's.
///
[Test]
public async Task ChangeBasePower_UserUsedDefenseCurl_PowerIsDoubled()
{
// Arrange
var (script, move, _, target, userVolatile) = CreateTestSetup();
var defenseCurlEffect = new DefenseCurlEffect();
userVolatile.Add(defenseCurlEffect);
ushort basePower = 30;
// Act - the battle engine runs the move script's hook and the user's volatile hooks
script.ChangeBasePower(move, target, 0, ref basePower);
defenseCurlEffect.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)60);
}
///
/// Bulbapedia: "the power will reset ... if Ice Ball is interrupted." A miss interrupts the sequence,
/// removing the from the user.
///
[Test]
public async Task OnMoveMiss_MoveMisses_EffectRemovesItself()
{
// Arrange
var (_, move, user, target, userVolatile) = CreateTestSetup();
var effect = new IceBallEffect(user, "ice_ball");
userVolatile.Add(effect);
// Act
effect.OnMoveMiss(move, target);
// Assert
await Assert.That(userVolatile.Contains(new StringKey("ice_ball"))).IsFalse();
}
///
/// Bulbapedia: "the power will reset after five turns" — once the five-turn sequence has completed, the
/// removes itself from the user.
///
[Test]
public async Task OnEndTurn_AfterFiveCompletedTurns_EffectRemovesItself()
{
// Arrange
var (_, _, user, _, userVolatile) = CreateTestSetup();
var effect = new IceBallEffect(user, "ice_ball");
userVolatile.Add(effect);
var battle = Substitute.For();
// Act - six end-of-turn ticks: five turns of use, then the reset
for (var i = 0; i < 6; i++)
effect.OnEndTurn(user, battle);
// Assert
await Assert.That(userVolatile.Contains(new StringKey("ice_ball"))).IsFalse();
}
}