283 lines
9.6 KiB
C#
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);
|
|
}
|
|
} |