226 lines
8.7 KiB
C#
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();
|
|
}
|
|
} |