Files
PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/HealBlockTests.cs
Deukhoofd d2a82b5fe3
All checks were successful
Build / Build (push) Successful in 3m12s
Many more tests and fixes
2026-08-27 17:11:12 +02:00

294 lines
11 KiB
C#

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;
/// <summary>
/// Tests for the <see cref="HealBlock"/> move script and its <see cref="HealBlockEffect"/> 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."
/// </summary>
public class HealBlockTests
{
/// <summary>
/// Creates a mocked Pokémon whose <see cref="IPokemon.Volatile"/> is a real <see cref="ScriptSet"/>,
/// so scripts can be added and can remove themselves.
/// </summary>
private static (IPokemon pokemon, ScriptSet volatileSet) CreatePokemonWithRealVolatile()
{
var pokemon = Substitute.For<IPokemon>();
var volatileSet = new ScriptSet(pokemon);
pokemon.Volatile.Returns(volatileSet);
pokemon.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));
return (pokemon, volatileSet);
}
/// <summary>
/// Creates a mocked move choice whose chosen move does or does not carry the
/// <see cref="MoveFlags.Heal"/> flag.
/// </summary>
private static IMoveChoice CreateMoveChoice(bool hasHealFlag)
{
var moveData = Substitute.For<IMoveData>();
moveData.HasFlag(MoveFlags.Heal).Returns(hasHealFlag);
var learnedMove = Substitute.For<ILearnedMove>();
learnedMove.MoveData.Returns(moveData);
var choice = Substitute.For<IMoveChoice>();
choice.ChosenMove.Returns(learnedMove);
return choice;
}
/// <summary>
/// Creates a mocked executing move whose chosen move does or does not carry the
/// <see cref="MoveFlags.Heal"/> flag.
/// </summary>
private static IExecutingMove CreateExecutingMove(bool hasHealFlag)
{
var moveData = Substitute.For<IMoveData>();
moveData.HasFlag(MoveFlags.Heal).Returns(hasHealFlag);
var learnedMove = Substitute.For<ILearnedMove>();
learnedMove.MoveData.Returns(moveData);
var move = Substitute.For<IExecutingMove>();
move.ChosenMove.Returns(learnedMove);
return move;
}
/// <summary>
/// Bulbapedia: "Heal Block restricts the targets from healing in certain ways for five turns."
/// Using the move attaches the <see cref="HealBlockEffect"/> volatile to the target.
/// </summary>
[Test]
public async Task OnSecondaryEffect_Target_AddsHealBlockEffectToTarget()
{
// Arrange
var script = new HealBlock();
var move = Substitute.For<IExecutingMove>();
var (target, targetVolatile) = CreatePokemonWithRealVolatile();
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<HealBlockEffect>())).IsTrue();
}
/// <summary>
/// Bulbapedia: Heal Block prevents the target from using HP-recovery moves. A move carrying the
/// <see cref="MoveFlags.Heal"/> flag cannot be selected while the effect is active.
/// </summary>
[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();
}
/// <summary>
/// Bulbapedia: only healing moves are restricted; other moves remain selectable while under the
/// effect of Heal Block.
/// </summary>
[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();
}
/// <summary>
/// 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.
/// </summary>
[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<ILearnedMove>();
learnedMove.MoveData.Returns(absorb!);
var choice = Substitute.For<IMoveChoice>();
choice.ChosenMove.Returns(learnedMove);
var effect = new HealBlockEffect();
var prevent = false;
// Act
effect.PreventMoveSelection(choice, ref prevent);
// Assert
await Assert.That(prevent).IsTrue();
}
/// <summary>
/// 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.
/// </summary>
[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();
}
/// <summary>
/// Bulbapedia: only healing moves are restricted; execution of non-healing moves is not prevented.
/// </summary>
[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();
}
/// <summary>
/// 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.
/// </summary>
[Test]
public async Task PreventHeal_AnyHealing_PreventsHeal()
{
// Arrange
var effect = new HealBlockEffect();
var pokemon = Substitute.For<IPokemon>();
var prevented = false;
// Act
effect.PreventHeal(pokemon, 10, false, ref prevented);
// Assert
await Assert.That(prevented).IsTrue();
}
/// <summary>
/// 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
/// <see cref="IBattleSide.SwapPokemon(byte, IPokemon?)"/> call moves such as U-turn make), a damaged
/// Pokémon with Regenerator under Heal Block still restores 1/3 of its max HP.
/// </summary>
[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);
}
/// <summary>
/// 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.
/// </summary>
[Test]
public async Task OnEndTurn_FourTurnsPassed_EffectStillActive()
{
// Arrange
var (pokemon, volatileSet) = CreatePokemonWithRealVolatile();
var effect = new HealBlockEffect();
volatileSet.Add(effect);
var battle = Substitute.For<IBattle>();
// Act
for (var i = 0; i < 4; i++)
effect.OnEndTurn(pokemon, battle);
// Assert
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<HealBlockEffect>())).IsTrue();
}
/// <summary>
/// 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.
/// </summary>
[Test]
public async Task OnEndTurn_FiveTurnsPassed_EffectRemovesItself()
{
// Arrange
var (pokemon, volatileSet) = CreatePokemonWithRealVolatile();
var effect = new HealBlockEffect();
volatileSet.Add(effect);
var battle = Substitute.For<IBattle>();
// Act
for (var i = 0; i < 5; i++)
effect.OnEndTurn(pokemon, battle);
// Assert
await Assert.That(volatileSet.Contains(ScriptUtils.ResolveName<HealBlockEffect>())).IsFalse();
}
}