using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Side; using PkmnLib.Static.Moves; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script and its side marker. /// Gen VII Bulbapedia behavior: "The move's base power increases by 40 each consecutive turn when used /// repeatedly, reaching a maximum of 200. Power increases only once per round, regardless of how many /// Pokémon deploy it." /// public class EchoedVoiceTests { /// /// Creates a mocked move choice whose chosen move has the given name. /// private static IMoveChoice CreateMoveChoice(string moveName) { var moveData = Substitute.For(); moveData.Name.Returns(new StringKey(moveName)); var learnedMove = Substitute.For(); learnedMove.MoveData.Returns(moveData); var choice = Substitute.For(); choice.ChosenMove.Returns(learnedMove); return choice; } /// /// Creates a fully mocked test setup where the user's side has a real as its /// volatile scripts. /// private static (EchoedVoice script, IExecutingMove move, IPokemon target, IScriptSet sideScripts) CreateTestSetup() { var script = new EchoedVoice(); var move = Substitute.For(); var target = Substitute.For(); var side = Substitute.For(); side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty>())); IScriptSet sideScripts = new ScriptSet(side); side.VolatileScripts.Returns(sideScripts); var battle = Substitute.For(); battle.Sides.Returns(new[] { side }); var battleData = Substitute.For(); battleData.Battle.Returns(battle); battleData.SideIndex.Returns((byte)0); var user = Substitute.For(); user.BattleData.Returns(battleData); move.User.Returns(user); return (script, move, target, sideScripts); } /// /// Bulbapedia: "The move's base power increases by 40 each consecutive turn" — on the first use there /// is no previous turn, so the base power is not modified. /// [Test] public async Task ChangeBasePower_FirstUse_BasePowerUnchanged() { // Arrange var (script, move, target, _) = CreateTestSetup(); ushort basePower = 40; // Act script.ChangeBasePower(move, target, 0, ref basePower); // Assert await Assert.That(basePower).IsEqualTo((ushort)40); } /// /// Bulbapedia: "The move's base power increases by 40 each consecutive turn when used repeatedly, /// reaching a maximum of 200." Each previous consecutive use adds a stack worth 40 power: 80 on the /// second turn, 120 on the third, 160 on the fourth, and 200 on the fifth. /// [Test, Arguments(1, (ushort)80), Arguments(2, (ushort)120), Arguments(3, (ushort)160), Arguments(4, (ushort)200)] public async Task ChangeBasePower_ConsecutiveUses_PowerIncreasesByFortyPerTurn(int previousUses, ushort expectedPower) { // Arrange - the move hit on the given number of previous consecutive turns var (script, move, target, _) = CreateTestSetup(); for (var i = 0; i < previousUses; i++) script.OnSecondaryEffect(move, target, 0); ushort basePower = 40; // Act script.ChangeBasePower(move, target, 0, ref basePower); // Assert await Assert.That(basePower).IsEqualTo(expectedPower); } /// /// Bulbapedia: the base power increases "reaching a maximum of 200" — further consecutive uses beyond /// the fifth turn do not push the power past the cap. /// [Test] public async Task ChangeBasePower_MoreThanFourPreviousUses_PowerCappedAtTwoHundred() { // Arrange var (script, move, target, _) = CreateTestSetup(); for (var i = 0; i < 6; i++) script.OnSecondaryEffect(move, target, 0); ushort basePower = 40; // Act script.ChangeBasePower(move, target, 0, ref basePower); // Assert await Assert.That(basePower).IsEqualTo((ushort)200); } /// /// Bulbapedia: the power increase applies "when used repeatedly" — using the move marks the user's side /// with so the next turn's use is recognized as consecutive. /// [Test] public async Task OnSecondaryEffect_AddsEchoedVoiceDataToUserSide() { // Arrange var (script, move, target, sideScripts) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName())).IsTrue(); } /// /// Bulbapedia: "The move's base power increases by 40 each consecutive turn" — each additional use /// stacks the side marker, increasing . /// [Test] public async Task OnSecondaryEffect_UsedAgain_StacksEchoedVoiceData() { // Arrange var (script, move, target, sideScripts) = CreateTestSetup(); // Act script.OnSecondaryEffect(move, target, 0); script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(sideScripts.Get()!.Stacks).IsEqualTo(2); } /// /// Technical test: without battle data on the user (outside of battle) the base power hook does /// nothing and does not throw. /// [Test] public async Task ChangeBasePower_UserHasNoBattleData_BasePowerUnchanged() { // Arrange var (script, move, target, _) = CreateTestSetup(); move.User.BattleData.Returns((IPokemonBattleData?)null); ushort basePower = 40; // Act script.ChangeBasePower(move, target, 0, ref basePower); // Assert await Assert.That(basePower).IsEqualTo((ushort)40); } /// /// Bulbapedia: the boost only applies on "consecutive" turns — when a Pokémon on the side chooses a /// move other than Echoed Voice, the marker removes itself, resetting /// the power. /// [Test] public async Task OnBeforeMoveChoice_DifferentMoveChosen_MarkerRemovesItself() { // Arrange var side = Substitute.For(); side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty>())); IScriptSet sideScripts = new ScriptSet(side); var data = new EchoedVoiceData(); sideScripts.Add(data); // Act data.OnBeforeMoveChoice(CreateMoveChoice("tackle")); // Assert await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName())).IsFalse(); } /// /// Bulbapedia: "The move's base power increases by 40 each consecutive turn when used repeatedly" — /// choosing Echoed Voice again keeps the marker (and its stacks) alive. /// [Test] public async Task OnBeforeMoveChoice_EchoedVoiceChosen_MarkerStaysActive() { // Arrange var side = Substitute.For(); side.GetScripts().Returns(_ => new ScriptIterator(Array.Empty>())); IScriptSet sideScripts = new ScriptSet(side); var data = new EchoedVoiceData(); sideScripts.Add(data); // Act data.OnBeforeMoveChoice(CreateMoveChoice("echoed_voice")); // Assert await Assert.That(sideScripts.Contains(ScriptUtils.ResolveName())).IsTrue(); } }