diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 47dc8092a3..17163db376 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -1380,6 +1380,12 @@ .byte \counter .4byte \ptr .endm + + .macro jumpifcantloseitem battler:req, ptr:req + callnative BS_JumpIfCantLoseItem + .byte \battler + .4byte \ptr + .endm @ various command changed to more readable macros .macro cancelmultiturnmoves battler:req diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index ec6958d94e..85ed815510 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -435,6 +435,30 @@ gBattleScriptsForMoveEffects:: .4byte BattleScript_EffectHit @ EFFECT_COLLISION_COURSE .4byte BattleScript_EffectSpinOut @ EFFECT_SPIN_OUT .4byte BattleScript_EffectMakeItRain @ EFFECT_MAKE_IT_RAIN + .4byte BattleScript_EffectCorrosiveGas @ EFFECT_CORROSIVE_GAS + +BattleScript_EffectCorrosiveGas: + attackcanceler + accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE + attackstring + ppreduce + jumpifsubstituteblocks BattleScript_CorrosiveGasFail + jumpifcantloseitem BS_TARGET, BattleScript_CorrosiveGasFail + attackanimation + waitanimation + jumpifability BS_TARGET, ABILITY_STICKY_HOLD, BattleScript_StickyHoldActivates + setlastuseditem BS_TARGET + removeitem BS_TARGET + printstring STRINGID_PKMNITEMMELTED + waitmessage B_WAIT_TIME_LONG + goto BattleScript_MoveEnd + +BattleScript_CorrosiveGasFail: + pause B_WAIT_TIME_SHORT + orhalfword gMoveResultFlags, MOVE_RESULT_FAILED + printstring STRINGID_NOEFFECTONTARGET + waitmessage B_WAIT_TIME_LONG + goto BattleScript_MoveEnd BattleScript_EffectMakeItRain: setmoveeffect MOVE_EFFECT_PAYDAY diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index bb38d86599..27cc037af4 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -412,7 +412,8 @@ #define EFFECT_COLLISION_COURSE 406 #define EFFECT_SPIN_OUT 407 #define EFFECT_MAKE_IT_RAIN 408 +#define EFFECT_CORROSIVE_GAS 409 -#define NUM_BATTLE_MOVE_EFFECTS 409 +#define NUM_BATTLE_MOVE_EFFECTS 410 #endif // GUARD_CONSTANTS_BATTLE_MOVE_EFFECTS_H diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 174c611e2f..5c8dc865dc 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -664,8 +664,9 @@ #define STRINGID_SNOWCONTINUES 662 #define STRINGID_SNOWSTOPPED 663 #define STRINGID_SNOWWARNINGSNOW 664 +#define STRINGID_PKMNITEMMELTED 665 -#define BATTLESTRINGS_COUNT 665 +#define BATTLESTRINGS_COUNT 666 // This is the string id that gBattleStringsTable starts with. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/src/battle_message.c b/src/battle_message.c index c8f05fb9af..c4de3c06e8 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -799,9 +799,11 @@ static const u8 sText_ItemCuredSpeciesStatus[] = _("{B_BUFF1} had\nits status he static const u8 sText_ItemRestoredSpeciesPP[] = _("{B_BUFF1} had its\nPP restored!"); static const u8 sText_AtkTrappedDef[] = _("{B_ATK_NAME_WITH_PREFIX} trapped\nthe {B_DEF_NAME_WITH_PREFIX}!"); static const u8 sText_MirrorHerbCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} used its {B_LAST_ITEM}\nto mirror its opponent's stat changes!"); +static const u8 sText_PkmnItemMelted[] = _("{B_ATK_NAME_WITH_PREFIX} corroded\n{B_DEF_NAME_WITH_PREFIX}'s {B_LAST_ITEM}!"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { + [STRINGID_PKMNITEMMELTED - BATTLESTRINGS_TABLE_START] = sText_PkmnItemMelted, [STRINGID_MIRRORHERBCOPIED - BATTLESTRINGS_TABLE_START] = sText_MirrorHerbCopied, [STRINGID_THUNDERCAGETRAPPED - BATTLESTRINGS_TABLE_START] = sText_AtkTrappedDef, [STRINGID_ITEMRESTOREDSPECIESHEALTH - BATTLESTRINGS_TABLE_START] = sText_ItemRestoredSpeciesHealth, diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 1059967ed5..006530e5fb 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -7756,7 +7756,9 @@ static void Cmd_removeitem(void) itemId = gBattleMons[gActiveBattler].item; // Popped Air Balloon cannot be restored by any means. - if (GetBattlerHoldEffect(gActiveBattler, TRUE) != HOLD_EFFECT_AIR_BALLOON) + // Corroded items cannot be restored either. + if (GetBattlerHoldEffect(gActiveBattler, TRUE) != HOLD_EFFECT_AIR_BALLOON + && gBattleMoves[gCurrentMove].effect != EFFECT_CORROSIVE_GAS) gBattleStruct->usedHeldItems[gBattlerPartyIndexes[gActiveBattler]][GetBattlerSide(gActiveBattler)] = itemId; // Remember if switched out gBattleMons[gActiveBattler].item = ITEM_NONE; @@ -9932,7 +9934,8 @@ static void Cmd_various(void) if (gBattleMons[gBattlerAttacker].item == ITEM_NONE || gBattleMons[gBattlerTarget].item != ITEM_NONE || !CanBattlerGetOrLoseItem(gBattlerAttacker, gBattleMons[gBattlerAttacker].item) - || !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerAttacker].item)) + || !CanBattlerGetOrLoseItem(gBattlerTarget, gBattleMons[gBattlerAttacker].item) + || gWishFutureKnock.knockedOffMons[GetBattlerSide(gBattlerTarget)] & gBitTable[gBattlerPartyIndexes[gBattlerTarget]]) { gBattlescriptCurrInstr = cmd->failInstr; } @@ -16071,6 +16074,18 @@ void BS_CheckParentalBondCounter(void) gBattlescriptCurrInstr = cmd->nextInstr; } +void BS_JumpIfCantLoseItem(void) +{ + NATIVE_ARGS(u8 battler, const u8 *jumpInstr); + u8 battler = GetBattlerForBattleScript(cmd->battler); + u16 item = gBattleMons[battler].item; + + if (item == ITEM_NONE || !CanBattlerGetOrLoseItem(battler, item)) + gBattlescriptCurrInstr = cmd->jumpInstr; + else + gBattlescriptCurrInstr = cmd->nextInstr; +} + void BS_GetBattlerSide(void) { NATIVE_ARGS(u8 battler); diff --git a/src/data/battle_moves.h b/src/data/battle_moves.h index bb1db2f7de..75a7d1b962 100644 --- a/src/data/battle_moves.h +++ b/src/data/battle_moves.h @@ -12302,7 +12302,7 @@ const struct BattleMove gBattleMoves[MOVES_COUNT_Z] = [MOVE_CORROSIVE_GAS] = { - .effect = EFFECT_PLACEHOLDER, // EFFECT_CORROSIVE_GAS, TODO + .effect = EFFECT_CORROSIVE_GAS, .power = 0, .type = TYPE_POISON, .accuracy = 100, diff --git a/test/move_effect_corrosive_gas.c b/test/move_effect_corrosive_gas.c new file mode 100644 index 0000000000..097646cee1 --- /dev/null +++ b/test/move_effect_corrosive_gas.c @@ -0,0 +1,119 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_CORROSIVE_GAS].effect == EFFECT_CORROSIVE_GAS); +} + +SINGLE_BATTLE_TEST("Corrosive Gas destroys the target's item or fails if the target has no item") +{ + u16 item; + + PARAMETRIZE {item = ITEM_NONE; } + PARAMETRIZE {item = ITEM_POTION; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET) {Item(item); } + } WHEN { + TURN { MOVE(player, MOVE_CORROSIVE_GAS); } + } SCENE { + MESSAGE("Wobbuffet used CorrosiveGas!"); + if (item == ITEM_POTION) { + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player); + MESSAGE("Wobbuffet corroded Foe Wobbuffet's Potion!"); + } + else { + MESSAGE("It had no effect on Foe Wobbuffet!"); + } + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +SINGLE_BATTLE_TEST("Corrosive Gas doesn't destroy the item of a Pokemon with the Sticky Hold ability") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_MUK) {Item(ITEM_POISON_BARB); Ability(ABILITY_STICKY_HOLD); } + } WHEN { + TURN { MOVE(player, MOVE_CORROSIVE_GAS); } + } SCENE { + MESSAGE("Wobbuffet used CorrosiveGas!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player); + NOT MESSAGE("Wobbuffet corroded Foe Wobbuffet's Potion!"); + ABILITY_POPUP(opponent, ABILITY_STICKY_HOLD); + MESSAGE("Foe Muk's Sticky Hold made CorrosiveGas ineffective!"); + } THEN { + EXPECT_EQ(opponent->item, ITEM_POISON_BARB); + } +} + +SINGLE_BATTLE_TEST("Items lost to Corrosive Gas cannot be restored by Recycle") +{ + GIVEN { + ASSUME(gBattleMoves[MOVE_RECYCLE].effect == EFFECT_RECYCLE); + PLAYER(SPECIES_WOBBUFFET) {Speed(15); } + OPPONENT(SPECIES_WOBBUFFET) {Item(ITEM_ORAN_BERRY); Speed(10); } + } WHEN { + TURN { MOVE(player, MOVE_CORROSIVE_GAS); MOVE(opponent, MOVE_RECYCLE); } + } SCENE { + MESSAGE("Wobbuffet used CorrosiveGas!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_CORROSIVE_GAS, player); + MESSAGE("Wobbuffet corroded Foe Wobbuffet's Oran Berry!"); + MESSAGE("Foe Wobbuffet used Recycle!"); + MESSAGE("But it failed!"); + } THEN { + EXPECT_EQ(opponent->item, ITEM_NONE); + } +} + +DOUBLE_BATTLE_TEST("Corrosive Gas destroys foes and ally's items if they have one") +{ + // Check it affects all targets in all possible configurations. + u32 j, k, l; + u16 itemOpponentLeft, itemOpponentRight, itemPlayerLeft; + + for (j = 0; j < 2; j++) { + for (k = 0; k < 2; k++) { + for (l = 0; l < 2; l++) { + PARAMETRIZE {itemOpponentLeft = (j & 1) ? ITEM_ORAN_BERRY : ITEM_NONE; + itemOpponentRight = (k & 1) ? ITEM_CHESTO_BERRY : ITEM_NONE; + itemPlayerLeft = (l & 1) ? ITEM_CHERI_BERRY : ITEM_NONE; } + } + } + } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {Item(itemPlayerLeft);} + PLAYER(SPECIES_WYNAUT) {Item(ITEM_SITRUS_BERRY);} + OPPONENT(SPECIES_ABRA) {Item(itemOpponentLeft);} + OPPONENT(SPECIES_KADABRA) {Item(itemOpponentRight);} + } WHEN { + TURN { MOVE(playerRight, MOVE_CORROSIVE_GAS); } + } SCENE { + MESSAGE("Wynaut used CorrosiveGas!"); + if (itemPlayerLeft == ITEM_CHERI_BERRY) { + MESSAGE("Wynaut corroded Wobbuffet's Cheri Berry!"); + } else { + MESSAGE("It had no effect on Wobbuffet!"); + } + if (itemOpponentLeft == ITEM_ORAN_BERRY) { + MESSAGE("Wynaut corroded Foe Abra's Oran Berry!"); + } else { + MESSAGE("It had no effect on Foe Abra!"); + } + if (itemOpponentRight == ITEM_CHESTO_BERRY) { + MESSAGE("Wynaut corroded Foe Kadabra's Chesto Berry!"); + } else { + MESSAGE("It had no effect on Foe Kadabra!"); + } + + } THEN { + EXPECT_EQ(playerRight->item, ITEM_SITRUS_BERRY); // Attacker doesn't lose its item. + EXPECT_EQ(playerLeft->item, ITEM_NONE); + EXPECT_EQ(opponentLeft->item, ITEM_NONE); + EXPECT_EQ(opponentRight->item, ITEM_NONE); + } +}