160 lines
6.8 KiB
C#
160 lines
6.8 KiB
C#
using PkmnLib.Dynamic.Libraries;
|
|
using PkmnLib.Dynamic.Models;
|
|
using PkmnLib.Static;
|
|
using PkmnLib.Static.Species;
|
|
using PkmnLib.Tests.Integration;
|
|
|
|
namespace PkmnLib.Tests.Dynamic;
|
|
|
|
/// <summary>
|
|
/// Regression tests for the battle lifecycle of <see cref="IBattlePokemon"/>: battle-only state must die
|
|
/// with the battle, and the underlying <see cref="IPokemon"/> must come out of a battle unchanged except
|
|
/// for the deliberately persistent parts (health, PP, non-volatile status, experience).
|
|
/// </summary>
|
|
public class BattleLifecycleTests
|
|
{
|
|
private static IPokemon CreatePokemon(IDynamicLibrary library, string speciesName)
|
|
{
|
|
if (!library.StaticLibrary.Species.TryGet(speciesName, out var species))
|
|
throw new InvalidOperationException($"Failed to load {speciesName} species.");
|
|
return new PokemonImpl(library, species, species.GetDefaultForm(), new AbilityIndex
|
|
{
|
|
Index = 0,
|
|
IsHidden = false,
|
|
}, 50, 0, Gender.Male, 0, "hardy");
|
|
}
|
|
|
|
private static IBattle CreateBattle(IDynamicLibrary library, IPokemon own, IPokemon opponent,
|
|
bool isWildBattle = false, int seed = 0)
|
|
{
|
|
var party = new PokemonPartyImpl(1);
|
|
party.SwapInto(own, 0);
|
|
var opponentParty = new PokemonPartyImpl(1);
|
|
opponentParty.SwapInto(opponent, 0);
|
|
var parties = new[]
|
|
{
|
|
new BattlePartyImpl(party, [new ResponsibleIndex(0, 0)]),
|
|
new BattlePartyImpl(opponentParty, [new ResponsibleIndex(1, 0)]),
|
|
};
|
|
var battle = new BattleImpl(library, parties, false, 2, 1, isWildBattle, "grass", seed);
|
|
battle.Sides[0].SendOut(0, own);
|
|
battle.Sides[1].SendOut(0, opponent);
|
|
return battle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The original bug this refactor removes by construction: a captured Pokémon must not carry any
|
|
/// battle state into the party of its captor. The capture is reported through
|
|
/// <see cref="BattleResult.CapturedPokemon"/> as the persistent Pokémon.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task Capture_ReportsPersistentPokemonAndLeavesItUsable()
|
|
{
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
if (!library.StaticLibrary.Items.TryGet("master_ball", out var masterBall))
|
|
throw new InvalidOperationException("Failed to load master ball.");
|
|
|
|
// The capture roll is random; find a seed where the capture succeeds. The loop is deterministic,
|
|
// so the test always exercises the same battle.
|
|
var result = CaptureResult.Failed;
|
|
IBattle? battle = null;
|
|
IPokemon? wildPokemon = null;
|
|
for (var seed = 0; seed < 100 && !result.IsCaught; seed++)
|
|
{
|
|
battle?.Dispose();
|
|
var own = CreatePokemon(library, "bulbasaur");
|
|
wildPokemon = CreatePokemon(library, "caterpie");
|
|
battle = CreateBattle(library, own, wildPokemon, true, seed);
|
|
var wildBattlePokemon = battle.GetPokemon(1, 0)!;
|
|
wildBattlePokemon.Damage(wildBattlePokemon.CurrentHealth - 1, DamageSource.MoveDamage);
|
|
result = battle.AttempCapture(1, 0, masterBall);
|
|
}
|
|
|
|
await Assert.That(result.IsCaught).IsTrue();
|
|
await Assert.That(battle!.HasEnded).IsTrue();
|
|
await Assert.That(battle.Result!.Value.CapturedPokemon).Contains(wildPokemon!);
|
|
|
|
battle.Dispose();
|
|
|
|
// The persistent Pokémon left the battle without any battle state: it is usable and simply damaged.
|
|
await Assert.That(wildPokemon!.IsUsable).IsTrue();
|
|
await Assert.That(wildPokemon.CurrentHealth).IsEqualTo(1u);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Items removed or stolen during a battle are a battle-only overlay: after the battle, the victim
|
|
/// still holds its item.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task StolenHeldItem_IsRestoredAfterBattle()
|
|
{
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
if (!library.StaticLibrary.Items.TryGet("oran_berry", out var berry))
|
|
throw new InvalidOperationException("Failed to load oran berry.");
|
|
var own = CreatePokemon(library, "bulbasaur");
|
|
_ = own.ForceSetHeldItem(berry);
|
|
var opponent = CreatePokemon(library, "charmander");
|
|
|
|
var battle = CreateBattle(library, own, opponent);
|
|
var battlePokemon = battle.GetPokemon(0, 0)!;
|
|
await Assert.That(battlePokemon.TryStealHeldItem(out _)).IsTrue();
|
|
await Assert.That(battlePokemon.HeldItem).IsNull();
|
|
|
|
battle.Dispose();
|
|
|
|
await Assert.That(own.HeldItem).IsSameReferenceAs(berry);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A battle-only form (such as a mega evolution) reverts to the original form when the battle ends.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task BattleOnlyForm_RevertsWhenBattleEnds()
|
|
{
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
var own = CreatePokemon(library, "absol");
|
|
var originalForm = own.Form;
|
|
if (!own.Species.TryGetForm("mega", out var megaForm))
|
|
throw new InvalidOperationException("Absol has no mega form.");
|
|
await Assert.That(megaForm.IsBattleOnlyForm).IsTrue();
|
|
var opponent = CreatePokemon(library, "charmander");
|
|
|
|
var battle = CreateBattle(library, own, opponent);
|
|
var battlePokemon = battle.GetPokemon(0, 0)!;
|
|
battlePokemon.ChangeForm(megaForm);
|
|
await Assert.That(own.Form).IsSameReferenceAs(megaForm);
|
|
|
|
battle.Dispose();
|
|
|
|
await Assert.That(own.Form).IsSameReferenceAs(originalForm);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A second battle with the same party starts with fresh battle state: no seen opponents or stale
|
|
/// original species from the previous battle.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task SecondBattleWithSameParty_StartsWithFreshBattleState()
|
|
{
|
|
var library = LibraryHelpers.LoadLibrary();
|
|
var own = CreatePokemon(library, "bulbasaur");
|
|
var opponent1 = CreatePokemon(library, "charmander");
|
|
|
|
var battle1 = CreateBattle(library, own, opponent1);
|
|
var firstWrapper = battle1.GetPokemon(0, 0)!;
|
|
await Assert.That(firstWrapper.SeenOpponents.Count).IsEqualTo(1);
|
|
firstWrapper.ChangeStatBoost(Statistic.Attack, 3, true, false);
|
|
battle1.Dispose();
|
|
|
|
var opponent2 = CreatePokemon(library, "squirtle");
|
|
var battle2 = CreateBattle(library, own, opponent2);
|
|
var secondWrapper = battle2.GetPokemon(0, 0)!;
|
|
|
|
await Assert.That(secondWrapper).IsNotSameReferenceAs(firstWrapper);
|
|
await Assert.That(secondWrapper.SeenOpponents.Count).IsEqualTo(1);
|
|
await Assert.That(secondWrapper.SeenOpponents[0].UnderlyingPokemon).IsSameReferenceAs(opponent2);
|
|
await Assert.That(secondWrapper.StatBoost.Attack).IsEqualTo((sbyte)0);
|
|
await Assert.That(secondWrapper.OriginalSpecies).IsSameReferenceAs(own.Species);
|
|
battle2.Dispose();
|
|
}
|
|
} |