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, IBattlePokemon user, IBattlePokemon 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(); } }