using PkmnLib.Dynamic.Events; using PkmnLib.Dynamic.Models; using PkmnLib.Dynamic.Models.Choices; using PkmnLib.Dynamic.ScriptHandling; using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Plugin.Gen7.Common; using PkmnLib.Plugin.Gen7.Scripts.Moves; using PkmnLib.Plugin.Gen7.Scripts.Pokemon; using PkmnLib.Static.Moves; using PkmnLib.Static.Species; using PkmnLib.Static.Utils; namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves; /// /// Tests for the move script and its volatile. /// Gen VII Bulbapedia behavior: "Heal Block restricts the targets from healing in certain ways for five /// turns." Affected Pokémon cannot use HP-recovery moves, and since Generation VI "Affected Pokémon can no /// longer use HP-draining moves." Since Generation V they can "no longer be healed by Black Sludge, /// Leftovers, or Shell Bell", but "Heal Block will not prevent Pokémon with the Ability Regenerator from /// having their HP restored upon switching out." /// public class HealBlockTests { /// /// Creates a mocked Pokémon whose is a real , /// so scripts can be added and can remove themselves. /// private static (IPokemon pokemon, ScriptSet volatileSet) CreatePokemonWithRealVolatile() { var pokemon = Substitute.For(); var volatileSet = new ScriptSet(pokemon); pokemon.Volatile.Returns(volatileSet); pokemon.GetScripts().Returns(_ => new ScriptIterator(new List>())); return (pokemon, volatileSet); } /// /// Creates a mocked move choice whose chosen move does or does not carry the /// flag. /// private static IMoveChoice CreateMoveChoice(bool hasHealFlag) { var moveData = Substitute.For(); moveData.HasFlag(MoveFlags.Heal).Returns(hasHealFlag); var learnedMove = Substitute.For(); learnedMove.MoveData.Returns(moveData); var choice = Substitute.For(); choice.ChosenMove.Returns(learnedMove); return choice; } /// /// Creates a mocked executing move whose chosen move does or does not carry the /// flag. /// private static IExecutingMove CreateExecutingMove(bool hasHealFlag) { var moveData = Substitute.For(); moveData.HasFlag(MoveFlags.Heal).Returns(hasHealFlag); var learnedMove = Substitute.For(); learnedMove.MoveData.Returns(moveData); var move = Substitute.For(); move.ChosenMove.Returns(learnedMove); return move; } /// /// Bulbapedia: "Heal Block restricts the targets from healing in certain ways for five turns." /// Using the move attaches the volatile to the target. /// [Test] public async Task OnSecondaryEffect_Target_AddsHealBlockEffectToTarget() { // Arrange var script = new HealBlock(); var move = Substitute.For(); var (target, targetVolatile) = CreatePokemonWithRealVolatile(); // Act script.OnSecondaryEffect(move, target, 0); // Assert await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName())).IsTrue(); } /// /// Bulbapedia: Heal Block prevents the target from using HP-recovery moves. A move carrying the /// flag cannot be selected while the effect is active. /// [Test] public async Task PreventMoveSelection_HealingMove_PreventsSelection() { // Arrange var effect = new HealBlockEffect(); var choice = CreateMoveChoice(true); var prevent = false; // Act effect.PreventMoveSelection(choice, ref prevent); // Assert await Assert.That(prevent).IsTrue(); } /// /// Bulbapedia: only healing moves are restricted; other moves remain selectable while under the /// effect of Heal Block. /// [Test] public async Task PreventMoveSelection_NonHealingMove_DoesNotPreventSelection() { // Arrange var effect = new HealBlockEffect(); var choice = CreateMoveChoice(false); var prevent = false; // Act effect.PreventMoveSelection(choice, ref prevent); // Assert await Assert.That(prevent).IsFalse(); } /// /// Bulbapedia (Generations VI to VII): "Affected Pokémon can no longer use HP-draining moves." /// Integration check: Absorb's actual Gen7 move data carries the heal flag, so selecting it is /// prevented while under the effect of Heal Block. /// [Test] public async Task PreventMoveSelection_DrainMoveFromGen7Data_PreventsSelection() { // Arrange var library = LibraryHelpers.LoadLibrary(); await Assert.That(library.StaticLibrary.Moves.TryGet("absorb", out var absorb)).IsTrue(); var learnedMove = Substitute.For(); learnedMove.MoveData.Returns(absorb!); var choice = Substitute.For(); choice.ChosenMove.Returns(learnedMove); var effect = new HealBlockEffect(); var prevent = false; // Act effect.PreventMoveSelection(choice, ref prevent); // Assert await Assert.That(prevent).IsTrue(); } /// /// Bulbapedia: Heal Block prevents the target from using HP-recovery moves. If a healing move would /// still execute (e.g. it was chosen before Heal Block hit), its execution is prevented. /// [Test] public async Task PreventMove_HealingMove_PreventsExecution() { // Arrange var effect = new HealBlockEffect(); var move = CreateExecutingMove(true); var prevent = false; // Act effect.PreventMove(move, ref prevent); // Assert await Assert.That(prevent).IsTrue(); } /// /// Bulbapedia: only healing moves are restricted; execution of non-healing moves is not prevented. /// [Test] public async Task PreventMove_NonHealingMove_DoesNotPreventExecution() { // Arrange var effect = new HealBlockEffect(); var move = CreateExecutingMove(false); var prevent = false; // Act effect.PreventMove(move, ref prevent); // Assert await Assert.That(prevent).IsFalse(); } /// /// Bulbapedia (Generation V onwards): "Affected Pokémon can no longer be healed by Black Sludge, /// Leftovers, or Shell Bell", nor by other healing effects. Any incoming heal on the affected /// Pokémon is prevented. /// [Test] public async Task PreventHeal_AnyHealing_PreventsHeal() { // Arrange var effect = new HealBlockEffect(); var pokemon = Substitute.For(); var prevented = false; // Act effect.PreventHeal(pokemon, 10, false, ref prevented); // Assert await Assert.That(prevented).IsTrue(); } /// /// Bulbapedia (Generation V onwards): "Heal Block will not prevent Pokémon with the Ability Regenerator /// from having their HP restored upon switching out." Run through the real switch-out flow (the direct /// call moves such as U-turn make), a damaged /// Pokémon with Regenerator under Heal Block still restores 1/3 of its max HP. /// [Test] public async Task SwitchOut_RegeneratorUnderHealBlock_RestoresThirdOfMaxHealth() { // Arrange var library = LibraryHelpers.LoadLibrary(); await Assert.That(library.StaticLibrary.Species.TryGet("mienfoo", out var species)).IsTrue(); IPokemon CreateMienfoo(byte abilityIndex) => new PokemonImpl(library, species!, species!.GetDefaultForm(), new AbilityIndex { IsHidden = false, Index = abilityIndex }, 50, 0, Gender.Male, 0, "hardy"); var regeneratorMon = CreateMienfoo(1); await Assert.That(regeneratorMon.ActiveAbility!.Name).IsEqualTo(new StringKey("regenerator")); var benchMon = CreateMienfoo(0); var opponent = CreateMienfoo(0); var party = new PokemonPartyImpl(6); party.SwapInto(regeneratorMon, 0); party.SwapInto(benchMon, 1); var opponentParty = new PokemonPartyImpl(6); opponentParty.SwapInto(opponent, 0); using var battle = new BattleImpl(library, [ new BattlePartyImpl(party, [new ResponsibleIndex(0, 0)]), new BattlePartyImpl(opponentParty, [new ResponsibleIndex(1, 0)]), ], false, 2, 1, false, "grass", 10); battle.Sides[0].SwapPokemon(0, regeneratorMon); battle.Sides[1].SwapPokemon(0, opponent); regeneratorMon.Damage(60, DamageSource.MoveDamage, new EventBatchId()); regeneratorMon.Volatile.Add(new HealBlockEffect()); // Sanity check: the effect is live and blocks an ordinary heal. await Assert.That(regeneratorMon.Heal(10)).IsFalse(); var healthBeforeSwitch = regeneratorMon.CurrentHealth; // Act battle.Sides[0].SwapPokemon(0, benchMon); // Assert await Assert.That(regeneratorMon.CurrentHealth).IsEqualTo(healthBeforeSwitch + regeneratorMon.MaxHealth / 3); } /// /// Bulbapedia: "Heal Block restricts the targets from healing in certain ways for five turns." /// After four end-of-turn ticks the effect is still active. /// [Test] public async Task OnEndTurn_FourTurnsPassed_EffectStillActive() { // Arrange var (pokemon, volatileSet) = CreatePokemonWithRealVolatile(); var effect = new HealBlockEffect(); volatileSet.Add(effect); var battle = Substitute.For(); // Act for (var i = 0; i < 4; i++) effect.OnEndTurn(pokemon, battle); // Assert await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName())).IsTrue(); } /// /// Bulbapedia: "Heal Block restricts the targets from healing in certain ways for five turns." /// After the fifth end-of-turn tick the effect removes itself. /// [Test] public async Task OnEndTurn_FiveTurnsPassed_EffectRemovesItself() { // Arrange var (pokemon, volatileSet) = CreatePokemonWithRealVolatile(); var effect = new HealBlockEffect(); volatileSet.Add(effect); var battle = Substitute.For(); // Act for (var i = 0; i < 5; i++) effect.OnEndTurn(pokemon, battle); // Assert await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName())).IsFalse(); } }