From c340ae04d78926c9f6fb3dd0aab2244bdc20a653 Mon Sep 17 00:00:00 2001 From: AgustinGDLV <103095241+AgustinGDLV@users.noreply.github.com> Date: Wed, 3 Apr 2024 01:13:39 -0700 Subject: [PATCH] Updated Knock Off (#4333) * added config for updated Knock Off behavior, added tests * fixed unused var * added item check in tests * fixed item checks --- include/config/battle.h | 1 + src/battle_script_commands.c | 12 ++- test/battle/move_effect/knock_off.c | 142 ++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 1 deletion(-) diff --git a/include/config/battle.h b/include/config/battle.h index ffdfc40385..427862534d 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -118,6 +118,7 @@ #define B_IMPRISON GEN_LATEST // In Gen5+, Imprison doesn't fail if opposing pokemon don't have any moves the user knows. #define B_ALLY_SWITCH_FAIL_CHANCE GEN_LATEST // In Gen9, using Ally Switch consecutively decreases the chance of success for each consecutive use. #define B_SKETCH_BANS GEN_LATEST // In Gen9+, Sketch is unable to copy more moves than in previous generations. +#define B_KNOCK_OFF_REMOVAL GEN_LATEST // In Gen5+, Knock Off removes the foe's item instead of rendering it unusable. // Ability settings #define B_EXPANDED_ABILITY_NAMES TRUE // If TRUE, ability names are increased from 12 characters to 16 characters. diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index e3c6b2f7a5..7ff8a9e772 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5341,9 +5341,19 @@ static bool32 TryKnockOffBattleScript(u32 battlerDef) gBattleMons[battlerDef].item = 0; if (gBattleMons[battlerDef].ability != ABILITY_GORILLA_TACTICS) gBattleStruct->choicedMove[battlerDef] = 0; - gWishFutureKnock.knockedOffMons[side] |= gBitTable[gBattlerPartyIndexes[battlerDef]]; CheckSetUnburden(battlerDef); + // In Gen 5+, Knock Off removes the target's item rather than rendering it unusable. + if (B_KNOCK_OFF_REMOVAL >= GEN_5) + { + BtlController_EmitSetMonData(battlerDef, BUFFER_A, REQUEST_HELDITEM_BATTLE, 0, sizeof(gBattleMons[battlerDef].item), &gBattleMons[battlerDef].item); + MarkBattlerForControllerExec(battlerDef); + } + else + { + gWishFutureKnock.knockedOffMons[side] |= gBitTable[gBattlerPartyIndexes[battlerDef]]; + } + BattleScriptPushCursor(); gBattlescriptCurrInstr = BattleScript_KnockedOff; } diff --git a/test/battle/move_effect/knock_off.c b/test/battle/move_effect/knock_off.c index 863cf165d3..177e3b18c1 100644 --- a/test/battle/move_effect/knock_off.c +++ b/test/battle/move_effect/knock_off.c @@ -21,6 +21,8 @@ SINGLE_BATTLE_TEST("Knock Off knocks a healing berry before it has the chance to } ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); MESSAGE("Wobbuffet knocked off Foe Wobbuffet's Sitrus Berry!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); } } @@ -49,5 +51,145 @@ SINGLE_BATTLE_TEST("Knock Off activates after Rocky Helmet and Weakness Policy") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); MESSAGE("Wobbuffet knocked off Foe Wobbuffet's Rocky Helmet!"); } + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Knock Off deals additional damage to opponents holding an item in Gen 6+", s16 damage) +{ + u16 item = 0; + + PARAMETRIZE { item = ITEM_NONE; } + PARAMETRIZE { item = ITEM_LEFTOVERS; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(item); }; + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + HP_BAR(opponent, captureDamage: &results[i].damage); + if (item != ITEM_NONE) + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + } FINALLY { + if (B_KNOCK_OFF_DMG >= GEN_6) + EXPECT_MUL_EQ(results[0].damage, UQ_4_12(1.5), results[1].damage); + else + EXPECT_EQ(results[0].damage, results[1].damage); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + + +SINGLE_BATTLE_TEST("Knock Off does not remove items through Substitute") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); }; + } WHEN { + TURN { MOVE(opponent, MOVE_SUBSTITUTE); + MOVE(player, MOVE_KNOCK_OFF); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + NOT { ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); } + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Recycle cannot recover an item removed by Knock Off") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); + MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off Foe Wobbuffet's Leftovers!"); + + MESSAGE("Foe Wobbuffet used Recycle!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Knock Off does not prevent targets from receiving another item in Gen 5+") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + TURN { MOVE(player, MOVE_BESTOW); } + } SCENE { + // turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off Foe Wobbuffet's Leftovers!"); + // turn 2 + if (B_KNOCK_OFF_REMOVAL >= GEN_5) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT); + MESSAGE("Foe Wobbuffet's Leftovers restored its HP a little!"); + } else { + NOT { ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player); } + MESSAGE("But it failed!"); + } + } THEN { + if (B_KNOCK_OFF_REMOVAL >= GEN_5) + EXPECT(opponent->item == ITEM_LEFTOVERS); + else + EXPECT(opponent->item == ITEM_NONE); + } +} + +// Knock Off triggers Unburden regardless of whether the item is fully removed (Gen 5+) or not. +SINGLE_BATTLE_TEST("Knock Off triggers Unburden") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Speed(60); } + OPPONENT(SPECIES_WOBBUFFET) { Ability(ABILITY_UNBURDEN); Item(ITEM_LEFTOVERS); Speed(50); } + } WHEN { + TURN { MOVE(player, MOVE_KNOCK_OFF); } + TURN { MOVE(player, MOVE_CELEBRATE); } + } SCENE { + // turn 1 + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, player); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Wobbuffet knocked off Foe Wobbuffet's Leftovers!"); + // turn 2 + MESSAGE("Foe Wobbuffet used Celebrate!"); + MESSAGE("Wobbuffet used Celebrate!"); + } THEN { + EXPECT(opponent->item == ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Knock Off does not trigger the opposing ally's Symbiosis") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LEFTOVERS); } + PLAYER(SPECIES_FLORGES) { Item(ITEM_LEFTOVERS); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_KNOCK_OFF, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_KNOCK_OFF, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_ITEM_KNOCKOFF); + MESSAGE("Foe Wobbuffet knocked off Wobbuffet's Leftovers!"); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT); + MESSAGE("Wobbuffet's Leftovers restored health!"); + } + } THEN { + EXPECT(playerLeft->item == ITEM_NONE); } }