using PkmnLib.Dynamic.Events; using PkmnLib.Dynamic.Models; using PkmnLib.Plugin.Gen7.Scripts.Moves; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script. /// Gen VII Bulbapedia behavior: "Magnitude's power varies based on a randomly selected 'magnitude' value /// ranging from 4 to 10, with 4 having the least power and 10 having the greatest." /// The doubling of damage against a Pokémon in the semi-invulnerable turn of Dig and the halving under /// Grassy Terrain are handled outside this script and are not tested here. /// public class MagnitudeTests { private static (Magnitude script, IExecutingMove move, IBattlePokemon target, IBattlePokemon user, EventHook eventHook) CreateTestSetup(int randomValue) { var script = new Magnitude(); var random = Substitute.For(); random.GetInt(0, 100).Returns(randomValue); var eventHook = new EventHook(); var battle = Substitute.For(); battle.Random.Returns(random); battle.EventHook.Returns(eventHook); var user = Substitute.For(); var move = Substitute.For(); user.Battle.Returns(battle); move.User.Returns(user); var target = Substitute.For(); return (script, move, target, user, eventHook); } /// /// Bulbapedia magnitude→power table: 4→10, 5→30, 6→50, 7→70, 8→90, 9→110, 10→150. /// Each row exercises a boundary of a random-percentage bracket, verifying both edges of every /// magnitude's probability range (5%, 10%, 20%, 30%, 20%, 10%, 5%). /// [Test, Arguments(0, 10), Arguments(4, 10), Arguments(5, 30), Arguments(14, 30), Arguments(15, 50), Arguments(34, 50), Arguments(35, 70), Arguments(64, 70), Arguments(65, 90), Arguments(84, 90), Arguments(85, 110), Arguments(94, 110), Arguments(95, 150), Arguments(99, 150)] // magnitude 4 bracket start // magnitude 4 bracket end // magnitude 5 bracket start // magnitude 5 bracket end // magnitude 6 bracket start // magnitude 6 bracket end // magnitude 7 bracket start // magnitude 7 bracket end // magnitude 8 bracket start // magnitude 8 bracket end // magnitude 9 bracket start // magnitude 9 bracket end // magnitude 10 bracket start // magnitude 10 bracket end public async Task ChangeBasePower_RandomRoll_MapsToExpectedBasePower(int randomValue, int expectedPower) { // Arrange var (script, move, target, _, _) = CreateTestSetup(randomValue); ushort basePower = 1; // Act script.ChangeBasePower(move, target, 0, ref basePower); // Assert await Assert.That(basePower).IsEqualTo((ushort)expectedPower); } /// /// Bulbapedia probability table: magnitude 4 has 5% probability, 5 has 10%, 6 has 20%, 7 has 30%, /// 8 has 20%, 9 has 10% and 10 has 5%. Rolling every possible random percentage (0–99) exactly once /// must produce each power in exactly that proportion. /// [Test] public async Task ChangeBasePower_AllRandomRolls_MatchBulbapediaProbabilities() { // Arrange var powerCounts = new Dictionary(); // Act - roll every possible random percentage once for (var roll = 0; roll < 100; roll++) { var (script, move, target, _, _) = CreateTestSetup(roll); ushort basePower = 1; script.ChangeBasePower(move, target, 0, ref basePower); powerCounts[basePower] = powerCounts.GetValueOrDefault(basePower) + 1; } // Assert - counts out of 100 equal the Bulbapedia percentages await Assert.That(powerCounts.GetValueOrDefault((ushort)10)).IsEqualTo(5); await Assert.That(powerCounts.GetValueOrDefault((ushort)30)).IsEqualTo(10); await Assert.That(powerCounts.GetValueOrDefault((ushort)50)).IsEqualTo(20); await Assert.That(powerCounts.GetValueOrDefault((ushort)70)).IsEqualTo(30); await Assert.That(powerCounts.GetValueOrDefault((ushort)90)).IsEqualTo(20); await Assert.That(powerCounts.GetValueOrDefault((ushort)110)).IsEqualTo(10); await Assert.That(powerCounts.GetValueOrDefault((ushort)150)).IsEqualTo(5); } /// /// Bulbapedia: the power is based on "a randomly selected 'magnitude' value ranging from 4 to 10" — /// the selected magnitude is announced through a so the front-end can show /// it (in the games: "Magnitude <value>!"). /// [Test, Arguments(0, 4), Arguments(5, 5), Arguments(15, 6), Arguments(35, 7), Arguments(65, 8), Arguments(85, 9), Arguments(95, 10)] public async Task ChangeBasePower_RandomRoll_FiresDialogEventWithSelectedMagnitude(int randomValue, int expectedMagnitude) { // Arrange var (script, move, target, _, eventHook) = CreateTestSetup(randomValue); DialogEvent? capturedEvent = null; eventHook.Handler += (_, args) => { if (args is DialogEvent dialogEvent) capturedEvent = dialogEvent; }; ushort basePower = 1; // Act script.ChangeBasePower(move, target, 0, ref basePower); // Assert await Assert.That(capturedEvent).IsNotNull(); await Assert.That(capturedEvent!.Message).IsEqualTo("magnitude"); await Assert.That((int)capturedEvent.Parameters!["magnitude"]).IsEqualTo(expectedMagnitude); } /// /// Bulbapedia: the magnitude announcement concerns the move's user and target — the fired /// carries both so the front-end can format its message. /// [Test] public async Task ChangeBasePower_RandomRoll_DialogEventContainsUserAndTarget() { // Arrange var (script, move, target, user, eventHook) = CreateTestSetup(50); DialogEvent? capturedEvent = null; eventHook.Handler += (_, args) => { if (args is DialogEvent dialogEvent) capturedEvent = dialogEvent; }; ushort basePower = 1; // Act script.ChangeBasePower(move, target, 0, ref basePower); // Assert await Assert.That(capturedEvent).IsNotNull(); await Assert.That((IBattlePokemon)capturedEvent!.Parameters!["user"]).IsEqualTo(user); await Assert.That((IBattlePokemon)capturedEvent.Parameters!["target"]).IsEqualTo(target); } }