using PkmnLib.Dynamic.Models; using PkmnLib.Static; using PkmnLib.Static.Species; using PkmnLib.Tests.Integration; namespace PkmnLib.Tests.Dynamic; /// /// Tests for the ephemeral wrapper. Battle-only state must live on the wrapper /// and never leak into the underlying ; dropping the wrapper is all the cleanup a /// battle needs. /// public class BattlePokemonTests { private static (IBattle battle, IBattlePokemon wrapper, IPokemon inner) CreateBattleWithWrapper( string heldItem = "oran_berry") { var library = LibraryHelpers.LoadLibrary(); if (!library.StaticLibrary.Species.TryGet("bulbasaur", out var bulbasaur)) throw new InvalidOperationException("Failed to load bulbasaur species."); if (!library.StaticLibrary.Species.TryGet("charmander", out var charmander)) throw new InvalidOperationException("Failed to load charmander species."); if (!library.StaticLibrary.Items.TryGet(heldItem, out var item)) throw new InvalidOperationException($"Failed to load item {heldItem}."); var pokemon = new PokemonImpl(library, bulbasaur, bulbasaur.GetDefaultForm(), new AbilityIndex { Index = 0, IsHidden = false, }, 50, 0, Gender.Male, 0, "hardy"); pokemon.LearnMove("tackle", MoveLearnMethod.LevelUp, 0); _ = pokemon.ForceSetHeldItem(item); var opponent = new PokemonImpl(library, charmander, charmander.GetDefaultForm(), new AbilityIndex { Index = 0, IsHidden = false, }, 50, 0, Gender.Male, 0, "hardy"); var party1 = new PokemonPartyImpl(6); party1.SwapInto(pokemon, 0); var party2 = new PokemonPartyImpl(6); party2.SwapInto(opponent, 0); var parties = new[] { new BattlePartyImpl(party1, [new ResponsibleIndex(0, 0)]), new BattlePartyImpl(party2, [new ResponsibleIndex(1, 0)]), }; var battle = new BattleImpl(library, parties, false, 2, 1, false, "grass", 0); var wrapper = parties[0].GetBattlePokemon(pokemon) ?? throw new InvalidOperationException("Wrapper not created."); return (battle, wrapper, pokemon); } [Test] public async Task PersistentMembersAreProxiedToUnderlyingPokemon() { var (_, wrapper, inner) = CreateBattleWithWrapper(); await Assert.That(wrapper.UnderlyingPokemon).IsSameReferenceAs(inner); await Assert.That(wrapper.Species).IsSameReferenceAs(inner.Species); await Assert.That(wrapper.Form).IsSameReferenceAs(inner.Form); await Assert.That(wrapper.Level).IsEqualTo(inner.Level); await Assert.That(wrapper.CurrentHealth).IsEqualTo(inner.CurrentHealth); await Assert.That(wrapper.Nature).IsSameReferenceAs(inner.Nature); await Assert.That(wrapper.OriginalSpecies).IsSameReferenceAs(inner.Species); await Assert.That(wrapper.OriginalForm).IsSameReferenceAs(inner.Form); } [Test] public async Task TypeOverlayDoesNotTouchUnderlyingPokemon() { var (_, wrapper, inner) = CreateBattleWithWrapper(); var originalTypes = inner.Types.ToList(); wrapper.SetTypes([new TypeIdentifier(18, "water")]); await Assert.That(wrapper.Types.Count).IsEqualTo(1); await Assert.That(inner.Types.SequenceEqual(originalTypes)).IsTrue(); wrapper.OnSwitchedOut(); await Assert.That(wrapper.Types.SequenceEqual(inner.Form.Types)).IsTrue(); } [Test] public async Task TemporaryMoveOverlayDoesNotTouchUnderlyingPokemonAndResetsOnSwitchOut() { var (_, wrapper, inner) = CreateBattleWithWrapper(); wrapper.LearnTemporaryMove("swords_dance", MoveLearnMethod.Mimic, 0); await Assert.That(wrapper.Moves[0]!.MoveData.Name.ToString()).IsEqualTo("swords_dance"); await Assert.That(wrapper.BaseMoves[0]!.MoveData.Name.ToString()).IsEqualTo("tackle"); await Assert.That(inner.Moves[0]!.MoveData.Name.ToString()).IsEqualTo("tackle"); wrapper.OnSwitchedOut(); await Assert.That(wrapper.Moves[0]!.MoveData.Name.ToString()).IsEqualTo("tackle"); } [Test] public async Task StatBoostLivesOnWrapperAndResetsOnSwitchOut() { var (_, wrapper, inner) = CreateBattleWithWrapper(); var innerFlatAttack = inner.FlatStats.Attack; var changed = wrapper.ChangeStatBoost(Statistic.Attack, 2, true, false); await Assert.That(changed).IsTrue(); await Assert.That(wrapper.StatBoost.Attack).IsEqualTo((sbyte)2); await Assert.That(wrapper.BoostedStats.Attack > wrapper.FlatStats.Attack).IsTrue(); await Assert.That(inner.FlatStats.Attack).IsEqualTo(innerFlatAttack); wrapper.OnSwitchedOut(); await Assert.That(wrapper.StatBoost.Attack).IsEqualTo((sbyte)0); await Assert.That(wrapper.BoostedStats.Attack).IsEqualTo(wrapper.FlatStats.Attack); } [Test] public async Task HeldItemOverlayDoesNotTouchUnderlyingPokemon() { var (_, wrapper, inner) = CreateBattleWithWrapper(); var innerItem = inner.HeldItem; await Assert.That(innerItem).IsNotNull(); var removed = wrapper.RemoveHeldItemForBattle(); await Assert.That(removed).IsSameReferenceAs(innerItem); await Assert.That(wrapper.HeldItem).IsNull(); await Assert.That(wrapper.HasItemBeenRemovedForBattle).IsTrue(); await Assert.That(inner.HeldItem).IsSameReferenceAs(innerItem); wrapper.RestoreRemovedHeldItem(); await Assert.That(wrapper.HeldItem).IsSameReferenceAs(innerItem); await Assert.That(wrapper.HasItemBeenRemovedForBattle).IsFalse(); } [Test] public async Task StealingHeldItemDoesNotTouchUnderlyingPokemon() { var (_, wrapper, inner) = CreateBattleWithWrapper(); var innerItem = inner.HeldItem; var stolen = wrapper.TryStealHeldItem(out var item); await Assert.That(stolen).IsTrue(); await Assert.That(item).IsSameReferenceAs(innerItem); await Assert.That(wrapper.HeldItem).IsNull(); await Assert.That(inner.HeldItem).IsSameReferenceAs(innerItem); } [Test] public async Task MarkAsCaughtOnlyAffectsWrapper() { var (_, wrapper, inner) = CreateBattleWithWrapper(); wrapper.MarkAsCaught(); await Assert.That(wrapper.IsCaught).IsTrue(); await Assert.That(wrapper.IsUsable).IsFalse(); await Assert.That(inner.IsUsable).IsTrue(); } [Test] public async Task AbilityOverrideAndSuppressionLiveOnWrapper() { var (_, wrapper, inner) = CreateBattleWithWrapper(); var naturalAbility = inner.Ability; await Assert.That(naturalAbility).IsNotNull(); wrapper.SuppressAbility(); await Assert.That(wrapper.AbilitySuppressed).IsTrue(); await Assert.That(wrapper.ActiveAbility).IsNull(); await Assert.That(inner.Ability).IsSameReferenceAs(naturalAbility); wrapper.OnSwitchedOut(); await Assert.That(wrapper.AbilitySuppressed).IsFalse(); await Assert.That(wrapper.ActiveAbility).IsSameReferenceAs(naturalAbility); } [Test] public async Task GetOwnScriptsUsesUnderlyingStatusScript() { var (_, wrapper, inner) = CreateBattleWithWrapper(); var scripts = new List>(); wrapper.GetOwnScripts(scripts); await Assert.That(scripts.Count).IsEqualTo(4); await Assert.That(scripts[0]).IsSameReferenceAs(wrapper.HeldItemTriggerScript); await Assert.That(scripts[1]).IsSameReferenceAs(wrapper.AbilityScript); await Assert.That(scripts[2]).IsSameReferenceAs(inner.StatusScript); await Assert.That(scripts[3]).IsSameReferenceAs(wrapper.Volatile); } [Test] public async Task GetBattlePokemonFindsWrapperByInnerAndByWrapper() { var (battle, wrapper, inner) = CreateBattleWithWrapper(); var party = battle.Parties[0]; await Assert.That(party.GetBattlePokemon(inner)).IsSameReferenceAs(wrapper); await Assert.That(party.GetBattlePokemon(wrapper)).IsSameReferenceAs(wrapper); await Assert.That(battle.Parties[1].GetBattlePokemon(inner)).IsNull(); } [Test] public async Task OnSwitchedInTracksTurnAndPosition() { var (_, wrapper, _) = CreateBattleWithWrapper(); await Assert.That(wrapper.IsOnBattlefield).IsFalse(); wrapper.OnSwitchedIn(0); await Assert.That(wrapper.IsOnBattlefield).IsTrue(); await Assert.That(wrapper.Position).IsEqualTo((byte)0); // Bulbasaur's ability (Overgrow) has no script in the Gen7 plugin, so the ability script stays empty. await Assert.That(wrapper.AbilityScript.IsEmpty).IsTrue(); } }