This commit is contained in:
226
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/IceBallTests.cs
Normal file
226
Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/IceBallTests.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
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, IPokemon user, IPokemon target, ScriptSet userVolatile)
|
||||
CreateTestSetup()
|
||||
{
|
||||
var script = new IceBall();
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
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<IPokemon>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user