This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
using PkmnLib.Dynamic.Events;
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
||||
|
||||
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the <see cref="Magnitude"/> 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.
|
||||
/// </summary>
|
||||
public class MagnitudeTests
|
||||
{
|
||||
private static (Magnitude script, IExecutingMove move, IPokemon target, IPokemon user, EventHook eventHook)
|
||||
CreateTestSetup(int randomValue)
|
||||
{
|
||||
var script = new Magnitude();
|
||||
|
||||
var random = Substitute.For<IBattleRandom>();
|
||||
random.GetInt(0, 100).Returns(randomValue);
|
||||
|
||||
var eventHook = new EventHook();
|
||||
var battle = Substitute.For<IBattle>();
|
||||
battle.Random.Returns(random);
|
||||
battle.EventHook.Returns(eventHook);
|
||||
|
||||
var battleData = Substitute.For<IPokemonBattleData>();
|
||||
battleData.Battle.Returns(battle);
|
||||
|
||||
var user = Substitute.For<IPokemon>();
|
||||
user.BattleData.Returns(battleData);
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
move.User.Returns(user);
|
||||
var target = Substitute.For<IPokemon>();
|
||||
|
||||
return (script, move, target, user, eventHook);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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%).
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ChangeBasePower_AllRandomRolls_MatchBulbapediaProbabilities()
|
||||
{
|
||||
// Arrange
|
||||
var powerCounts = new Dictionary<ushort, int>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: the power is based on "a randomly selected 'magnitude' value ranging from 4 to 10" —
|
||||
/// the selected magnitude is announced through a <see cref="DialogEvent"/> so the front-end can show
|
||||
/// it (in the games: "Magnitude <value>!").
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulbapedia: the magnitude announcement concerns the move's user and target — the fired
|
||||
/// <see cref="DialogEvent"/> carries both so the front-end can format its message.
|
||||
/// </summary>
|
||||
[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((IPokemon)capturedEvent!.Parameters!["user"]).IsEqualTo(user);
|
||||
await Assert.That((IPokemon)capturedEvent.Parameters!["target"]).IsEqualTo(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Technical test: when the user has no <see cref="IPokemon.BattleData"/> (it is not in a battle),
|
||||
/// no random roll can be made and the base power is left unchanged.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task ChangeBasePower_NullBattleData_BasePowerUnchanged()
|
||||
{
|
||||
// Arrange
|
||||
var script = new Magnitude();
|
||||
var user = Substitute.For<IPokemon>();
|
||||
user.BattleData.Returns((IPokemonBattleData?)null);
|
||||
var move = Substitute.For<IExecutingMove>();
|
||||
move.User.Returns(user);
|
||||
ushort basePower = 1;
|
||||
|
||||
// Act
|
||||
script.ChangeBasePower(move, Substitute.For<IPokemon>(), 0, ref basePower);
|
||||
|
||||
// Assert
|
||||
await Assert.That(basePower).IsEqualTo((ushort)1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user