Files
PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/IceBallTests.cs

226 lines
8.7 KiB
C#

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;
/// <summary>
/// Tests for the <see cref="IceBall"/> move script and its <see cref="IceBallEffect"/> 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."
/// </summary>
public class IceBallTests
{
/// <summary>
/// Creates a fully mocked test setup for Ice Ball tests. The user gets a real <see cref="ScriptSet"/> as
/// its volatile set, so the effect applied by the move can be inspected.
/// </summary>
private static (IceBall script, IExecutingMove move, IBattlePokemon user, IBattlePokemon target, ScriptSet
userVolatile) CreateTestSetup()
{
var script = new IceBall();
var move = Substitute.For<IExecutingMove>();
var user = Substitute.For<IBattlePokemon>();
user.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
var userVolatile = new ScriptSet(user);
user.Volatile.Returns(userVolatile);
move.User.Returns(user);
var moveData = Substitute.For<PkmnLib.Static.Moves.IMoveData>();
moveData.Name.Returns(new StringKey("ice_ball"));
move.UseMove.Returns(moveData);
var target = Substitute.For<IBattlePokemon>();
return (script, move, user, target, userVolatile);
}
/// <summary>
/// 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.
/// </summary>
[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);
}
/// <summary>
/// Bulbapedia: Ice Ball inflicts damage "doubling in power for each consecutive hit". With a
/// <see cref="IceBallEffect.TurnCount"/> of n, the power is multiplied by 2^n.
/// </summary>
[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);
}
/// <summary>
/// Technical test: a doubled base power that would overflow the ushort range is clamped to
/// <see cref="ushort.MaxValue"/> instead of wrapping around.
/// </summary>
[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);
}
/// <summary>
/// Bulbapedia: "Ice Ball inflicts damage over five turns" — the first use starts the multi-turn effect
/// by attaching the <see cref="IceBallEffect"/> volatile to the user, with no consecutive hits yet.
/// </summary>
[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<IceBallEffect>();
await Assert.That(effect).IsNotNull();
await Assert.That(effect!.TurnCount).IsEqualTo(0);
}
/// <summary>
/// Bulbapedia: the power doubles "for each consecutive hit" — every hit after the first increments the
/// consecutive-hit counter on the existing <see cref="IceBallEffect"/>.
/// </summary>
[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);
}
/// <summary>
/// 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.
/// </summary>
[Test]
public async Task ChangeBasePower_ThirdConsecutiveHit_PowerIsQuadrupled()
{
// Arrange
var (script, move, user, target, userVolatile) = CreateTestSetup();
var battle = Substitute.For<IBattle>();
// Act - turn 1: first hit, then end of turn
script.OnSecondaryEffect(move, target, 0);
var effect = userVolatile.Get<IceBallEffect>()!;
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);
}
/// <summary>
/// Bulbapedia: "The move doubles its power further if the user previously employed Defense Curl."
/// The doubling is implemented by the <see cref="DefenseCurlEffect"/> volatile on the user, whose own
/// <see cref="DefenseCurlEffect.ChangeBasePower"/> hook runs alongside the move script's.
/// </summary>
[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);
}
/// <summary>
/// Bulbapedia: "the power will reset ... if Ice Ball is interrupted." A miss interrupts the sequence,
/// removing the <see cref="IceBallEffect"/> from the user.
/// </summary>
[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();
}
/// <summary>
/// Bulbapedia: "the power will reset after five turns" — once the five-turn sequence has completed, the
/// <see cref="IceBallEffect"/> removes itself from the user.
/// </summary>
[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<IBattle>();
// 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();
}
}