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; /// /// Tests for the 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." /// public class FacadeTests { /// /// Creates a fully mocked test setup where the user has the given (or no) non-volatile status in its /// , and is using a move of the given category. /// private static (Facade facade, IExecutingMove move, IBattlePokemon target) CreateTestSetup(Script? status, MoveCategory category = MoveCategory.Physical) { var facade = new Facade(); var move = Substitute.For(); var target = Substitute.For(); var user = Substitute.For(); user.StatusScript.Returns(status == null ? new ScriptContainer() : new ScriptContainer(status)); move.User.Returns(user); var useMove = Substitute.For(); useMove.Category.Returns(category); move.UseMove.Returns(useMove); return (facade, move, target); } /// /// Bulbapedia: "Its base power doubles from 70 to 140 if the user is poisoned, paralyzed, or burned." /// The user is burned. /// [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); } /// /// Bulbapedia: "Its base power doubles from 70 to 140 if the user is poisoned, paralyzed, or burned." /// The user is paralyzed. /// [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); } /// /// Bulbapedia: "Its base power doubles from 70 to 140 if the user is poisoned, paralyzed, or burned." /// The user is poisoned. /// [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); } /// /// 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 () also gets the /// doubled base power. /// [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); } /// /// Bulbapedia: the base power only doubles "if the user is poisoned, paralyzed, or burned" — with no /// status the base power is unchanged. /// [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); } /// /// Bulbapedia: the base power only doubles "if the user is poisoned, paralyzed, or burned" — freeze is /// not one of the boosting statuses. /// [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); } /// /// Bulbapedia: the base power only doubles "if the user is poisoned, paralyzed, or burned" — sleep is /// not one of the boosting statuses. /// [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); } /// /// Technical test: doubling a very large base power clamps at instead of /// overflowing. /// [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); } /// /// Bulbapedia (Generation VI onwards): "When using Facade, Burn's effect of halving the damage done by /// physical moves is now ignored." /// /// implements this by pre-doubling the damage so that the halving /// applies afterwards cancels out. Both hooks are registered on the executing move, and /// ExecutingMoveImpl.CollectScripts 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 /// and /// . /// [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); } /// /// The half of the above that belongs to : a burned user's physical damage is doubled, /// offsetting the halving applies later in the hook chain. /// [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); } /// /// The half of the above that belongs to : 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". /// [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); } /// /// 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. /// [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); } /// /// 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. /// [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); } }