229 lines
8.6 KiB
C#
229 lines
8.6 KiB
C#
using PkmnLib.Dynamic.Models;
|
|
using PkmnLib.Dynamic.Models.Choices;
|
|
using PkmnLib.Dynamic.ScriptHandling;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Moves;
|
|
using PkmnLib.Plugin.Gen7.Scripts.Status;
|
|
using PkmnLib.Static;
|
|
using PkmnLib.Static.Species;
|
|
|
|
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="BeatUp"/> move script.
|
|
/// Gen VII Bulbapedia behavior: "Beat Up inflicts damage on the target from the user and from each conscious
|
|
/// Pokémon in the user's party that does not have a non-volatile status." Since Generation V, "the base power
|
|
/// per strike is no longer 10, but instead individually based on the Attack base stats of the party Pokémon:
|
|
/// BasePower = BaseAttack(PartyMember)/10 + 5".
|
|
/// </summary>
|
|
public class BeatUpTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a mocked party member with the given base Attack stat, usability, and optional non-volatile
|
|
/// status script in its <see cref="IPokemon.StatusScript"/>.
|
|
/// </summary>
|
|
private static IPokemon CreatePartyMember(ushort baseAttack = 100, bool usable = true, Script? status = null)
|
|
{
|
|
var pokemon = Substitute.For<IPokemon>();
|
|
pokemon.IsUsable.Returns(usable);
|
|
pokemon.StatusScript.Returns(status == null ? new ScriptContainer() : new ScriptContainer(status));
|
|
var form = Substitute.For<IForm>();
|
|
form.BaseStats.Returns(new ImmutableStatisticSet<ushort>(100, baseAttack, 100, 100, 100, 100));
|
|
pokemon.Form.Returns(form);
|
|
return pokemon;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a fully mocked test setup where the user and the given other Pokémon form a party in a battle.
|
|
/// </summary>
|
|
private static (BeatUp script, IMoveChoice choice, IExecutingMove move) CreateTestSetup(IPokemon user,
|
|
params IPokemon?[] otherPartyMembers)
|
|
{
|
|
var script = new BeatUp();
|
|
|
|
var members = new List<IPokemon?> { user };
|
|
members.AddRange(otherPartyMembers);
|
|
|
|
var party = Substitute.For<IPokemonParty>();
|
|
party.GetEnumerator().Returns(_ => members.GetEnumerator());
|
|
var battleParty = Substitute.For<IBattleParty>();
|
|
battleParty.Party.Returns(party);
|
|
|
|
var battle = Substitute.For<IBattle>();
|
|
battle.Parties.Returns(new[] { battleParty });
|
|
var battleData = Substitute.For<IPokemonBattleData>();
|
|
battleData.Battle.Returns(battle);
|
|
user.BattleData.Returns(battleData);
|
|
|
|
var choice = Substitute.For<IMoveChoice>();
|
|
choice.User.Returns(user);
|
|
var move = Substitute.For<IExecutingMove>();
|
|
move.User.Returns(user);
|
|
|
|
return (script, choice, move);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Beat Up inflicts damage on the target from the user and from each conscious Pokémon in the
|
|
/// user's party that does not have a non-volatile status."
|
|
/// With the user and two healthy allies, the move strikes three times.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeNumberOfHits_AllPartyMembersEligible_OneHitPerMember()
|
|
{
|
|
// Arrange
|
|
var user = CreatePartyMember();
|
|
var (script, choice, _) = CreateTestSetup(user, CreatePartyMember(), CreatePartyMember());
|
|
byte numberOfHits = 1;
|
|
|
|
// Act
|
|
script.ChangeNumberOfHits(choice, ref numberOfHits);
|
|
|
|
// Assert
|
|
await Assert.That(numberOfHits).IsEqualTo((byte)3);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "Beat Up inflicts damage on the target from the user and from each conscious Pokémon in the
|
|
/// user's party".
|
|
/// A fainted (not conscious) party member does not contribute a strike.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeNumberOfHits_FaintedPartyMember_Excluded()
|
|
{
|
|
// Arrange
|
|
var user = CreatePartyMember();
|
|
var (script, choice, _) = CreateTestSetup(user, CreatePartyMember(usable: false), CreatePartyMember());
|
|
byte numberOfHits = 1;
|
|
|
|
// Act
|
|
script.ChangeNumberOfHits(choice, ref numberOfHits);
|
|
|
|
// Assert
|
|
await Assert.That(numberOfHits).IsEqualTo((byte)2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "each conscious Pokémon in the user's party that does not have a non-volatile status".
|
|
/// A party member with a non-volatile status (here <see cref="Burned"/>) does not contribute a strike.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeNumberOfHits_StatusedPartyMember_Excluded()
|
|
{
|
|
// Arrange
|
|
var user = CreatePartyMember();
|
|
var (script, choice, _) = CreateTestSetup(user, CreatePartyMember(status: new Burned()));
|
|
byte numberOfHits = 1;
|
|
|
|
// Act
|
|
script.ChangeNumberOfHits(choice, ref numberOfHits);
|
|
|
|
// Assert
|
|
await Assert.That(numberOfHits).IsEqualTo((byte)1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Technical test: outside of battle (no <see cref="IPokemon.BattleData"/>) there are no relevant party
|
|
/// members, and the number of hits falls back to a single strike.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeNumberOfHits_NoBattleData_SingleHit()
|
|
{
|
|
// Arrange
|
|
var script = new BeatUp();
|
|
var user = Substitute.For<IPokemon>();
|
|
user.BattleData.Returns((IPokemonBattleData?)null);
|
|
var choice = Substitute.For<IMoveChoice>();
|
|
choice.User.Returns(user);
|
|
byte numberOfHits = 3;
|
|
|
|
// Act
|
|
script.ChangeNumberOfHits(choice, ref numberOfHits);
|
|
|
|
// Assert
|
|
await Assert.That(numberOfHits).IsEqualTo((byte)1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "the base power per strike is no longer 10, but instead individually based on the Attack
|
|
/// base stats of the party Pokémon: BasePower = BaseAttack(PartyMember)/10 + 5".
|
|
/// Verifies the formula, including integer truncation of the division.
|
|
/// </summary>
|
|
[Test, Arguments((ushort)10, (ushort)6), Arguments((ushort)55, (ushort)10), Arguments((ushort)59, (ushort)10),
|
|
Arguments((ushort)100, (ushort)15), Arguments((ushort)255, (ushort)30)]
|
|
public async Task ChangeBasePower_UsesBaseAttackFormula(ushort baseAttack, ushort expectedBasePower)
|
|
{
|
|
// Arrange
|
|
var user = CreatePartyMember(baseAttack);
|
|
var (script, _, move) = CreateTestSetup(user);
|
|
var target = Substitute.For<IPokemon>();
|
|
ushort basePower = 10;
|
|
|
|
// Act
|
|
script.ChangeBasePower(move, target, 0, ref basePower);
|
|
|
|
// Assert
|
|
await Assert.That(basePower).IsEqualTo(expectedBasePower);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: "the base power per strike is [...] individually based on the Attack base stats of the party
|
|
/// Pokémon". The second strike uses the second eligible party member's base Attack, not the user's.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeBasePower_SecondHit_UsesSecondPartyMembersBaseAttack()
|
|
{
|
|
// Arrange
|
|
var user = CreatePartyMember(100);
|
|
var (script, _, move) = CreateTestSetup(user, CreatePartyMember(250));
|
|
var target = Substitute.For<IPokemon>();
|
|
ushort basePower = 10;
|
|
|
|
// Act
|
|
script.ChangeBasePower(move, target, 1, ref basePower);
|
|
|
|
// Assert - 250 / 10 + 5 = 30
|
|
await Assert.That(basePower).IsEqualTo((ushort)30);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulbapedia: strikes only come from party members "that [do] not have a non-volatile status".
|
|
/// An ineligible member is skipped entirely, so the strike after the user's uses the next eligible
|
|
/// member's base Attack.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeBasePower_StatusedMemberSkipped_UsesNextEligibleMember()
|
|
{
|
|
// Arrange
|
|
var user = CreatePartyMember(100);
|
|
var (script, _, move) = CreateTestSetup(user, CreatePartyMember(250, status: new Burned()),
|
|
CreatePartyMember(60));
|
|
var target = Substitute.For<IPokemon>();
|
|
ushort basePower = 10;
|
|
|
|
// Act
|
|
script.ChangeBasePower(move, target, 1, ref basePower);
|
|
|
|
// Assert - the burned member is skipped: 60 / 10 + 5 = 11
|
|
await Assert.That(basePower).IsEqualTo((ushort)11);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Technical test: a hit index beyond the number of eligible party members leaves the base power unchanged
|
|
/// instead of throwing.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task ChangeBasePower_HitIndexBeyondEligibleMembers_BasePowerUnchanged()
|
|
{
|
|
// Arrange
|
|
var user = CreatePartyMember(100);
|
|
var (script, _, move) = CreateTestSetup(user);
|
|
var target = Substitute.For<IPokemon>();
|
|
ushort basePower = 10;
|
|
|
|
// Act
|
|
script.ChangeBasePower(move, target, 3, ref basePower);
|
|
|
|
// Assert
|
|
await Assert.That(basePower).IsEqualTo((ushort)10);
|
|
}
|
|
} |