From ddff2def40ac95588ed82b758a6547d49c1e073f Mon Sep 17 00:00:00 2001 From: moostoet <70690976+moostoet@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:11:25 +0100 Subject: [PATCH 1/6] Prevent sameMoveTurns from incrementing when unable to use move (#6167) --- src/battle_script_commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 7a7fddac9827..4449040a7337 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6744,7 +6744,7 @@ static void Cmd_moveend(void) gBattleScripting.moveendState++; break; case MOVEEND_SAME_MOVE_TURNS: - if (gCurrentMove != gLastResultingMoves[gBattlerAttacker] || gMoveResultFlags & MOVE_RESULT_NO_EFFECT) + if (gCurrentMove != gLastResultingMoves[gBattlerAttacker] || gMoveResultFlags & MOVE_RESULT_NO_EFFECT || gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) gBattleStruct->sameMoveTurns[gBattlerAttacker] = 0; else if (gCurrentMove == gLastResultingMoves[gBattlerAttacker] && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT) gBattleStruct->sameMoveTurns[gBattlerAttacker]++; From fe41f9eaf5cd97e704a5df701ac96578a79634ac Mon Sep 17 00:00:00 2001 From: Eduardo Quezada Date: Mon, 3 Feb 2025 13:03:29 -0300 Subject: [PATCH 2/6] Fixed non-regional forms breeding incorrectly (#4985) --- include/constants/regions.h | 21 +++++ include/daycare.h | 1 + include/pokemon.h | 3 + include/regions.h | 12 +++ src/daycare.c | 34 ++++++-- src/pokemon.c | 68 +++++++++++++++ test/daycare.c | 164 ++++++++++++++++++++++++++++++++++++ 7 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 include/constants/regions.h create mode 100644 include/regions.h create mode 100644 test/daycare.c diff --git a/include/constants/regions.h b/include/constants/regions.h new file mode 100644 index 000000000000..3f7397ced6d5 --- /dev/null +++ b/include/constants/regions.h @@ -0,0 +1,21 @@ +#ifndef GUARD_CONSTANTS_REGIONS_H +#define GUARD_CONSTANTS_REGIONS_H + +// Core-series regions +enum Region +{ + REGION_NONE, + REGION_KANTO, + REGION_JOHTO, + REGION_HOENN, + REGION_SINNOH, + REGION_UNOVA, + REGION_KALOS, + REGION_ALOLA, + REGION_GALAR, + REGION_HISUI, + REGION_PALDEA, + REGIONS_COUNT, +}; + +#endif // GUARD_CONSTANTS_REGIONS_H diff --git a/include/daycare.h b/include/daycare.h index 4d5b470f8b3e..37a325d6556c 100644 --- a/include/daycare.h +++ b/include/daycare.h @@ -34,5 +34,6 @@ void ShowDaycareLevelMenu(void); void ChooseSendDaycareMon(void); u8 GetEggMovesBySpecies(u16 species, u16 *eggMoves); bool8 SpeciesCanLearnEggMove(u16 species, u16 move); +void StorePokemonInDaycare(struct Pokemon *mon, struct DaycareMon *daycareMon); #endif // GUARD_DAYCARE_H diff --git a/include/pokemon.h b/include/pokemon.h index 706b0cbf65bb..ff3d06cdd1b0 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -3,6 +3,7 @@ #include "sprite.h" #include "constants/items.h" +#include "constants/regions.h" #include "constants/region_map_sections.h" #include "constants/map_groups.h" #include "contest_effect.h" @@ -912,5 +913,7 @@ void UpdateDaysPassedSinceFormChange(u16 days); void TrySetDayLimitToFormChange(struct Pokemon *mon); u32 CheckDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler); uq4_12_t GetDynamaxLevelHPMultiplier(u32 dynamaxLevel, bool32 inverseMultiplier); +u32 GetRegionalFormByRegion(u32 species, u32 region); +bool32 IsSpeciesForeignRegionalForm(u32 species, u32 currentRegion); #endif // GUARD_POKEMON_H diff --git a/include/regions.h b/include/regions.h new file mode 100644 index 000000000000..2f44896ea2b0 --- /dev/null +++ b/include/regions.h @@ -0,0 +1,12 @@ +#ifndef GUARD_REGIONS_H +#define GUARD_REGIONS_H + +#include "constants/regions.h" + +static inline u32 GetCurrentRegion(void) +{ + // TODO: Since there's no current multi-region support, we have this constant for the purposes of regional form comparisons. + return REGION_HOENN; +} + +#endif // GUARD_REGIONS_H diff --git a/src/daycare.c b/src/daycare.c index 4997f4efe913..49fce40c0a3f 100644 --- a/src/daycare.c +++ b/src/daycare.c @@ -21,6 +21,7 @@ #include "list_menu.h" #include "overworld.h" #include "item.h" +#include "regions.h" #include "constants/form_change_types.h" #include "constants/items.h" #include "constants/hold_effects.h" @@ -244,7 +245,7 @@ static void TransferEggMoves(void) } } -static void StorePokemonInDaycare(struct Pokemon *mon, struct DaycareMon *daycareMon) +void StorePokemonInDaycare(struct Pokemon *mon, struct DaycareMon *daycareMon) { if (MonHasMail(mon)) { @@ -979,9 +980,12 @@ STATIC_ASSERT(P_SCATTERBUG_LINE_FORM_BREED == SPECIES_SCATTERBUG_ICY_SNOW || (P_ static u16 DetermineEggSpeciesAndParentSlots(struct DayCare *daycare, u8 *parentSlots) { - u16 i; - u16 species[DAYCARE_MON_COUNT]; - u16 eggSpecies; + u32 i; + u32 species[DAYCARE_MON_COUNT]; + u32 eggSpecies, parentSpecies; + bool32 hasMotherEverstone, hasFatherEverstone, motherIsForeign, fatherIsForeign; + bool32 motherEggSpecies, fatherEggSpecies; + u32 currentRegion = GetCurrentRegion(); for (i = 0; i < DAYCARE_MON_COUNT; i++) { @@ -998,7 +1002,24 @@ static u16 DetermineEggSpeciesAndParentSlots(struct DayCare *daycare, u8 *parent } } - eggSpecies = GetEggSpecies(species[parentSlots[0]]); + motherEggSpecies = GetEggSpecies(species[parentSlots[0]]); + fatherEggSpecies = GetEggSpecies(species[parentSlots[1]]); + hasMotherEverstone = ItemId_GetHoldEffect(GetBoxMonData(&daycare->mons[parentSlots[0]].mon, MON_DATA_HELD_ITEM)) == HOLD_EFFECT_PREVENT_EVOLVE; + hasFatherEverstone = ItemId_GetHoldEffect(GetBoxMonData(&daycare->mons[parentSlots[1]].mon, MON_DATA_HELD_ITEM)) == HOLD_EFFECT_PREVENT_EVOLVE; + motherIsForeign = IsSpeciesForeignRegionalForm(motherEggSpecies, currentRegion); + fatherIsForeign = IsSpeciesForeignRegionalForm(fatherEggSpecies, currentRegion); + + if (hasMotherEverstone) + parentSpecies = motherEggSpecies; + else if (fatherIsForeign && hasFatherEverstone) + parentSpecies = fatherEggSpecies; + else if (motherIsForeign) + parentSpecies = GetRegionalFormByRegion(motherEggSpecies, currentRegion); + else + parentSpecies = motherEggSpecies; + + eggSpecies = GetEggSpecies(parentSpecies); + if (eggSpecies == SPECIES_NIDORAN_F && daycare->offspringPersonality & EGG_GENDER_MALE) eggSpecies = SPECIES_NIDORAN_M; else if (eggSpecies == SPECIES_ILLUMISE && daycare->offspringPersonality & EGG_GENDER_MALE) @@ -1043,6 +1064,9 @@ static void _GiveEggFromDaycare(struct DayCare *daycare) u8 parentSlots[DAYCARE_MON_COUNT] = {0}; bool8 isEgg; + if (GetDaycareCompatibilityScore(daycare) == PARENTS_INCOMPATIBLE) + return; + species = DetermineEggSpeciesAndParentSlots(daycare, parentSlots); if (P_INCENSE_BREEDING < GEN_9) AlterEggSpeciesWithIncenseItem(&species, daycare); diff --git a/src/pokemon.c b/src/pokemon.c index 01a82d66e006..437ed55e4b4f 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -56,6 +56,7 @@ #include "constants/items.h" #include "constants/layouts.h" #include "constants/moves.h" +#include "constants/regions.h" #include "constants/songs.h" #include "constants/trainers.h" #include "constants/union_room.h" @@ -6985,3 +6986,70 @@ uq4_12_t GetDynamaxLevelHPMultiplier(u32 dynamaxLevel, bool32 inverseMultiplier) return UQ_4_12(1.0/(1.5 + 0.05 * dynamaxLevel)); return UQ_4_12(1.5 + 0.05 * dynamaxLevel); } + +bool32 IsSpeciesRegionalForm(u32 species) +{ + return gSpeciesInfo[species].isAlolanForm + || gSpeciesInfo[species].isGalarianForm + || gSpeciesInfo[species].isHisuianForm + || gSpeciesInfo[species].isPaldeanForm; +} + +bool32 IsSpeciesRegionalFormFromRegion(u32 species, u32 region) +{ + switch (region) + { + case REGION_ALOLA: return gSpeciesInfo[species].isAlolanForm; + case REGION_GALAR: return gSpeciesInfo[species].isGalarianForm; + case REGION_HISUI: return gSpeciesInfo[species].isHisuianForm; + case REGION_PALDEA: return gSpeciesInfo[species].isPaldeanForm; + default: return FALSE; + } +} + +bool32 SpeciesHasRegionalForm(u32 species) +{ + u32 formId; + const u16 *formTable = GetSpeciesFormTable(species); + for (formId = 0; formTable != NULL && formTable[formId] != FORM_SPECIES_END; formId++) + { + if (IsSpeciesRegionalForm(formTable[formId])) + return TRUE; + } + return FALSE; +} + +u32 GetRegionalFormByRegion(u32 species, u32 region) +{ + u32 formId = 0; + u32 firstFoundSpecies = 0; + const u16 *formTable = GetSpeciesFormTable(species); + + if (formTable != NULL) + { + for (formId = 0; formTable[formId] != FORM_SPECIES_END; formId++) + { + if (firstFoundSpecies == 0) + firstFoundSpecies = formTable[formId]; + + if (IsSpeciesRegionalFormFromRegion(formTable[formId], region)) + return formTable[formId]; + } + if (firstFoundSpecies != 0) + return firstFoundSpecies; + } + return species; +} + +bool32 IsSpeciesForeignRegionalForm(u32 species, u32 currentRegion) +{ + u32 i; + for (i = 0; i < REGIONS_COUNT; i++) + { + if (currentRegion != i && IsSpeciesRegionalFormFromRegion(species, i)) + return TRUE; + else if (currentRegion == i && SpeciesHasRegionalForm(species) && !IsSpeciesRegionalFormFromRegion(species, i)) + return TRUE; + } + return FALSE; +} diff --git a/test/daycare.c b/test/daycare.c new file mode 100644 index 000000000000..1f142f115495 --- /dev/null +++ b/test/daycare.c @@ -0,0 +1,164 @@ +#include "global.h" +#include "daycare.h" +#include "event_data.h" +#include "malloc.h" +#include "party_menu.h" +#include "regions.h" +#include "test/overworld_script.h" +#include "test/test.h" + +// We don't run the StoreSelectedPokemonInDaycare special because it relies on calling the +// party select screen and the GetCursorSelectionMonId function, so we store directly to the struct. +#define STORE_IN_DAYCARE_AND_GET_EGG() \ + StorePokemonInDaycare(&gPlayerParty[0], &gSaveBlock1Ptr->daycare.mons[0]); \ + StorePokemonInDaycare(&gPlayerParty[0], &gSaveBlock1Ptr->daycare.mons[1]); \ + RUN_OVERWORLD_SCRIPT( special GiveEggFromDaycare; ); + +TEST("(Daycare) Pokémon generate Eggs of the lowest member of the evolutionary family") +{ + ASSUME(P_FAMILY_PIKACHU == TRUE); + ASSUME(P_GEN_2_CROSS_EVOS == TRUE); + + ZeroPlayerPartyMons(); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_PIKACHU, 100, gender=MON_MALE; + givemon SPECIES_PIKACHU, 100, gender=MON_FEMALE; + ); + STORE_IN_DAYCARE_AND_GET_EGG(); + + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_PICHU); +} + +TEST("(Daycare) Pokémon offspring species is based off the mother's species") +{ + u32 offspring = 0; + ASSUME(P_FAMILY_PIKACHU == TRUE); + ASSUME(P_GEN_2_CROSS_EVOS == TRUE); + ASSUME(P_FAMILY_RIOLU == TRUE); + + ZeroPlayerPartyMons(); + PARAMETRIZE { offspring = SPECIES_RIOLU; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PIKACHU, 100, gender=MON_MALE; givemon SPECIES_LUCARIO, 100, gender=MON_FEMALE, item=ITEM_NONE; ); } + PARAMETRIZE { offspring = SPECIES_PICHU; RUN_OVERWORLD_SCRIPT(givemon SPECIES_PIKACHU, 100, gender=MON_FEMALE; givemon SPECIES_LUCARIO, 100, gender=MON_MALE;); } + STORE_IN_DAYCARE_AND_GET_EGG(); + + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), offspring); +} + +TEST("(Daycare) Pokémon can breed with Ditto if they don't belong to the Ditto or No Eggs Discovered group") +{ + u32 j = 0; + u32 parentSpecies = 0; + + ZeroPlayerPartyMons(); + for (j = 1; j < NUM_SPECIES; j++) + PARAMETRIZE { parentSpecies = j; } + VarSet(VAR_TEMP_C, parentSpecies); + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_DITTO, 100; givemon VAR_TEMP_C, 100; + ); + STORE_IN_DAYCARE_AND_GET_EGG(); + + if (gSpeciesInfo[parentSpecies].eggGroups[0] != EGG_GROUP_NO_EGGS_DISCOVERED + && gSpeciesInfo[parentSpecies].eggGroups[0] != EGG_GROUP_DITTO) + EXPECT_NE(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_NONE); + else + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), SPECIES_NONE); +} + +TEST("(Daycare) Shellos' form is always based on the mother's form") +{ + u32 offspring = 0; + ASSUME(P_FAMILY_MEOWTH == TRUE); + ASSUME(P_ALOLAN_FORMS == TRUE); + ASSUME(P_GALARIAN_FORMS == TRUE); + + ZeroPlayerPartyMons(); + PARAMETRIZE { offspring = SPECIES_SHELLOS_WEST; RUN_OVERWORLD_SCRIPT(givemon SPECIES_SHELLOS_EAST, 1, gender=MON_MALE; givemon SPECIES_SHELLOS_WEST, 1, gender=MON_FEMALE, item=ITEM_NONE; ); } + PARAMETRIZE { offspring = SPECIES_SHELLOS_WEST; RUN_OVERWORLD_SCRIPT(givemon SPECIES_SHELLOS_EAST, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_SHELLOS_WEST, 1, gender=MON_FEMALE, item=ITEM_NONE; ); } + PARAMETRIZE { offspring = SPECIES_SHELLOS_WEST; RUN_OVERWORLD_SCRIPT(givemon SPECIES_SHELLOS_EAST, 1, gender=MON_MALE; givemon SPECIES_SHELLOS_WEST, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + PARAMETRIZE { offspring = SPECIES_SHELLOS_EAST; RUN_OVERWORLD_SCRIPT(givemon SPECIES_SHELLOS_WEST, 1, gender=MON_MALE; givemon SPECIES_SHELLOS_EAST, 1, gender=MON_FEMALE, item=ITEM_NONE; ); } + PARAMETRIZE { offspring = SPECIES_SHELLOS_EAST; RUN_OVERWORLD_SCRIPT(givemon SPECIES_SHELLOS_WEST, 1, gender=MON_MALE, item=ITEM_EVERSTONE; givemon SPECIES_SHELLOS_EAST, 1, gender=MON_FEMALE, item=ITEM_NONE; ); } + PARAMETRIZE { offspring = SPECIES_SHELLOS_EAST; RUN_OVERWORLD_SCRIPT(givemon SPECIES_SHELLOS_WEST, 1, gender=MON_MALE; givemon SPECIES_SHELLOS_EAST, 1, gender=MON_FEMALE, item=ITEM_EVERSTONE;); } + STORE_IN_DAYCARE_AND_GET_EGG(); + + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), offspring); +} + +TEST("(Daycare) Pokémon with regional forms give the correct offspring") +{ + u32 region = 0, offspring = 0, species1 = 0, item1 = 0, species2 = 0, item2 = 0; + + ZeroPlayerPartyMons(); + + region = GetCurrentRegion(); + if (region == REGION_ALOLA) { + PARAMETRIZE { offspring=SPECIES_MEOWTH_ALOLA; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_ALOLA; item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_ALOLA; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_ALOLA; item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_ALOLA; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR; item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR; item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_ALOLA; species1=SPECIES_DIGLETT; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR; item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_DIGLETT; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR; item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_PERRSERKER; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN; item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_PERRSERKER; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN; item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_PERSIAN_ALOLA; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN; item2=ITEM_EVERSTONE; } + } else if (region == REGION_GALAR) { + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_ALOLA; item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_ALOLA; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_ALOLA; item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR; item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR; item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_DIGLETT; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR; item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_DIGLETT; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR; item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_PERRSERKER; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN; item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_PERRSERKER; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN; item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_PERSIAN_ALOLA; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN; item2=ITEM_EVERSTONE; } + } else { + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_ALOLA, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_ALOLA; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_ALOLA, item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_MEOWTH; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR, item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_DIGLETT; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_DIGLETT; item1=ITEM_NONE; species2=SPECIES_MEOWTH_GALAR, item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH_GALAR; species1=SPECIES_PERRSERKER; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_PERRSERKER; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN, item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_MEOWTH; species1=SPECIES_PERSIAN_ALOLA; item1=ITEM_EVERSTONE; species2=SPECIES_PERSIAN, item2=ITEM_EVERSTONE; } + } + + if (region == REGION_HISUI) { + PARAMETRIZE { offspring=SPECIES_SNEASEL_HISUI; species1=SPECIES_SNEASEL; item1=ITEM_NONE; species2=SPECIES_SNEASEL_HISUI, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_SNEASEL; species1=SPECIES_SNEASEL; item1=ITEM_EVERSTONE; species2=SPECIES_SNEASEL_HISUI, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_SNEASEL_HISUI; species1=SPECIES_SNEASEL; item1=ITEM_NONE; species2=SPECIES_SNEASEL_HISUI, item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_SNEASEL; species1=SPECIES_SNEASLER; item1=ITEM_EVERSTONE; species2=SPECIES_WEAVILE, item2=ITEM_EVERSTONE; } + } else { + PARAMETRIZE { offspring=SPECIES_SNEASEL; species1=SPECIES_SNEASEL; item1=ITEM_NONE; species2=SPECIES_SNEASEL_HISUI, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_SNEASEL; species1=SPECIES_SNEASEL; item1=ITEM_EVERSTONE; species2=SPECIES_SNEASEL_HISUI, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_SNEASEL_HISUI; species1=SPECIES_SNEASEL; item1=ITEM_NONE; species2=SPECIES_SNEASEL_HISUI, item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_SNEASEL; species1=SPECIES_SNEASLER; item1=ITEM_EVERSTONE; species2=SPECIES_WEAVILE, item2=ITEM_EVERSTONE; } + } + + if (region == REGION_PALDEA) { + PARAMETRIZE { offspring=SPECIES_WOOPER_PALDEA; species1=SPECIES_WOOPER; item1=ITEM_NONE; species2=SPECIES_WOOPER_PALDEA, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_WOOPER; species1=SPECIES_WOOPER; item1=ITEM_EVERSTONE; species2=SPECIES_WOOPER_PALDEA, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_WOOPER_PALDEA; species1=SPECIES_WOOPER; item1=ITEM_NONE; species2=SPECIES_WOOPER_PALDEA, item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_WOOPER; species1=SPECIES_CLODSIRE; item1=ITEM_EVERSTONE; species2=SPECIES_QUAGSIRE, item2=ITEM_EVERSTONE; } + } else { + PARAMETRIZE { offspring=SPECIES_WOOPER; species1=SPECIES_WOOPER; item1=ITEM_NONE; species2=SPECIES_WOOPER_PALDEA, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_WOOPER; species1=SPECIES_WOOPER; item1=ITEM_EVERSTONE; species2=SPECIES_WOOPER_PALDEA, item2=ITEM_NONE; } + PARAMETRIZE { offspring=SPECIES_WOOPER_PALDEA; species1=SPECIES_WOOPER; item1=ITEM_NONE; species2=SPECIES_WOOPER_PALDEA, item2=ITEM_EVERSTONE; } + PARAMETRIZE { offspring=SPECIES_WOOPER; species1=SPECIES_CLODSIRE; item1=ITEM_EVERSTONE; species2=SPECIES_QUAGSIRE, item2=ITEM_EVERSTONE; } + } + ASSUME(IsSpeciesEnabled(species1) == TRUE); + ASSUME(IsSpeciesEnabled(species2) == TRUE); + ASSUME(IsSpeciesEnabled(offspring) == TRUE); + + VarSet(VAR_0x8000, species1); + VarSet(VAR_0x8001, item1); + VarSet(VAR_0x8002, species2); + VarSet(VAR_0x8003, item2); + + RUN_OVERWORLD_SCRIPT(givemon VAR_0x8000, 1, gender=MON_MALE, item=VAR_0x8001;); + RUN_OVERWORLD_SCRIPT(givemon VAR_0x8002, 1, gender=MON_FEMALE, item=VAR_0x8003;); + + STORE_IN_DAYCARE_AND_GET_EGG(); + + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPECIES), offspring); +} From 5238665a7a754bd00f819e55b57ab47ac6b5f7f9 Mon Sep 17 00:00:00 2001 From: PhallenTree <168426989+PhallenTree@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:31:14 +0000 Subject: [PATCH 3/6] Fixes Suction Cups ability popup and Red Card + Guard Dog interaction (#6171) --- data/battle_scripts_1.s | 7 ++-- src/battle_script_commands.c | 6 ++-- test/battle/hold_effect/red_card.c | 38 ++++++++++++++++++--- test/battle/move_effect/hit_switch_target.c | 5 +-- test/battle/move_effect/roar.c | 35 +++++++++++++++++++ 5 files changed, 77 insertions(+), 14 deletions(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 88cd06d596b7..fff1961a9cd0 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8253,15 +8253,12 @@ BattleScript_FlashFireBoost:: goto BattleScript_MoveEnd BattleScript_AbilityPreventsPhasingOut:: - pause B_WAIT_TIME_SHORT - call BattleScript_AbilityPopUp - printstring STRINGID_PKMNANCHORSITSELFWITH - waitmessage B_WAIT_TIME_LONG + call BattleScript_AbilityPreventsPhasingOutRet goto BattleScript_MoveEnd BattleScript_AbilityPreventsPhasingOutRet:: pause B_WAIT_TIME_SHORT - call BattleScript_AbilityPopUp + call BattleScript_AbilityPopUpTarget printstring STRINGID_PKMNANCHORSITSELFWITH waitmessage B_WAIT_TIME_LONG return diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 4449040a7337..a07ac4579f37 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -6566,8 +6566,7 @@ static void Cmd_moveend(void) if (redCardBattlers && (gMovesInfo[gCurrentMove].effect != EFFECT_HIT_SWITCH_TARGET || gBattleStruct->hitSwitchTargetFailed) && IsBattlerAlive(gBattlerAttacker) - && !TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove) - && GetBattlerAbility(gBattlerAttacker) != ABILITY_GUARD_DOG) + && !TestIfSheerForceAffected(gBattlerAttacker, gCurrentMove)) { // Since we check if battler was damaged, we don't need to check move result. // In fact, doing so actually prevents multi-target moves from activating red card properly @@ -6592,7 +6591,8 @@ static void Cmd_moveend(void) if (gMovesInfo[gCurrentMove].effect == EFFECT_HIT_ESCAPE) gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection BattleScriptPushCursor(); - if (gBattleStruct->commanderActive[gBattlerAttacker] != SPECIES_NONE) + if (gBattleStruct->commanderActive[gBattlerAttacker] != SPECIES_NONE + || GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG) { gBattlescriptCurrInstr = BattleScript_RedCardActivationNoSwitch; } diff --git a/test/battle/hold_effect/red_card.c b/test/battle/hold_effect/red_card.c index aa312797b284..6bf34b8f7502 100644 --- a/test/battle/hold_effect/red_card.c +++ b/test/battle/hold_effect/red_card.c @@ -273,12 +273,13 @@ DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker is rooted") ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); MESSAGE("Wobbuffet held up its Red Card against the opposing Wobbuffet!"); MESSAGE("The opposing Wobbuffet anchored itself with its roots!"); + NOT MESSAGE("The opposing Unown was dragged out!"); // Red Card already consumed so cannot activate. ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); NONE_OF { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); - MESSAGE("Wynaut held up its Red Card against the opposing Wynaut!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wynaut!"); } } } @@ -301,12 +302,41 @@ DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker has Suction Cup ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); MESSAGE("Wobbuffet held up its Red Card against the opposing Octillery!"); MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); + NOT MESSAGE("The opposing Unown was dragged out!"); // Red Card already consumed so cannot activate. ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); NONE_OF { - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerRight); - MESSAGE("Wynaut held up its Red Card against the opposing Wynaut!"); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wynaut!"); + } + } +} + +DOUBLE_BATTLE_TEST("Red Card activates but fails if the attacker has Guard Dog") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_WYNAUT); + OPPONENT(SPECIES_UNOWN); + } WHEN { + TURN { + MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); + MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); + } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Okidogi!"); + NOT MESSAGE("The opposing Unown was dragged out!"); + + // Red Card already consumed so cannot activate. + ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight); + NONE_OF { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, playerLeft); + MESSAGE("Wobbuffet held up its Red Card against the opposing Wynaut!"); } } } diff --git a/test/battle/move_effect/hit_switch_target.c b/test/battle/move_effect/hit_switch_target.c index bc026110eee7..8760dd597ce3 100644 --- a/test/battle/move_effect/hit_switch_target.c +++ b/test/battle/move_effect/hit_switch_target.c @@ -90,7 +90,7 @@ SINGLE_BATTLE_TEST("Dragon Tail switches the target after Rocky Helmet and Iron } } -SINGLE_BATTLE_TEST("Dragon Tail effect will fails against Guard Dog ability") +SINGLE_BATTLE_TEST("Dragon Tail effect fails against target with Guard Dog") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -104,7 +104,7 @@ SINGLE_BATTLE_TEST("Dragon Tail effect will fails against Guard Dog ability") } } -SINGLE_BATTLE_TEST("Dragon Tail effect will fails against Suction Cups ability") +SINGLE_BATTLE_TEST("Dragon Tail effect fails against target with Suction Cups") { GIVEN { PLAYER(SPECIES_WOBBUFFET); @@ -114,6 +114,7 @@ SINGLE_BATTLE_TEST("Dragon Tail effect will fails against Suction Cups ability") TURN { MOVE(player, MOVE_DRAGON_TAIL); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_TAIL, player); + ABILITY_POPUP(opponent, ABILITY_SUCTION_CUPS); MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); NOT MESSAGE("The opposing Charmander was dragged out!"); } diff --git a/test/battle/move_effect/roar.c b/test/battle/move_effect/roar.c index f67a24bba102..3819fc204371 100644 --- a/test/battle/move_effect/roar.c +++ b/test/battle/move_effect/roar.c @@ -68,3 +68,38 @@ SINGLE_BATTLE_TEST("Roar fails if replacements fainted") MESSAGE("But it failed!"); } } + +SINGLE_BATTLE_TEST("Roar fails against target with Guard Dog") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OKIDOGI) { Ability(ABILITY_GUARD_DOG); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, player); + MESSAGE("The opposing Charmander was dragged out!"); + } + MESSAGE("Wobbuffet used Roar!"); + MESSAGE("But it failed!"); + } +} + +SINGLE_BATTLE_TEST("Roar fails to switch out target with Suction Cups") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_OCTILLERY) { Ability(ABILITY_SUCTION_CUPS); } + OPPONENT(SPECIES_CHARMANDER); + } WHEN { + TURN { MOVE(player, MOVE_ROAR); } + } SCENE { + MESSAGE("Wobbuffet used Roar!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROAR, player); + ABILITY_POPUP(opponent, ABILITY_SUCTION_CUPS); + MESSAGE("The opposing Octillery anchors itself with Suction Cups!"); + NOT MESSAGE("The opposing Charmander was dragged out!"); + } +} From b4fc611d5fb98901b333727fd1d98c0127c7ddd1 Mon Sep 17 00:00:00 2001 From: ghoulslash <41651341+ghoulslash@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:08:32 -0500 Subject: [PATCH 4/6] Cleanup some global battler ID usage (#6181) Co-authored-by: ghoulslash --- src/battle_util.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index a2d6c0fe1027..855851586be8 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -1218,7 +1218,7 @@ bool32 IsBelchPreventingMove(u32 battler, u32 move) } // Dynamax bypasses all selection prevention except Taunt and Assault Vest. -#define DYNAMAX_BYPASS_CHECK (!IsGimmickSelected(gBattlerAttacker, GIMMICK_DYNAMAX) && GetActiveGimmick(gBattlerAttacker) != GIMMICK_DYNAMAX) +#define DYNAMAX_BYPASS_CHECK (!IsGimmickSelected(battler, GIMMICK_DYNAMAX) && GetActiveGimmick(battler) != GIMMICK_DYNAMAX) u32 TrySetCantSelectMoveBattleScript(u32 battler) { @@ -1228,7 +1228,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) u32 holdEffect = GetBattlerHoldEffect(battler, TRUE); u16 *choicedMove = &gBattleStruct->choicedMove[battler]; - if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[battler].disabledMove == move && move != MOVE_NONE) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && gDisableStructs[battler].disabledMove == move && move != MOVE_NONE) { gBattleScripting.battler = battler; gCurrentMove = move; @@ -1244,7 +1244,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && move == gLastMoves[battler] && move != MOVE_STRUGGLE && (gBattleMons[battler].status2 & STATUS2_TORMENT)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && move == gLastMoves[battler] && move != MOVE_STRUGGLE && (gBattleMons[battler].status2 & STATUS2_TORMENT)) { CancelMultiTurnMoves(battler); if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1259,9 +1259,9 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[battler].tauntTimer != 0 && IS_MOVE_STATUS(move)) + if (GetActiveGimmick(battler) != GIMMICK_Z_MOVE && gDisableStructs[battler].tauntTimer != 0 && IS_MOVE_STATUS(move)) { - if ((GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)) + if ((GetActiveGimmick(battler) == GIMMICK_DYNAMAX)) gCurrentMove = MOVE_MAX_GUARD; else gCurrentMove = move; @@ -1277,7 +1277,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && gDisableStructs[battler].throatChopTimer != 0 && gMovesInfo[move].soundMove) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && gDisableStructs[battler].throatChopTimer != 0 && gMovesInfo[move].soundMove) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1292,7 +1292,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && GetImprisonedMovesCount(battler, move)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && GetImprisonedMovesCount(battler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1307,7 +1307,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && IsGravityPreventingMove(move)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && IsGravityPreventingMove(move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1322,7 +1322,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && IsHealBlockPreventingMove(battler, move)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && IsHealBlockPreventingMove(battler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1337,7 +1337,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } } - if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(gBattlerAttacker) != GIMMICK_Z_MOVE && IsBelchPreventingMove(battler, move)) + if (DYNAMAX_BYPASS_CHECK && GetActiveGimmick(battler) != GIMMICK_Z_MOVE && IsBelchPreventingMove(battler, move)) { gCurrentMove = move; if (gBattleTypeFlags & BATTLE_TYPE_PALACE) @@ -1401,7 +1401,7 @@ u32 TrySetCantSelectMoveBattleScript(u32 battler) } else if (holdEffect == HOLD_EFFECT_ASSAULT_VEST && IS_MOVE_STATUS(move) && gMovesInfo[move].effect != EFFECT_ME_FIRST) { - if ((GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)) + if ((GetActiveGimmick(battler) == GIMMICK_DYNAMAX)) gCurrentMove = MOVE_MAX_GUARD; else gCurrentMove = move; @@ -8685,8 +8685,8 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) return TRUE; if (!gProtectStructs[battlerDef].maxGuarded // Max Guard cannot be bypassed by Unseen Fist - && IsMoveMakingContact(move, gBattlerAttacker) - && GetBattlerAbility(gBattlerAttacker) == ABILITY_UNSEEN_FIST) + && IsMoveMakingContact(move, battlerAtk) + && GetBattlerAbility(battlerAtk) == ABILITY_UNSEEN_FIST) return FALSE; else if ((gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_CRAFTY_SHIELD) && IS_MOVE_STATUS(move) && gMovesInfo[move].effect != EFFECT_COACHING) @@ -8696,7 +8696,7 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) else if (gProtectStructs[battlerDef].protected) return TRUE; else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_WIDE_GUARD - && GetBattlerMoveTargetType(gBattlerAttacker, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) + && GetBattlerMoveTargetType(battlerAtk, move) & (MOVE_TARGET_BOTH | MOVE_TARGET_FOES_AND_ALLY)) return TRUE; else if (gProtectStructs[battlerDef].banefulBunkered) return TRUE; @@ -8711,7 +8711,7 @@ bool32 IsBattlerProtected(u32 battlerAtk, u32 battlerDef, u32 move) else if (gProtectStructs[battlerDef].maxGuarded) return TRUE; else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_QUICK_GUARD - && GetChosenMovePriority(gBattlerAttacker) > 0) + && GetChosenMovePriority(battlerAtk) > 0) return TRUE; else if (gSideStatuses[GetBattlerSide(battlerDef)] & SIDE_STATUS_MAT_BLOCK && !IS_MOVE_STATUS(move)) @@ -10458,7 +10458,7 @@ static inline bool32 IsFutureSightAttackerInParty(struct DamageCalculationData * if (gMovesInfo[damageCalcData->move].effect != EFFECT_FUTURE_SIGHT) return FALSE; - struct Pokemon *party = GetSideParty(GetBattlerSide(gBattlerAttacker)); + struct Pokemon *party = GetSideParty(GetBattlerSide(damageCalcData->battlerAtk)); return &party[gWishFutureKnock.futureSightPartyIndex[damageCalcData->battlerDef]] != &party[gBattlerPartyIndexes[damageCalcData->battlerAtk]]; } @@ -11157,7 +11157,7 @@ bool32 CanBattlerGetOrLoseItem(u32 battler, u16 itemId) else if (holdEffect == HOLD_EFFECT_Z_CRYSTAL) return FALSE; else if (holdEffect == HOLD_EFFECT_BOOSTER_ENERGY - && (gSpeciesInfo[gBattleMons[gBattlerAttacker].species].isParadox || gSpeciesInfo[gBattleMons[gBattlerTarget].species].isParadox)) + && (gSpeciesInfo[gBattleMons[battler].species].isParadox || gSpeciesInfo[gBattleMons[gBattlerTarget].species].isParadox)) return FALSE; else return TRUE; From ea3b81218b76e23661fc37d04f6542f8de4be155 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Tue, 4 Feb 2025 18:37:42 +0100 Subject: [PATCH 5/6] Wrote tests for Electrify (#6179) Co-authored-by: Hedara --- test/battle/move_effect/electrify.c | 73 ++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/test/battle/move_effect/electrify.c b/test/battle/move_effect/electrify.c index 71373cdd5866..e60bbdaf2288 100644 --- a/test/battle/move_effect/electrify.c +++ b/test/battle/move_effect/electrify.c @@ -1,5 +1,74 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Electrify makes the target's move Electric-type for the remainder of the turn"); -TO_DO_BATTLE_TEST("Electrify can change status moves to Electric-type"); // Test type immunity +SINGLE_BATTLE_TEST("Electrify makes the target's move Electric-type for the remainder of the turn (single move)") +{ + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_SANDSLASH].types[0] == TYPE_GROUND || gSpeciesInfo[SPECIES_SANDSLASH].types[1] == TYPE_GROUND); + ASSUME(gMovesInfo[MOVE_TACKLE].type != TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SANDSLASH); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_TACKLE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIFY, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player); + } +} + +DOUBLE_BATTLE_TEST("Electrify makes the target's move Electric-type for the remainder of the turn (double move)") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_SANDSLASH].types[0] == TYPE_GROUND || gSpeciesInfo[SPECIES_SANDSLASH].types[1] == TYPE_GROUND); + ASSUME(gMovesInfo[MOVE_TACKLE].type != TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_SANDSLASH); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponentLeft, MOVE_ELECTRIFY, target: playerLeft); MOVE(playerLeft, MOVE_TACKLE, target:opponentLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); MOVE(opponentRight, MOVE_CELEBRATE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIFY, opponentLeft); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft); + } +} + +SINGLE_BATTLE_TEST("Electrify can change status moves to Electric-type") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_SANDSLASH].types[0] == TYPE_GROUND || gSpeciesInfo[SPECIES_SANDSLASH].types[1] == TYPE_GROUND); + ASSUME(gMovesInfo[MOVE_LEER].category == DAMAGE_CATEGORY_STATUS); + ASSUME(gMovesInfo[MOVE_LEER].type != TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SANDSLASH); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_LEER); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIFY, opponent); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_LEER, player); + } +} + +SINGLE_BATTLE_TEST("Electrify changes the type of foreseen moves") +{ + KNOWN_FAILING; + GIVEN { + ASSUME(gSpeciesInfo[SPECIES_SANDSLASH].types[0] == TYPE_GROUND || gSpeciesInfo[SPECIES_SANDSLASH].types[1] == TYPE_GROUND); + ASSUME(gMovesInfo[MOVE_FUTURE_SIGHT].effect == EFFECT_FUTURE_SIGHT); + ASSUME(gMovesInfo[MOVE_FUTURE_SIGHT].type != TYPE_ELECTRIC); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_SANDSLASH); + } WHEN { + TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_FUTURE_SIGHT); } + TURN {} + TURN {} + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ELECTRIFY, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player); + NOT HP_BAR(opponent); + } +} From f6d1773e5119f59c58c6a3277fb59fdc6da75080 Mon Sep 17 00:00:00 2001 From: hedara90 <90hedara@gmail.com> Date: Wed, 5 Feb 2025 22:29:13 +0100 Subject: [PATCH 6/6] Fixed Unnerve message and wrote tests (#6192) Co-authored-by: Hedara --- src/battle_util.c | 2 + test/battle/ability/unnerve.c | 72 ++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/battle_util.c b/src/battle_util.c index 855851586be8..4dc9c9b45e90 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -4629,6 +4629,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_UNNERVE: if (!gSpecialStatuses[battler].switchInAbilityDone) { + gBattlerTarget = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerAtPosition(battler))); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_UNNERVE; gSpecialStatuses[battler].switchInAbilityDone = TRUE; BattleScriptPushCursorAndCallback(BattleScript_SwitchInAbilityMsg); @@ -4639,6 +4640,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32 case ABILITY_AS_ONE_SHADOW_RIDER: if (!gSpecialStatuses[battler].switchInAbilityDone) { + gBattlerTarget = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerAtPosition(battler))); gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SWITCHIN_ASONE; gSpecialStatuses[battler].switchInAbilityDone = TRUE; BattleScriptPushCursorAndCallback(BattleScript_ActivateAsOne); diff --git a/test/battle/ability/unnerve.c b/test/battle/ability/unnerve.c index 9ad4ee7e5fdd..ce35cf72c1c6 100644 --- a/test/battle/ability/unnerve.c +++ b/test/battle/ability/unnerve.c @@ -2,5 +2,73 @@ #include "test/battle.h" // Remember to add a PARAMETRIZE for As One in the following tests: -TO_DO_BATTLE_TEST("Unnerve prevents opposing Pokémon from eating their own berries"); -TO_DO_BATTLE_TEST("Unnerve doesn't prevent opposing Pokémon from using Natural Gift"); +SINGLE_BATTLE_TEST("Unnerve prevents opposing Pokémon from eating their own berries") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gItemsInfo[ITEM_RAWST_BERRY].holdEffect == HOLD_EFFECT_CURE_BRN); + PLAYER(mon) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RAWST_BERRY); Status1(STATUS1_BURN); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(player, ability); + NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + HP_BAR(opponent); + } +} + +SINGLE_BATTLE_TEST("Unnerve doesn't prevent opposing Pokémon from using Natural Gift") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + ASSUME(gMovesInfo[MOVE_NATURAL_GIFT].effect == EFFECT_NATURAL_GIFT); + PLAYER(mon) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ORAN_BERRY); } + } WHEN { + TURN { MOVE(opponent, MOVE_NATURAL_GIFT); } + } SCENE { + ABILITY_POPUP(player, ability); + HP_BAR(player); + } +} + +SINGLE_BATTLE_TEST("Unnerve prints the correct string (player)") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + PLAYER(mon) { Ability(ability); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(player, ability); + MESSAGE("The opposing team is too nervous to eat Berries!"); + } +} + +SINGLE_BATTLE_TEST("Unnerve prints the correct string (opponent)") +{ + u16 mon; + u16 ability; + PARAMETRIZE { mon = SPECIES_JOLTIK, ability = ABILITY_UNNERVE; } + PARAMETRIZE { mon = SPECIES_CALYREX_ICE, ability = ABILITY_AS_ONE_ICE_RIDER; } + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(mon) { Ability(ability); } + } WHEN { + TURN {} + } SCENE { + ABILITY_POPUP(opponent, ability); + MESSAGE("Your team is too nervous to eat Berries!"); + } +}