From fa81861593812fae26a5fc86aa4ce3eb710a82a1 Mon Sep 17 00:00:00 2001 From: sneed <56992013+Sneed69@users.noreply.github.com> Date: Fri, 7 Jun 2024 19:51:21 +0300 Subject: [PATCH] Add newer generation Heal Bell interactions with Soundproof (#4732) * newer generation soundproof heal bell interactions * Fix bugs and rewrite AnyPartyMemberStatused * add missing check, tests, clean up * fix ai code and rename battler for clarity --- include/config/battle.h | 1 + src/battle_ai_util.c | 37 +++++++--- src/battle_script_commands.c | 39 ++++++----- src/data/moves_info.h | 4 +- test/battle/ai_check_viability.c | 48 +++++++++++++ test/battle/move_effect/heal_bell.c | 102 ++++++++++++++++++++++++++++ 6 files changed, 201 insertions(+), 30 deletions(-) create mode 100644 test/battle/move_effect/heal_bell.c diff --git a/include/config/battle.h b/include/config/battle.h index c072cb7993..1af7e0c68a 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -120,6 +120,7 @@ #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. +#define B_HEAL_BELL_SOUNDPROOF GEN_LATEST // In Gen5, Heal Bell affects all mons with Soundproof. In Gen6-8 it affects inactive mons, but not battlers. In Gen9 it always affects the user. // 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_ai_util.c b/src/battle_ai_util.c index 0b6d7b3b9a..3ab39ab337 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2955,18 +2955,41 @@ bool32 IsWakeupTurn(u32 battler) bool32 AnyPartyMemberStatused(u32 battlerId, bool32 checkSoundproof) { struct Pokemon *party; - u32 i; + u32 i, battlerOnField1, battlerOnField2; if (GetBattlerSide(battlerId) == B_SIDE_PLAYER) party = gPlayerParty; else party = gEnemyParty; + if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE) + { + battlerOnField1 = gBattlerPartyIndexes[battlerId]; + battlerOnField2 = gBattlerPartyIndexes[GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(battlerId)))]; + // Check partner's status + if ((B_HEAL_BELL_SOUNDPROOF == GEN_5 || AI_DATA->abilities[BATTLE_PARTNER(battlerId)] != ABILITY_SOUNDPROOF || !checkSoundproof) + && GetMonData(&party[battlerOnField2], MON_DATA_STATUS) != STATUS1_NONE) + return TRUE; + } + else // In singles there's only one battlerId by side. + { + battlerOnField1 = gBattlerPartyIndexes[battlerId]; + battlerOnField2 = gBattlerPartyIndexes[battlerId]; + } + + // Check attacker's status + if ((B_HEAL_BELL_SOUNDPROOF == GEN_5 || B_HEAL_BELL_SOUNDPROOF >= GEN_9 + || AI_DATA->abilities[battlerId] != ABILITY_SOUNDPROOF || !checkSoundproof) + && GetMonData(&party[battlerOnField1], MON_DATA_STATUS) != STATUS1_NONE) + return TRUE; + + // Check inactive party mons' status for (i = 0; i < PARTY_SIZE; i++) { - if (checkSoundproof && GetMonAbility(&party[i]) == ABILITY_SOUNDPROOF) + if (i == battlerOnField1 || i == battlerOnField2) + continue; + if (B_HEAL_BELL_SOUNDPROOF < GEN_5 && checkSoundproof && GetMonAbility(&party[i]) == ABILITY_SOUNDPROOF) continue; - if (GetMonData(&party[i], MON_DATA_STATUS) != STATUS1_NONE) return TRUE; } @@ -3220,7 +3243,7 @@ bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move) u32 i; s32 firstId, lastId; struct Pokemon* party; - bool32 hasStatus = FALSE; + bool32 hasStatus = AnyPartyMemberStatused(battlerAtk, gMovesInfo[move].soundMove); bool32 needHealing = FALSE; GetAIPartyIndexes(battlerAtk, &firstId, &lastId); @@ -3246,12 +3269,6 @@ bool32 ShouldUseWishAromatherapy(u32 battlerAtk, u32 battlerDef, u32 move) { needHealing = TRUE; } - - if (GetMonData(&party[i], MON_DATA_STATUS, NULL) != STATUS1_NONE) - { - if (move != MOVE_HEAL_BELL || GetMonAbility(&party[i]) != ABILITY_SOUNDPROOF) - hasStatus = TRUE; - } } } diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 40038aa951..ceb4220523 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -13168,7 +13168,7 @@ static void Cmd_healpartystatus(void) CMD_ARGS(); u32 zero = 0; - u32 battler; + u32 partner = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker))); u8 toHeal = 0; if (gCurrentMove == MOVE_HEAL_BELL) @@ -13178,7 +13178,8 @@ static void Cmd_healpartystatus(void) gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_BELL; - if (GetBattlerAbility(gBattlerAttacker) != ABILITY_SOUNDPROOF) + if (GetBattlerAbility(gBattlerAttacker) != ABILITY_SOUNDPROOF + || B_HEAL_BELL_SOUNDPROOF == GEN_5 || B_HEAL_BELL_SOUNDPROOF >= GEN_9) { gBattleMons[gBattlerAttacker].status1 = 0; gBattleMons[gBattlerAttacker].status2 &= ~STATUS2_NIGHTMARE; @@ -13189,19 +13190,18 @@ static void Cmd_healpartystatus(void) gBattleCommunication[MULTISTRING_CHOOSER] |= B_MSG_BELL_SOUNDPROOF_ATTACKER; } - battler = gBattleScripting.battler = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker))); + gBattleScripting.battler = partner; - if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && !(gAbsentBattlerFlags & gBitTable[battler])) + if (IsBattlerAlive(partner)) { - if (GetBattlerAbility(battler) != ABILITY_SOUNDPROOF) + if (GetBattlerAbility(partner) != ABILITY_SOUNDPROOF || B_HEAL_BELL_SOUNDPROOF == GEN_5) { - gBattleMons[battler].status1 = 0; - gBattleMons[battler].status2 &= ~STATUS2_NIGHTMARE; + gBattleMons[partner].status1 = 0; + gBattleMons[partner].status2 &= ~STATUS2_NIGHTMARE; } else { - RecordAbilityBattle(battler, gBattleMons[battler].ability); + RecordAbilityBattle(partner, gBattleMons[partner].ability); gBattleCommunication[MULTISTRING_CHOOSER] |= B_MSG_BELL_SOUNDPROOF_PARTNER; } } @@ -13216,13 +13216,17 @@ static void Cmd_healpartystatus(void) if (species != SPECIES_NONE && species != SPECIES_EGG) { u16 ability; + bool32 isAttacker = gBattlerPartyIndexes[gBattlerAttacker] == i; + bool32 isDoublesPartner = gBattlerPartyIndexes[partner] == i && IsBattlerAlive(partner); - if (gBattlerPartyIndexes[gBattlerAttacker] == i) + if (B_HEAL_BELL_SOUNDPROOF == GEN_5 || (isAttacker && B_HEAL_BELL_SOUNDPROOF >= GEN_9)) + ability = ABILITY_NONE; + else if (B_HEAL_BELL_SOUNDPROOF > GEN_5 && !isAttacker && !isDoublesPartner) + ability = ABILITY_NONE; + else if (isAttacker) ability = GetBattlerAbility(gBattlerAttacker); - else if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && gBattlerPartyIndexes[battler] == i - && !(gAbsentBattlerFlags & gBitTable[battler])) - ability = GetBattlerAbility(battler); + else if (isDoublesPartner) + ability = GetBattlerAbility(partner); else ability = GetAbilityBySpecies(species, abilityNum); @@ -13239,12 +13243,11 @@ static void Cmd_healpartystatus(void) gBattleMons[gBattlerAttacker].status1 = 0; gBattleMons[gBattlerAttacker].status2 &= ~STATUS2_NIGHTMARE; - battler = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerAttacker))); if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE - && !(gAbsentBattlerFlags & gBitTable[battler])) + && !(gAbsentBattlerFlags & gBitTable[partner])) { - gBattleMons[battler].status1 = 0; - gBattleMons[battler].status2 &= ~STATUS2_NIGHTMARE; + gBattleMons[partner].status1 = 0; + gBattleMons[partner].status2 &= ~STATUS2_NIGHTMARE; } } diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 2a4846955d..a1fd2f18f8 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -5477,7 +5477,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .type = TYPE_NORMAL, .accuracy = 0, .pp = 5, - .target = MOVE_TARGET_USER | MOVE_TARGET_ALLY, + .target = MOVE_TARGET_USER, .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_RECOVER_HP }, @@ -7912,7 +7912,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] = .type = TYPE_GRASS, .accuracy = 0, .pp = 5, - .target = MOVE_TARGET_USER | MOVE_TARGET_ALLY, + .target = MOVE_TARGET_USER, .priority = 0, .category = DAMAGE_CATEGORY_STATUS, .zMove = { .effect = Z_EFFECT_RECOVER_HP }, diff --git a/test/battle/ai_check_viability.c b/test/battle/ai_check_viability.c index dcbcf8dbec..2e75f31903 100644 --- a/test/battle/ai_check_viability.c +++ b/test/battle/ai_check_viability.c @@ -191,3 +191,51 @@ AI_SINGLE_BATTLE_TEST("AI chooses moves with secondary effect that have a 100% c TURN { EXPECT_MOVES(opponent, MOVE_OCTAZOOKA); } } } + +AI_DOUBLE_BATTLE_TEST("AI chooses moves that cure self or partner") +{ + u32 status1_0, status1_1, partnerAbility; + + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_NONE; partnerAbility = ABILITY_SCRAPPY; } + PARAMETRIZE { status1_0 = STATUS1_TOXIC_POISON; status1_1 = STATUS1_NONE; partnerAbility = ABILITY_SCRAPPY; } + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_PARALYSIS; partnerAbility = ABILITY_SCRAPPY; } + PARAMETRIZE { status1_0 = STATUS1_NONE; status1_1 = STATUS1_PARALYSIS; partnerAbility = ABILITY_SOUNDPROOF; } + + GIVEN { + ASSUME(gMovesInfo[MOVE_HEAL_BELL].effect == EFFECT_HEAL_BELL); + ASSUME(B_HEAL_BELL_SOUNDPROOF >= GEN_9); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGIROCK) { Moves(MOVE_ROCK_SLIDE, MOVE_HEAL_BELL, MOVE_ACID); Status1(status1_0); } + OPPONENT(SPECIES_EXPLOUD) { Status1(status1_1); Ability(partnerAbility); } + } WHEN { + if (status1_0 != STATUS1_NONE || (status1_1 != STATUS1_NONE && partnerAbility != ABILITY_SOUNDPROOF)) + TURN { EXPECT_MOVE(opponentLeft, MOVE_HEAL_BELL); } + else + TURN { EXPECT_MOVE(opponentLeft, MOVE_ROCK_SLIDE); } + } +} + +AI_SINGLE_BATTLE_TEST("AI chooses moves that cure inactive party members") +{ + u32 status, ability; + + PARAMETRIZE { status = STATUS1_TOXIC_POISON; ability = ABILITY_SCRAPPY; } + PARAMETRIZE { status = STATUS1_NONE; ability = ABILITY_SCRAPPY; } + PARAMETRIZE { status = STATUS1_TOXIC_POISON; ability = ABILITY_SOUNDPROOF; } + + GIVEN { + ASSUME(gMovesInfo[MOVE_HEAL_BELL].effect == EFFECT_HEAL_BELL); + ASSUME(B_HEAL_BELL_SOUNDPROOF >= GEN_5); + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_REGIROCK) { Moves(MOVE_BODY_PRESS, MOVE_HEAL_BELL); } + OPPONENT(SPECIES_EXPLOUD) { Status1(status); Ability(ability); } + } WHEN { + if (status == STATUS1_NONE) + TURN { EXPECT_MOVE(opponent, MOVE_BODY_PRESS); } + else + TURN { EXPECT_MOVE(opponent, MOVE_HEAL_BELL); } + } +} diff --git a/test/battle/move_effect/heal_bell.c b/test/battle/move_effect/heal_bell.c new file mode 100644 index 0000000000..7c01d0cfd3 --- /dev/null +++ b/test/battle/move_effect/heal_bell.c @@ -0,0 +1,102 @@ +#include "global.h" +#include "test/battle.h" + +ASSUMPTIONS +{ + ASSUME(gMovesInfo[MOVE_HEAL_BELL].effect == EFFECT_HEAL_BELL); + ASSUME(gMovesInfo[MOVE_AROMATHERAPY].effect == EFFECT_HEAL_BELL); +} + +DOUBLE_BATTLE_TEST("Heal Bell cures the entire party") +{ + u32 move; + + PARAMETRIZE { move = MOVE_HEAL_BELL; } + PARAMETRIZE { move = MOVE_AROMATHERAPY; } + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, move, target: playerLeft); } + TURN { SWITCH(playerLeft, 2); SWITCH(playerRight, 3); } + } SCENE { + int i; + + ANIMATION(ANIM_TYPE_MOVE, move, playerLeft); + NOT MESSAGE("Wobbuffet is hurt by poison!"); + for (i = 0; i < 6; i++) + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_STATUS), STATUS1_NONE); + } +} + +DOUBLE_BATTLE_TEST("Heal Bell does not cure soundproof partners") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_SCRAPPY; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; } + + ASSUME(B_HEAL_BELL_SOUNDPROOF != GEN_5); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_EXPLOUD) { Ability(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_HEAL_BELL, target: playerLeft); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BELL, playerLeft); + if (ability == ABILITY_SOUNDPROOF) { + MESSAGE("Exploud is hurt by poison!"); + } else { + NOT MESSAGE("Exploud is hurt by poison!"); + } + } +} + +SINGLE_BATTLE_TEST("Heal Bell cures inactive soundproof Pokemon") +{ + u32 ability; + + PARAMETRIZE { ability = ABILITY_SCRAPPY; } + PARAMETRIZE { ability = ABILITY_SOUNDPROOF; } + + ASSUME(B_HEAL_BELL_SOUNDPROOF >= GEN_5); + + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_POISON); } + PLAYER(SPECIES_EXPLOUD) { Ability(ability); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_HEAL_BELL, target: player); } + TURN { SWITCH(player, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BELL, player); + SEND_IN_MESSAGE("Exploud"); + NOT MESSAGE("Exploud is hurt by poison!"); + } +} + + +SINGLE_BATTLE_TEST("Heal Bell cures a soundproof user") +{ + ASSUME(B_HEAL_BELL_SOUNDPROOF == GEN_5 || B_HEAL_BELL_SOUNDPROOF >= GEN_9); + + GIVEN { + PLAYER(SPECIES_EXPLOUD) { Ability(ABILITY_SOUNDPROOF); Status1(STATUS1_POISON); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(player, MOVE_HEAL_BELL, target: player); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_HEAL_BELL, player); + NOT MESSAGE("Exploud is hurt by poison!"); + } +}