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);
}
}