Files
PkmnLib.NET/Plugins/PkmnLib.Plugin.Gen7.Tests/Scripts/Moves/FacadeTests.cs

283 lines
9.6 KiB
C#

using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Plugin.Gen7.Scripts.Status;
using PkmnLib.Static.Moves;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
/// <summary>
/// Tests for the <see cref="Facade"/> move script.
/// Gen VII Bulbapedia behavior: "Facade inflicts damage. Its base power doubles from 70 to 140 if the user is
/// poisoned, paralyzed, or burned." Generation VI onwards: "When using Facade, Burn's effect of halving the
/// damage done by physical moves is now ignored."
/// </summary>
public class FacadeTests
{
/// <summary>
/// Creates a fully mocked test setup where the user has the given (or no) non-volatile status in its
/// <see cref="IBattlePokemon.StatusScript"/>, and is using a move of the given category.
/// </summary>
private static (Facade facade, IExecutingMove move, IBattlePokemon target) CreateTestSetup(Script? status,
MoveCategory category = MoveCategory.Physical)
{
var facade = new Facade();
var move = Substitute.For<IExecutingMove>();
var target = Substitute.For<IBattlePokemon>();
var user = Substitute.For<IBattlePokemon>();
user.StatusScript.Returns(status == null ? new ScriptContainer() : new ScriptContainer(status));
move.User.Returns(user);
var useMove = Substitute.For<IMoveData>();
useMove.Category.Returns(category);
move.UseMove.Returns(useMove);
return (facade, move, target);
}
/// <summary>
/// Bulbapedia: "Its base power doubles from 70 to 140 if the user is poisoned, paralyzed, or burned."
/// The user is burned.
/// </summary>
[Test]
public async Task ChangeBasePower_UserBurned_BasePowerDoubles()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Burned());
ushort basePower = 70;
// Act
facade.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)140);
}
/// <summary>
/// Bulbapedia: "Its base power doubles from 70 to 140 if the user is poisoned, paralyzed, or burned."
/// The user is paralyzed.
/// </summary>
[Test]
public async Task ChangeBasePower_UserParalyzed_BasePowerDoubles()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Paralyzed());
ushort basePower = 70;
// Act
facade.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)140);
}
/// <summary>
/// Bulbapedia: "Its base power doubles from 70 to 140 if the user is poisoned, paralyzed, or burned."
/// The user is poisoned.
/// </summary>
[Test]
public async Task ChangeBasePower_UserPoisoned_BasePowerDoubles()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Poisoned());
ushort basePower = 70;
// Act
facade.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)140);
}
/// <summary>
/// Bulbapedia: "Its base power doubles from 70 to 140 if the user is poisoned, paralyzed, or burned."
/// Bad poison is a form of poison, so a badly poisoned user (<see cref="BadlyPoisoned"/>) also gets the
/// doubled base power.
/// </summary>
[Test]
public async Task ChangeBasePower_UserBadlyPoisoned_BasePowerDoubles()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new BadlyPoisoned());
ushort basePower = 70;
// Act
facade.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)140);
}
/// <summary>
/// Bulbapedia: the base power only doubles "if the user is poisoned, paralyzed, or burned" — with no
/// status the base power is unchanged.
/// </summary>
[Test]
public async Task ChangeBasePower_UserHasNoStatus_BasePowerUnchanged()
{
// Arrange
var (facade, move, target) = CreateTestSetup(null);
ushort basePower = 70;
// Act
facade.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)70);
}
/// <summary>
/// Bulbapedia: the base power only doubles "if the user is poisoned, paralyzed, or burned" — freeze is
/// not one of the boosting statuses.
/// </summary>
[Test]
public async Task ChangeBasePower_UserFrozen_BasePowerUnchanged()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Frozen());
ushort basePower = 70;
// Act
facade.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)70);
}
/// <summary>
/// Bulbapedia: the base power only doubles "if the user is poisoned, paralyzed, or burned" — sleep is
/// not one of the boosting statuses.
/// </summary>
[Test]
public async Task ChangeBasePower_UserAsleep_BasePowerUnchanged()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Sleep());
ushort basePower = 70;
// Act
facade.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo((ushort)70);
}
/// <summary>
/// Technical test: doubling a very large base power clamps at <see cref="ushort.MaxValue"/> instead of
/// overflowing.
/// </summary>
[Test]
public async Task ChangeBasePower_UserBurnedWithLargeBasePower_ClampsAtMaxValue()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Burned());
ushort basePower = ushort.MaxValue - 100;
// Act
facade.ChangeBasePower(move, target, 0, ref basePower);
// Assert
await Assert.That(basePower).IsEqualTo(ushort.MaxValue);
}
/// <summary>
/// Bulbapedia (Generation VI onwards): "When using Facade, Burn's effect of halving the damage done by
/// physical moves is now ignored."
///
/// <see cref="Facade"/> implements this by pre-doubling the damage so that the halving <see cref="Burned"/>
/// applies afterwards cancels out. Both hooks are registered on the executing move, and
/// <c>ExecutingMoveImpl.CollectScripts</c> yields the move script before the user's status script, so this
/// drives them in that order.
///
/// This test only pins the composition; that each half individually does its part is covered by
/// <see cref="ChangeMoveDamage_BurnedUser_DamageDoubledToOffsetBurn"/> and
/// <see cref="ChangeMoveDamage_BurnedUserPhysicalMoveWithoutFacade_DamageHalved"/>.
/// </summary>
[Test]
public async Task ChangeMoveDamage_BurnedUserUsingFacade_BurnHalvingIgnored()
{
// Arrange
var burned = new Burned();
var (facade, move, target) = CreateTestSetup(burned);
uint damage = 100;
// Act
facade.ChangeMoveDamage(move, target, 0, ref damage);
burned.ChangeMoveDamage(move, target, 0, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(100u);
}
/// <summary>
/// The half of the above that belongs to <see cref="Facade"/>: a burned user's physical damage is doubled,
/// offsetting the halving <see cref="Burned"/> applies later in the hook chain.
/// </summary>
[Test]
public async Task ChangeMoveDamage_BurnedUser_DamageDoubledToOffsetBurn()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Burned());
uint damage = 100;
// Act
facade.ChangeMoveDamage(move, target, 0, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(200u);
}
/// <summary>
/// The half of the above that belongs to <see cref="Burned"/>: without Facade's compensating hook, a burned
/// user's physical damage is halved. Bulbapedia: burn "halves damage dealt by a burned Pokémon's physical
/// moves".
/// </summary>
[Test]
public async Task ChangeMoveDamage_BurnedUserPhysicalMoveWithoutFacade_DamageHalved()
{
// Arrange
var burned = new Burned();
var (_, move, target) = CreateTestSetup(burned);
uint damage = 100;
// Act
burned.ChangeMoveDamage(move, target, 0, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(50u);
}
/// <summary>
/// Burn is the only status whose damage penalty Facade offsets — a paralyzed user gets the doubled base
/// power but no damage compensation, since paralysis does not reduce damage.
/// </summary>
[Test]
public async Task ChangeMoveDamage_ParalyzedUser_DamageUnchanged()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Paralyzed());
uint damage = 100;
// Act
facade.ChangeMoveDamage(move, target, 0, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(100u);
}
/// <summary>
/// Burn only halves physical damage, so Facade must not compensate for a special move. Facade itself is
/// physical; this pins the category guard rather than an in-game scenario.
/// </summary>
[Test]
public async Task ChangeMoveDamage_BurnedUserSpecialMove_DamageUnchanged()
{
// Arrange
var (facade, move, target) = CreateTestSetup(new Burned(), MoveCategory.Special);
uint damage = 100;
// Act
facade.ChangeMoveDamage(move, target, 0, ref damage);
// Assert
await Assert.That(damage).IsEqualTo(100u);
}
}